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

15

89

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...

Containment answered 15/12, 2016 at 1:5 Comment(5)
How about 'Aspect fill' ?Ostyak
Aspect fill is not nearly an option since it cuts the pictures...Containment
not quite sure what your ideal result looks like, post a picture of what you want your app to do.Induline
@Yohst, posted. I thought it was pretty clear what I was requesting... Sorry if that was not the caseContainment
I have the same problem I got solutions Propotionately height-widthThreat
Z
46

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)
        }
    }
Zorn answered 15/12, 2016 at 1:42 Comment(12)
i'm not using a containerView, currently trying to re-code your solution.Containment
@DavidSeek, containerView here is for demonstration. If you are not using it, the controller's root view could be considered as the container view.Zorn
@DavidSeek, according to your screenshots, the width looks fixed, only the height is dynamic. If so, just use following sentence to get height: let newHeight = containerView.frame.width / ratio to get dynamic height.Zorn
but again, I don't have a containerView ;)Containment
@ DavidSeek, let newHeight = self.view.frame.width / ratioZorn
will check it out in a couple of minutesContainment
it was not perfectly working because of the constraints, but it lead me to the right idea. thank youContainment
@DavidSeek, if you are usingZorn
if you are using AutoLayout Constraint, you should change imageView's height constraint's constant value after right height calculated. Could use property to bind the Constraint what you define in XIB or Storyboard @property (strong, nonatomic) NSLayoutConstraint *imageViewHeight; Then change it like this: imageViewHeight.constant = newHeight;Zorn
i did and it is working pretty good. only when scrolling through the tableview, same cells are receiving different heights. but this is another issue. thank you thoContainment
For me it worked using containerView.frame.width < containerView.frame.height instead of containerView.frame.width > containerView.frame.height, also this method it's not working correctly if the image has the same height and width.Alli
For me it doesn't work if image is of larger size ... 1000x1500.Cough
I
93

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.

Irfan answered 22/2, 2018 at 21:8 Comment(14)
This code worked great for me to adjust the height of the image loaded from the Web that filled the entire width of the UIImageView. I just needed to set the content mode to Aspect Fill (important) and not set the view's Height constraint, but set Intrinsic size to Placeholder at the bottom of the Size inspector (and type any value in the Height field below).Pelham
Excellent solution - gives much better control over size than setting content mode. I think it's too easy to forget that there are override-able properties such as this. Thanks!Onlybegotten
This is a great solution. Thank you so much, olearyj234 :)Headrail
Hi thanks for your solution. But there is a problem when image rotates. the imageView size does not change accordingly. How to make it changes when device rotates?Lawabiding
In some rare cases, this solution didn't worked for me yet. Especially when using the ImageView in a StackView. This comment along with this linked gist helped me to complete it.Biotope
I think you might want to use ceil here when calculating scaledHeight.Yearling
How can this be used to set a UITableViewCell size once an image is posted/added? This does work and will size the UIImageView, but I don't see the change until I scroll my table view.Donoghue
@LukeIrvin. Did you found any solution?Sizing
@jaypatel I have not found a solution yet.Donoghue
@Pelham point about using Aspect Fill is very important. I spent a good hour trying to figure out why sometimes the image would scale properly and sometimes it wouldn't. Its because I was using aspect fit. Make sure to use auto-layout constraints on the side edges, put a placeholder height, and use Aspect-Fill content type. Those were the best results for me.Lindsy
How about finding image origin position without scale imageview size?Nabal
Wow!!! Can't believe I finally found the solution to my lifelong problem. Tears in my eyes.Hesychast
Hi. Could you please say what should be a height of imageView when using this extension? I don't need to set height constraint?Sair
Could you please say how can I use this extension programmatically? and what should be height constraint of imageView?Sair
T
72

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

