Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resize UIImageView based on UIImage's size/ratio in Swift 3?

Tags:

ios

swift

I have a UIImageView and the user is able to download UIImages in various formats. The issue is that I need the UIImageView to resize based on the given Image's ratio.

Currently, I'm using Aspect fit, but the UIImageView remains empty on big parts of itself. I would like to have the UIImageView resize itself based on its content. E.g if the pic is 1:1, 4:3, 6:2, 16:9...

enter image description here

Help is very appreciated.

As requested, that is what I want:

enter image description here

I have had an UIImageView that was square, loaded with an Image in 16:7 or whatever, and the UIImageView resized to fit the size of the Image...

like image 897
David Seek Avatar asked Dec 15 '16 01:12

David Seek


People also ask

How do I change the UIImage size in Swift?

Image resize function in swift as below. func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage { let size = image. size let widthRatio = targetSize. width / size.

How do I resize the UIImage to reduce upload image size?

It reduces the image size to less than 100kb. It works proportionally! extension UIImage { class func scaleImageWithDivisor(img: UIImage, divisor: CGFloat) -> UIImage { let size = CGSize(width: img. size.

Does UIImageView have intrinsic content size?

Here is the essential problem: the only way in which UIImageView interacts with Auto Layout is via its intrinsicContentSize property. That property provides the intrinsic size of the image itself, and nothing more.


8 Answers

I spent many hours trying to find a solution to the same problem you're having and this is the only solution that worked for me (Swift 4, Xcode 9.2):

class ScaledHeightImageView: UIImageView {

    override var intrinsicContentSize: CGSize {

        if let myImage = self.image {
            let myImageWidth = myImage.size.width
            let myImageHeight = myImage.size.height
            let myViewWidth = self.frame.size.width
 
            let ratio = myViewWidth/myImageWidth
            let scaledHeight = myImageHeight * ratio

            return CGSize(width: myViewWidth, height: scaledHeight)
        }

        return CGSize(width: -1.0, height: -1.0)
    }

}

Add the class to the project and set the UIImageView to the custom class ScaledHeightImageView. The image view's content mode is Aspect Fit.

My problem is the same as the one stated in this post. Inside my prototype TableViewCell's ContentView, I have a vertical StackView constrained to each edge. Inside the StackView there was a Label, ImageView and another Label. Having the ImageView set to AspectFit was not enough. The image would be the proper size and proportions but the ImageView didn't wrap the actual image leaving a bunch of extra space between the image and label (just like in the image above). The ImageView height seemed to match height of the original image rather than the height of the resized image (after aspectFit did it's job). Other solutions I found didn't completely resolve the problem for various reasons. I hope this helps someone.

like image 78
James Avatar answered Oct 11 '22 18:10

James


I spent many hours on this, and I finally got a solution that worked for me (Swift 3):

  • in IB, I set UIImageView's 'Content Mode' to 'Aspect Fit'
  • in IB, I set UIImageView's width constraint to be equal to whatever you want (in my case, the view's width)
  • in IB, I set UIImageView's height constraint to be equal to 0, and create a referencing outlet for it (say, constraintHeight)

Then, when I need to display the image, I simply write the following (sampled from answers above):

let ratio = image.size.width / image.size.height
let newHeight = myImageView.frame.width / ratio
constraintHeight.constant = newHeight
view.layoutIfNeeded()

Basically, this ensures that the image fills the UIImageView's width and forces the UIImageView's height to be equal to the image's height after it scaled

like image 44
Merricat Avatar answered Oct 11 '22 20:10

Merricat


It looks like you want to resize an ImageView according to the image ratio and the container view's size, here is the example in Swift (Sorry,the former answer with a bug, I fixed it):

   let containerView = UIView(frame: CGRect(x:0,y:0,width:320,height:500))
   let imageView = UIImageView()

    if let image = UIImage(named: "a_image") {
        let ratio = image.size.width / image.size.height
        if containerView.frame.width > containerView.frame.height {
            let newHeight = containerView.frame.width / ratio
            imageView.frame.size = CGSize(width: containerView.frame.width, height: newHeight)
        }
        else{
            let newWidth = containerView.frame.height * ratio
            imageView.frame.size = CGSize(width: newWidth, height: containerView.frame.height)
        }
    }
like image 39
Yun CHEN Avatar answered Oct 11 '22 19:10

Yun CHEN


SWIFT 5

This is what I have done in my project:

Place an ImageView in ViewController and create an outlet in viewDidLoad() named imageView.

override func viewDidLoad() {
        super.viewDidLoad()

        var image = UIImage(contentsOfFile: "yourFilePath")!
        var aspectR: CGFloat = 0.0

        aspectR = image.size.width/image.size.height

        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.image = image
        imageView.contentMode = .scaleAspectFit

        NSLayoutConstraint.activate([
        imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        imageView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor),
        imageView.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor),
        imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 1/aspectR)
        ])
}

The last 3 lines of NSLayoutConstraint.activate array ensures that the image width stays within the bounds of the container view and the height stays in proportion to width (i.e. the aspect ratio is maintained and height of imageView is shrunk to minimum required value).


View Controller in Interface Builder: Main.storyboard

Snapshot of UIImageView in running app: appSnapshot

like image 30
Ankit Shah Avatar answered Oct 11 '22 18:10

Ankit Shah


The solution I used is based on olearyj234's solution, but makes having no image take up essentially no space (or more specifically the minimum iOS will accept). It also uses ceil to avoid problems which can occur with non-integer values when UIImageView's are embedded in things like scrolling cells.

class FixedWidthAspectFitImageView: UIImageView
{

    override var intrinsicContentSize: CGSize
    {
        // VALIDATE ELSE RETURN
        // frameSizeWidth
        let frameSizeWidth = self.frame.size.width

        // image
        // ⓘ In testing on iOS 12.1.4 heights of 1.0 and 0.5 were respected, but 0.1 and 0.0 led intrinsicContentSize to be ignored.
        guard let image = self.image else
        {
            return CGSize(width: frameSizeWidth, height: 1.0)
        }

        // MAIN
        let returnHeight = ceil(image.size.height * (frameSizeWidth / image.size.width))
        return CGSize(width: frameSizeWidth, height: returnHeight)
    }

}
like image 22
John Bushnell Avatar answered Oct 11 '22 20:10

John Bushnell


The solution is also based on olearyj234's solution, but I think this will help more people.

@IBDesignable
class DynamicImageView: UIImageView {

    @IBInspectable var fixedWidth: CGFloat = 0 {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }
    @IBInspectable var fixedHeight: CGFloat = 0 {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        var size = CGSize.zero
        if fixedWidth > 0 && fixedHeight > 0 { // 宽高固定
            size.width = fixedWidth
            size.height = fixedHeight
        } else if fixedWidth <= 0 && fixedHeight > 0 { // 固定高度动态宽度
            size.height = fixedHeight
            if let image = self.image {
                let ratio = fixedHeight / image.size.height
                size.width = image.size.width * ratio
            }
        } else if fixedWidth > 0 && fixedHeight <= 0 { // 固定宽度动态高度
            size.width = fixedWidth
            if let image = self.image {
                let ratio = fixedWidth / image.size.width
                size.height = image.size.height * ratio
            }
        } else { // 动态宽高
            size = image?.size ?? .zero
        }
        return size
    }

}
like image 27
user8969729 Avatar answered Oct 11 '22 18:10

user8969729


A lot of the answers here are using the frame when calculating the intrinsicContentSize. The docs discourage this:

This intrinsic size must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height, for example.

I've found wanting the UIImageView height to be dynamically set according to:

  • the aspect ratio of the image
  • a fixed width

to be a common problem, I provide a possible solution below.

Solution

I think this is best solved by adding an NSLayoutConstraint to the UIImageView which constrains the widthAnchor and heightAnchor (or vice versa) such that the multiplier matches the aspect ratio of the image. I have created a UIImageView subclass that does exactly this:

import UIKit

/// `AdjustableImageView` is a `UIImageView` which should have a fixed width or height.
/// It will add an `NSLayoutConstraint` such that it's width/height (aspect) ratio matches the
/// `image` width/height ratio.
class AdjustableImageView: UIImageView {

    /// `NSLayoutConstraint` constraining `heightAnchor` relative to the `widthAnchor`
    /// with the same `multiplier` as the inverse of the `image` aspect ratio, where aspect
    /// ratio is defined width/height.
    private var aspectRatioConstraint: NSLayoutConstraint?

    /// Override `image` setting constraint if necessary on set
    override var image: UIImage? {
        didSet {
            updateAspectRatioConstraint()
        }
    }

    // MARK: - Init

    override init(image: UIImage?) {
        super.init(image: image)
        setup()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    // MARK: - Setup

    /// Shared initializer code
    private func setup() {
        // Set default `contentMode`
        contentMode = .scaleAspectFill

        // Update constraints
        updateAspectRatioConstraint()
    }

    // MARK: - Resize

    /// De-active `aspectRatioConstraint` and re-active if conditions are met
    private func updateAspectRatioConstraint() {
        // De-active old constraint
        aspectRatioConstraint?.isActive = false

        // Check that we have an image
        guard let image = image else { return }

        // `image` dimensions
        let imageWidth = image.size.width
        let imageHeight = image.size.height

        // `image` aspectRatio
        guard imageWidth > 0 else { return }
        let aspectRatio = imageHeight / imageWidth
        guard aspectRatio > 0 else { return }

        // Create a new constraint
        aspectRatioConstraint = heightAnchor.constraint(
            equalTo: widthAnchor,
            multiplier: aspectRatio
        )

        // Activate new constraint
        aspectRatioConstraint?.isActive = true
    }
}
like image 38
Bechsh Avatar answered Oct 11 '22 18:10

Bechsh


In case the Content mode is set aspectFit or aspectFill the answer would vary:

extension UIImageView {
var intrinsicScaledContentSize: CGSize? {
    switch contentMode {
    case .scaleAspectFit:
        // aspect fit
        if let image = self.image {
            let imageWidth = image.size.width
            let imageHeight = image.size.height
            let viewWidth = self.frame.size.width
            
            let ratio = viewWidth/imageWidth
            let scaledHeight = imageHeight * ratio
            
            return CGSize(width: viewWidth, height: scaledHeight)
        }
    case .scaleAspectFill:
        // aspect fill
        if let image = self.image {
            let imageWidth = image.size.width
            let imageHeight = image.size.height
            let viewHeight = self.frame.size.width
            
            let ratio = viewHeight/imageHeight
            let scaledWidth = imageWidth * ratio
            
            return CGSize(width: scaledWidth, height: imageHeight)
        }
    
    default: return self.bounds.size
    }
    
    return nil
}

}

like image 37
sajede Nouri Avatar answered Oct 11 '22 18:10

sajede Nouri