17

For a UIImageView, I am using the aspect fill content mode. It's OK, but for some images, it will cut from the top because I used clipsToBounds = true. So here is what I want: I want to make the two filters active at the same time, for example:

Here is an image view that I set to aspect fill:

image view with the aspect fill content mode

...and an image view I set using contentMode = .top:

image view with the top content mode

So I want to merge these two content modes. Is it possible? Thanks in advance.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
V-Dev
  • 488
  • 1
  • 4
  • 16

6 Answers6

20

Note This approach involves literally building a new image, which is extremely non-performant and would only be used in unusual situations. It's trivial to move and scale a UIImageView in a number of different ways.

--

Update: device scaling is now properly handled, thanks to budidino for that!

You should resize the image, so that it will have the width of your image view, but by keeping its aspect ratio. After that, set the image view's content mode to .top and enable clipping to bounds for it.

The resizeTopAlignedToFill function is a modified version of this answer.

func setImageView() {
    imageView.contentMode = .top
    imageView.clipsToBounds = true

    let image = <custom image>
    imageView.image = image.resizeTopAlignedToFill(newWidth: imageView.frame.width)
}

extension UIImage {
    func resizeTopAlignedToFill(newWidth: CGFloat) -> UIImage? {
        let newHeight = size.height * newWidth / size.width
        
        let newSize = CGSize(width: newWidth, height: newHeight)
        
        UIGraphicsBeginImageContextWithOptions(newSize, false, UIScreen.main.scale)
        draw(in: CGRect(origin: .zero, size: newSize))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return newImage
    }
}
Fattie
  • 27,874
  • 70
  • 431
  • 719
Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
5

Try this:

imageView.contentMode = UIViewContentModeTop;
imageView.image = [UIImage imageWithCGImage:image.CGImage scale:image.size.width / imageView.frame.size.width orientation:UIImageOrientationUp];
3

This is my solution. First, you set comtentMode to top then you resize image

func resize(toWidth scaledToWidth: CGFloat) -> UIImage {
        let image = self
        let oldWidth = image.size.width
        let scaleFactor = scaledToWidth / oldWidth
        let newHeight = image.size.height * scaleFactor
        let newWidth = oldWidth * scaleFactor
        let scaledSize = CGSize(width:newWidth, height:newHeight)
        UIGraphicsBeginImageContextWithOptions(scaledSize, false, 0)
        image.draw(in: CGRect(x: 0, y: 0, width: scaledSize.width, height: scaledSize.height))
        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return scaledImage!
    }

imageView.contentMode = .top
imageView.image = imageView.resize(toWidth:imageView.frame.width)
1

The accepted answer was scaling down the image and therefore lowering the quality on @2x and @3x devices. This should produce the same result with better image quality:

extension UIImage {
  func resizeTopAlignedToFill(containerSize: CGSize) -> UIImage? {
    let scaleTarget = containerSize.height / containerSize.width
    let scaleOriginal = size.height / size.width

    if scaleOriginal <= scaleTarget { return self }

    let newHeight = size.width * scaleTarget
    let newSize = CGSize(width: size.width, height: newHeight)

    UIGraphicsBeginImageContextWithOptions(newSize, false, scale)
    self.draw(in: CGRect(origin: .zero, size: newSize))
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return newImage
  }
}

then to use it just:

imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.image = UIImage(named: "portrait").resizeTopAlignedToFill(containerSize: imageView.frame.size)
budiDino
  • 13,044
  • 8
  • 95
  • 91
-1

as a matter of performance I would suggest to put the UIImageView with the full image size / aspect ratio into a ContainerView with clipsToBounds set to true.

-2

Swift 5:

imageView.contentMode = .top
if let image = UIImage(named: "xxx"),
   let cgImage = image.cgImage {
    imageView.image = UIImage(cgImage: cgImage, scale: image.size.width / view.frame.size.width * UIScreen.main.scale, orientation: .up)
}
Sam Xu
  • 252
  • 3
  • 5