Teishateixeira answered 23/6, 2018 at 1:2 Comment(15)
Simplest solution in my opinion.Octaviooctavius
I love this solution! Well structured.Whitaker
Simple & perfect solutionAjay
Did not worked on ipad.. still empty spaces to the widthSofko
You have the ratio. Instead of using a constant constrain the height of the view to the width of the view using a multiplier of image.size.height / image.size.width. That means you would need to create the constraint programmatically (since the multiplier is read only), but the height would adjust if the width of the view changed.Cerebration
What you suggest would be a nice solution if my width was indeed variable, but my width doesn't change. Mine was a simple solution that worked for me, for what I specifically needed. While powerful, I find that constraints are kinda of a pain to deal with already, so I tend to stay away from dealing with them programmatically as much as possible ;)Teishateixeira
I'm facing an issue where I am resizing cells in my UITableView to go edge-to-edge. I want them to fill the full width and have the height adjust accordingly. This above does the trick but I'm loading my images from Firebase Storage so they don't update initially. How can I use this so that the images show updated at first load?Donoghue
@LukeIrvin I think the solution you're looking for is to call the code above when the image has been fetched (i.e. through a delegate, or a completion closure)Teishateixeira
@RaimondoPrevidi I've tried that but still not workingDonoghue
@LukeIrvin Inside what function did you try calling the code above? I think I did a similar thing inside my custom Cell's layoutSubviews(). Try calling the code from it inside there. If that doesn't work, I'd look into trying inside Tableview's tableView(_:willDisplay:forRowAt:)Teishateixeira
@RaimondoPrevidi got it. I believe doing this in layoutSubviews will do what I need. I ran one test. Only thing I noticed is when doing this my table view does not start at my tables first row. It starts at the first row that contains an image. I'll debug it more over the weekend. Thanks for your guidance!Donoghue
@LukeIrvin glad I could help. You could always wait until your data source has finished fetching all the images, and only then allow it to reloadData(), but that might not be your desired outcome ;)Teishateixeira
Thanks for the simple solution :)Twana
In my case width/height is infinite...So it doesn't workFlexion
This is very simple solution that works great!Dispermous
Z
46

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)
        }
    }
Zorn answered 15/12, 2016 at 1:42 Comment(12)
i'm not using a containerView, currently trying to re-code your solution.Containment
@DavidSeek, containerView here is for demonstration. If you are not using it, the controller's root view could be considered as the container view.Zorn
@DavidSeek, according to your screenshots, the width looks fixed, only the height is dynamic. If so, just use following sentence to get height: let newHeight = containerView.frame.width / ratio to get dynamic height.Zorn
but again, I don't have a containerView ;)Containment
@ DavidSeek, let newHeight = self.view.frame.width / ratioZorn
will check it out in a couple of minutesContainment
it was not perfectly working because of the constraints, but it lead me to the right idea. thank youContainment
@DavidSeek, if you are usingZorn
if you are using AutoLayout Constraint, you should change imageView's height constraint's constant value after right height calculated. Could use property to bind the Constraint what you define in XIB or Storyboard @property (strong, nonatomic) NSLayoutConstraint *imageViewHeight; Then change it like this: imageViewHeight.constant = newHeight;Zorn
i did and it is working pretty good. only when scrolling through the tableview, same cells are receiving different heights. but this is another issue. thank you thoContainment
For me it worked using containerView.frame.width < containerView.frame.height instead of containerView.frame.width > containerView.frame.height, also this method it's not working correctly if the image has the same height and width.Alli
For me it doesn't work if image is of larger size ... 1000x1500.Cough
D
12

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

Dorison answered 30/1, 2021 at 7:56 Comment(2)
For anyone looking for an autolayout answer, I'd recommend this answer! Very cleaver, thanks Ankit!Binky
I agree with Jan - I looked at a lot of answers and this worked for in autolayout! Thanks!Sinless
K
4

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
    }
}
Kaliope answered 15/3, 2021 at 10:17 Comment(0)
Y
3

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)
    }

}
Yearling answered 1/3, 2019 at 21:27 Comment(3)
How can this be used with a UITableView that has UIImageView's in the cells? My goal is when a user posts a new image (think Instagram); the cell resizes at the first reloadData and I don't have to scroll the cell off screen for it to update it's height.Donoghue
Constrain all four sides of the UIImageView (of class FixedWidthAspectFitImageView) to the edges of the cell (or elements inside the cell which are fixed to the edges of the cell) and the cell's intrinsic content size will become what it needs to be for the image being loaded into it. If your goal is to see the cell be sized properly as soon as possible, you'd need to set the image right away. If the image is changing later I'd expect reloadData to be sufficient to reset the cell height, but I haven't tried that.Yearling
Best way to 'set the image right away'? I've tried this but the cell still loads wrong and I have to reloadData again to see the cell resize.Donoghue
Y
1

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
    }

}
Ynez answered 15/5, 2020 at 4:13 Comment(0)
B
1

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
}

}

