I have an image view that is constrained in a way so that its left, right and upper edge are pinned to its superview and its bottom edge has a fixed spacing to the label below. It doesn't have any width or height constraints so it's supposed to get its height from its image's intrinsic size. The content mode is set to Aspect Fit.

This works great if the image has a smaller or equal width as the image view (case 1 and 2, see images below). However, if the image has a width that exceeds the width of its containing image view I run into a problem (case 3):

The image is scaled down as expected so that its width matches the image view's width. But the image view doesn't update its height accordingly, leaving white spaces above and below. I understand that the image view still obtains its height from the image's original height. But due to the content mode the image's height is now a lot smaller and I want the image view to adjust its height to match the image's actual height.

I'm looking for a neat solution that works for each of the three cases, preferably one that works with IB_DESIGNABLE views as well. Any ideas?

Case 1

intrinsic width < frame width

intrinsic width < frame width

Case 2

intrinsic width = frame width

intrinsic width = frame width

Case 3

intrinsic width > frame width

intrinsic width > frame width

1

There are 1 best solutions below

2
On

You can create an extension on UIImageView in following manner:

extension UIImageView {
    @IBInspectable var shouldAdjustHeight: Bool {
        get {
            return self.frame.size.height == self.adjustedHeight;
        }
        set {
            if newValue {
                if !shouldAdjustHeight {
                    let newHeight = self.adjustedHeight

                    // add constraint to adjust height
                    self.addConstraint(NSLayoutConstraint(
                        item:self, attribute:NSLayoutAttribute.Height,
                    relatedBy:NSLayoutRelation.Equal,
                    toItem:nil, attribute:NSLayoutAttribute.NotAnAttribute,
                    multiplier:0, constant:newHeight))
            }
        }
        else {
            let newHeight = self.adjustedHeight
            // create predicate to find the height constraint that we added
            let predicate = NSPredicate(format: "%K == %d && %K == %f", "firstAttribute", NSLayoutAttribute.Height.rawValue, "constant", newHeight)
            // remove constraint
            self.removeConstraints(self.constraints.filter{ predicate.evaluateWithObject($0) })
        }
    }
}

var adjustedHeight: CGFloat {
    let screenWidth = UIScreen.mainScreen().bounds.width
    let deviceScaleFactor = screenWidth/self.bounds.size.width
    return CGFloat(ceilf(Float(deviceScaleFactor * AVMakeRectWithAspectRatioInsideRect((self.image?.size)!, self.bounds).size.height))) // deviceScaleFactor multiplied with the image size for the frame
    // I am using size class in my XIB with value as (Any,Any) and I was not getting correct frame values for a particular device so I have used deviceScaleFactor.
    // You can modify above code to calculate newHeight as per your requirement
}
}

After adding above extension you can set the property in Interface-builder as shown below.

enter image description here

On setting this property, a height constraint will be added to your imageView. The calculation of adjustedHeight may not be very neat in my solution, so you can look into it further.