Bradney answered 6/6, 2021 at 15:3 Comment(1)
Hi. Could you please say how to set imageView constraints in this case? Especially height constraintSair
R
1

iOS Create UIImageView based on UIImage size

for Frame-based[About]

func addImageView(image: UIImage, x: CGFloat = 0, y: CGFloat = 0) {
    let imageViewSize = self.getImageViewSize(image: image, minSize: minSize)
    let imageView = UIImageView(
        frame: CGRect(
            x: x,
            y: y,
            width: imageViewSize.width,
            height: imageViewSize.height
        )
    )
    imageView.image = image

    self.view.addSubview(imageView)
}

func getImageViewSize(image: UIImage, minSize: Double) -> CGSize {
    let imageSize = image.size
    
    let imageWidth = imageSize.width
    let imageHeight = imageSize.height
    
    let imageRatio = imageWidth/imageHeight
    
    if imageWidth < imageHeight {
        //portrait
        return CGSize(width: minSize, height: minSize / imageRatio)
    } else {
        //landscape
        return CGSize(width: minSize * imageRatio, height: minSize)
    }
}

To work with AutoLayout you can adjust constraints(e.g. width height)

Size for maxWidth x maxHeight

func getViewSize(size: CGSize, maxSize: CGSize) -> CGSize {
    let width = size.width
    let height = size.height
    
    let maxWidth = maxSize.width
    let maxHeight = maxSize.height
    
    let resultWidth = maxHeight * width / height
    if resultWidth <= maxWidth {
        return CGSize(width: resultWidth, height: maxHeight)
    } else {
//        resultHeight <= maxHeight
        let resultHeight = maxWidth * height / width
        return CGSize(width: maxWidth, height: resultHeight)
    }
}

func setImage(_ image: UIImage?) {
    if let image = image {
        if let superView = self.uiImageView.superview {
            let size = getViewSize(size: image.size, maxSize: superView.frame.size)
            
            self.uiImageWidthConstraint.constant = size.width
            self.uiImageHeightConstraint.constant = size.height
        }
    }
}
Romanism answered 15/9, 2023 at 13:1 Comment(0)
C
0

Set your imageView to aspectFit, that will resize the image to not exceed your imageView's frame.

You can get the size of your UIImage of your imageView with logic from this question - basically just get the height and width of the UIImage.

Calculate the ratio and set the width/height of the imageView to fit you screen.

There is also a similar question to your that you might get you answer from.

Culottes answered 15/12, 2016 at 1:32 Comment(2)
You got the question the wrong way. Quoting the OP: I would like to have the UIImageView resize itself based on its content. This means: He would like to resize the UIImageView w.r.t the UIImage it contains. He does not want to resize the UIImage w.r.t the UIImageView in which the UIImage is contained. In other words, he wants to let the UIImage determine the size of the UIImageView. He does not want the UIImageView to determine the size of the UIImage.Detachment
@Detachment not sure how you understood OP's question but I basically gave the theory of the accepted answer w/o codes... but if you need a simpler explanation, I suggested to get the aspect ratio of the Image then set the UIImageView to the same ratio so that there will be no extra space.Culottes
C
0

I modified @user8969729 's solution to replace the "fixed" width/height with "max", thus more like @JoshuaHart's solution. Handle the maxWidth == 0 / maxHeight == 0 case as you wish, since I always had both set I just quickly ignored that case.

public class AdjustsViewBoundsImageView: UIImageView {
   
   /// The maximum width that you want this imageView to grow to.
   @objc dynamic var maxWidth: CGFloat = 0 {
      didSet {
         invalidateIntrinsicContentSize()
      }
   }
   
   /// The maximum height that you want this imageView to grow to.
   @objc dynamic var maxHeight: CGFloat = 0 {
      didSet {
         invalidateIntrinsicContentSize()
      }
   }
   
   private var maxAspectRatio: CGFloat { return maxWidth / maxHeight }
   
   override public var intrinsicContentSize: CGSize {
      guard let classImage = self.image else { return super.intrinsicContentSize }
      if maxHeight == 0 || maxWidth == 0 {
         return super.intrinsicContentSize
      }
      
      let imageWidth = classImage.size.width
      let imageHeight = classImage.size.height
      let aspectRatio = imageWidth / imageHeight
      
      // Width is greater than height, return max width image and new height.
      if imageWidth > imageHeight {
         let newHeight = maxWidth/aspectRatio
         return CGSize(width: maxWidth, height: newHeight)
      }
      
      // Height is greater than width, return max height and new width.
      if imageHeight > imageWidth {
         // If the aspect ratio is larger than our max ratio, then using max width
         // will be hit before max height.
         if aspectRatio > maxAspectRatio {
            let newHeight = maxWidth/aspectRatio
            return CGSize(width: maxWidth, height: newHeight)
         }
         let newWidth = maxHeight * aspectRatio
         return CGSize(width: newWidth, height: maxHeight)
      }
      
      // Square image, return the lesser of max width and height.
      let squareMinimumValue = min(maxWidth, maxHeight)
      return CGSize(width: squareMinimumValue, height: squareMinimumValue)
   }
}
Citrine answered 8/9, 2020 at 1:19 Comment(0)
F
0

If you want scale UIImageView by width and height - use this class:

import UIKit

class AutoSizeImageView: UIImageView {
    
    @IBInspectable var maxSize: CGFloat = 100
    
    // MARK: Methods
    
    func updateSize() {
        let newSize = getSize()
        
        snp.remakeConstraints { make in
            make.width.equalTo(newSize.width)
            make.height.equalTo(newSize.height)
        }
    }
    
    private func getSize() -> CGSize {
        guard let image = image else { return .zero }
        
        if image.size.width == image.size.height { return CGSize(width: maxSize, height: maxSize) }
        
        if image.size.width > image.size.height {
            let widthRatio = maxSize / image.size.width
            let scaledHeight = image.size.height * widthRatio
            return CGSize(width: maxSize, height: scaledHeight)
        }
        
        let heightRatio = maxSize / image.size.height
        let scaledWidth = image.size.width * heightRatio
        return CGSize(width: scaledWidth, height: maxSize)
    }
    
}

Call it like this:

@IBOutlet weak var imageView: AutoSizeImageView!

imageView.image = image
imageView.updateSize()

Please note I've used SnapKit to manage constraints:

snp.remakeConstraints { make in
    make.width.equalTo(newSize.width)
    make.height.equalTo(newSize.height)
}
Foolhardy answered 22/2, 2022 at 11:39 Comment(0)
E
0

Change solution for Merricat. Hi. Use your solution in collection view cell, make onboarding. First launch and scroll not not give right height. I add this -

contentView.layoutIfNeeded()
if let image = UIImage(named: "\(data.imageName)") {
   let ratio = image.size.width / image.size.height
   let newHeight = imageView.frame.width / ratio
   imageView.image = image
   imageHeightConstraint.priority = .defaultHigh
   imageHeightConstraint.constant = newHeight
   contentView.layoutIfNeeded()
 }
Electroencephalogram answered 13/2, 2023 at 10:13 Comment(0)
A
0

Here you can find a good class for that.

//
//  ResizeableImageView.swift
//
//  Created by Amir Daliri on 8.08.2023.
//  Copyright © 2023 Amir Daliri. All rights reserved.
//

import UIKit

/// Resizeable Image View that takes a max height and max width
/// Will resize the imageView to best fit for the aspect ratio of the image,
/// With the given space provided.
public class ResizeableImageView: UIImageView {
    private var widthConstraint: NSLayoutConstraint?
    private var heightConstraint: NSLayoutConstraint?
    
    // MARK: - INITIALIZERS:
    
    public override init(image: UIImage?) {
        super.init(image: image)
    }
    
    /// Given the max width and height, resizes the imageView to fit the image.
    ///  - IMPORTANT: This subclass adds a height and width constraint.
    /// - Parameters:
    ///   - image: (UIImage?) The image to add to the imageView.
    ///   - maxWidth: (CGFloat) The max width you would like the imageView to grow to.
    ///   - maxHeight: (CGFloat) The max height you would like the imageView to grow to.
    convenience init(image: UIImage?, maxWidth: CGFloat, maxHeight: CGFloat) {
        self.init(image: image)
        
        widthConstraint = constrain(width: maxWidth)
        heightConstraint = constrain(height: maxHeight)
    }
    
    /// Required initializer for creating a view from the nib or storyboard.
    ///
    /// This initializer is essential when you want to instantiate the custom view
    /// from a storyboard or nib file. Failing to implement this initializer will
    /// result in a runtime crash when the system tries to load the view.
    ///
    /// - Parameter aDecoder: An abstract class that serves as the basis for objects that enable
    ///                        archiving and distribution of other objects.
    /// - Returns: An optional instance of the class. It returns nil if the object can't be
    ///            initialized.
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    /// Additional setup after the view is loaded.
    ///
    /// Use this function to make any additional configurations or settings after the view has been
    /// instantiated, especially if it has been loaded from a storyboard or nib file. This can
    /// include setting default values, adding subviews, setting constraints, or any other kind
    /// of initial setup.
    private func setup() {
        // Any additional setup after loading the view, typically from a nib or storyboard.
    }

    
    // MARK: - VARIABLES:
    
    /// The maximum width that you want this imageView to grow to.
    private var maxWidth: CGFloat {
        get { widthConstraint?.constant ?? 0 }
        set { widthConstraint?.constant = newValue }
    }
    
    /// The maximum height that you want this imageView to grow to.
    private var maxHeight: CGFloat {
        get { heightConstraint?.constant ?? 0 }
        set { heightConstraint?.constant = newValue }
    }
    
    private var maxAspectRatio: CGFloat { maxWidth / maxHeight }
    
    override public var intrinsicContentSize: CGSize {
        guard let classImage = self.image else { return frame.size }
        
        let imageWidth = classImage.size.width
        let imageHeight = classImage.size.height
        let aspectRatio = imageWidth / imageHeight
        
        // Width is greater than height, return max width image and new height.
        if imageWidth > imageHeight {
            let newHeight = maxWidth/aspectRatio
            self.widthConstraint?.constant = maxWidth
            self.heightConstraint?.constant = newHeight
            return CGSize(width: maxWidth, height: newHeight)
        }
        
        // Height is greater than width, return max height and new width.
        if imageHeight > imageWidth {
            // If the aspect ratio is larger than our max ratio, then using max width
            // will be hit before max height.
            if aspectRatio > maxAspectRatio {
                let newHeight = maxWidth/aspectRatio
                self.widthConstraint?.constant = maxWidth
                self.heightConstraint?.constant = newHeight
                return CGSize(width: maxWidth, height: newHeight)
            }
            let newWidth = maxHeight * aspectRatio
            self.widthConstraint?.constant = newWidth
            self.heightConstraint?.constant = maxHeight
            return CGSize(width: newWidth, height: maxHeight)
        }
        
        // Square image, return the lesser of max width and height.
        let squareMinimumValue = min(maxWidth, maxHeight)
        self.widthConstraint?.constant = squareMinimumValue
        self.heightConstraint?.constant = squareMinimumValue
        return CGSize(width: squareMinimumValue, height: squareMinimumValue)
    }
}
// MARK: - Helper
private extension ResizeableImageView {
    
    /// Creates a width constraint for the view and activates it.
    ///
    /// - Parameter width: The width for the constraint.
    /// - Returns: The created width constraint.
    private func constrain(width: CGFloat) -> NSLayoutConstraint {
        let constraint = self.widthAnchor.constraint(equalToConstant: width)
        constraint.isActive = true
        return constraint
    }
    
    /// Creates a height constraint for the view and activates it.
    ///
    /// - Parameter height: The height for the constraint.
    /// - Returns: The created height constraint.
    private func constrain(height: CGFloat) -> NSLayoutConstraint {
        let constraint = self.heightAnchor.constraint(equalToConstant: height)
        constraint.isActive = true
        return constraint
    }
}
// MARK: - Update
extension ResizeableImageView {
    
    /// Updates the imageView with a new image and dimensions, resizing it accordingly.
    ///
    /// - Parameters:
    ///   - image: (UIImage?) The new image to add to the imageView.
    ///   - maxWidth: (CGFloat) The new max width you'd like the imageView to have.
    ///   - maxHeight: (CGFloat) The new max height you'd like the imageView to have.
    public func updateImageView(with image: UIImage?, maxWidth: CGFloat, maxHeight: CGFloat) {
        self.image = image
        
        // Remove existing constraints if any
        if let widthC = widthConstraint {
            self.removeConstraint(widthC)
        }
        
        if let heightC = heightConstraint {
            self.removeConstraint(heightC)
        }
        
        // Apply new constraints
        widthConstraint = constrain(width: maxWidth)
        heightConstraint = constrain(height: maxHeight)
        
        // Request layout update
        self.layoutIfNeeded()
    }
}
// MARK: - Public API:
extension ResizeableImageView {
    /// Sets the image view's image from a given URL string and resizes it based on provided max dimensions.
    ///
    /// - Parameters:
    ///   - urlString: The string representation of the image URL. If nil or invalid, the function will call the completion handler with nil.
    ///   - placeholder: An optional placeholder image to display while the image is being fetched.
    ///   - maxWidth: The max width you would like the imageView to grow to.
    ///   - maxHeight: The max height you would like the imageView to grow to.
    ///   - completion: An optional completion handler that gets called when the image fetch completes.
    func setImage(from urlString: String?, placeholder: UIImage? = nil, maxWidth: CGFloat, maxHeight: CGFloat, completion: ((UIImage?) -> Void)? = nil) {
        
        // Remove existing constraints if any
        if let widthC = widthConstraint {
            self.removeConstraint(widthC)
        }
        
        if let heightC = heightConstraint {
            self.removeConstraint(heightC)
        }
        
        // Apply new constraints
        widthConstraint = constrain(width: maxWidth)
        heightConstraint = constrain(height: maxHeight)
        
        // Check if the provided urlString is non-nil and can be converted into a valid URL.
        guard let urlString = urlString, let url = URL(string: urlString) else {
            completion?(nil) // Call the completion with nil if URL is invalid.
            return
        }
        
        // Start an URLSession data task to fetch the image from the URL.
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            
            // Ensure there are no errors, that data is non-nil, and the data can be converted into an image.
            guard error == nil, let data = data, let downloadedImage = UIImage(data: data) else {
                DispatchQueue.main.async {
                    // If the image fetch fails, set the imageView to display the placeholder image.
                    self.image = placeholder
                    completion?(nil) // Call the completion with nil since fetching failed.
                }
                return
            }
            
            DispatchQueue.main.async {
                // Set the downloaded image to the imageView.
                self.image = downloadedImage
                completion?(downloadedImage) // Call the completion with the downloaded image.
            }
        }.resume() // Start the URLSession data task.
    }
}
Anaesthesiology answered 8/8, 2023 at 20:24 Comment(0)
A
-1

SWIFT 5 CLASS

This can easily be converted to use IBOutlets if desired. My use-case involved programmatically adding imageViews. This is very reliable. Just create a new file in your project and add the code below.

import UIKit

/// Resizeable Image View that takes a max height and max width
/// Will resize the imageView to best fit for the aspect ratio of the image,
/// With the given space provided.
public class ResizeableImageView: UIImageView {
    private var widthConstraint: NSLayoutConstraint?
    private var heightConstraint: NSLayoutConstraint?
    
    // MARK: - INITIALIZERS:
    
    public override init(image: UIImage?) {
        super.init(image: image)
    }
    
    /// Given the max width and height, resizes the imageView to fit the image.
    ///  - IMPORTANT: This subclass adds a height and width constraint.
    /// - Parameters:
    ///   - image: (UIImage?) The image to add to the imageView.
    ///   - maxWidth: (CGFloat) The max width you would like the imageView to grow to.
    ///   - maxHeight: (CGFloat) The max height you would like the imageView to grow to.
    convenience init(image: UIImage?, maxWidth: CGFloat, maxHeight: CGFloat) {
        self.init(image: image)
        widthConstraint = constrain(width: maxWidth)
        heightConstraint = constrain(height: maxHeight)
    }
    
    @available (*, unavailable) required internal init?(coder aDecoder: NSCoder) { nil }
    
    // MARK: - VARIABLES:
    
    /// The maximum width that you want this imageView to grow to.
    private var maxWidth: CGFloat {
        get { widthConstraint?.constant ?? 0 }
        set { widthConstraint?.constant = newValue }
    }
    
    /// The maximum height that you want this imageView to grow to.
    private var maxHeight: CGFloat {
        get { heightConstraint?.constant ?? 0 }
        set { heightConstraint?.constant = newValue }
    }
    
    private var maxAspectRatio: CGFloat { maxWidth / maxHeight }
    
    override public var intrinsicContentSize: CGSize {
        guard let classImage = self.image else { return frame.size }
        
        let imageWidth = classImage.size.width
        let imageHeight = classImage.size.height
        let aspectRatio = imageWidth / imageHeight
        
        // Width is greater than height, return max width image and new height.
        if imageWidth > imageHeight {
            let newHeight = maxWidth/aspectRatio
            self.widthConstraint?.constant = maxWidth
            self.heightConstraint?.constant = newHeight
            return CGSize(width: maxWidth, height: newHeight)
        }
        
        // Height is greater than width, return max height and new width.
        if imageHeight > imageWidth {
            // If the aspect ratio is larger than our max ratio, then using max width
            // will be hit before max height.
            if aspectRatio > maxAspectRatio {
                let newHeight = maxWidth/aspectRatio
                self.widthConstraint?.constant = maxWidth
                self.heightConstraint?.constant = newHeight
                return CGSize(width: maxWidth, height: newHeight)
            }
            let newWidth = maxHeight * aspectRatio
            self.widthConstraint?.constant = newWidth
            self.heightConstraint?.constant = maxHeight
            return CGSize(width: newWidth, height: maxHeight)
        }
        
        // Square image, return the lesser of max width and height.
        let squareMinimumValue = min(maxWidth, maxHeight)
        self.widthConstraint?.constant = squareMinimumValue
        self.heightConstraint?.constant = squareMinimumValue
        return CGSize(width: squareMinimumValue, height: squareMinimumValue)
    }
}

Example Usage:

let imageView = ResizeableImageView(image: image, maxWidth: 250, maxHeight: 250)
Amboina answered 1/8, 2020 at 4:6 Comment(3)
I'm fairly sure that this class would not solve the issue described in the problem statement. The idea is to have a UIImageView resize based on the image's sizes - BUT without necessarily knowing the sizes. Example: Facebook. All kinds of height/width options. Just some thoughts. Doesn't matter to me right now as I had this problem 4 years ago :)Containment
But ultimately, you always have a bounding box of screen real estate in which the image cannot grow beyond, even if that's the scene safe area height and width. Seems like the safe area is implied by the question and screenshot.Citrine
@Citrine exactly. If it's within a containing view, and that containing view is based upon constraints that keep it snug against the leading, trailing, top and bottom, then the frame height and width of the containing view could be utilized. This is exactly what I did, and it works like a dream.Amboina

© 2022 - 2024 — McMap. All rights reserved.