UIImage aspect fit and align to top

In short, you cannot do this with a UIImageView.

One solution is to subclass a UIView containing an UIImageView and change its frame according to image size. For example, you can find one version here.


Set the UIImageView's bottom layout constraint priority to lowest (i.e. 250) and it will handle it for you.


The way to do this is to modify the contentsRect of the UIImageView layer. The following code from my project (sub class of UIImageView) assumes scaleToFill and offsets the image such that it aligns top, bottom, left or right instead of the default center alignment. For aspectFit is would be a similar solution.

typedef NS_OPTIONS(NSUInteger, AHTImageAlignmentMode) {
    AHTImageAlignmentModeCenter = 0,
    AHTImageAlignmentModeLeft = 1 << 0,
    AHTImageAlignmentModeRight = 1 << 1,
    AHTImageAlignmentModeTop = 1 << 2,
    AHTImageAlignmentModeBottom = 1 << 3,
    AHTImageAlignmentModeDefault = AHTImageAlignmentModeCenter,
};

- (void)updateImageViewContentsRect {
    CGRect imageViewContentsRect = CGRectMake(0, 0, 1, 1);

    if (self.image.size.height > 0 && self.bounds.size.height > 0) {

        CGRect imageViewBounds = self.bounds;
        CGSize imageSize = self.image.size;

        CGFloat imageViewFactor = imageViewBounds.size.width / imageViewBounds.size.height;
        CGFloat imageFactor = imageSize.width / imageSize.height;

        if (imageFactor > imageViewFactor) {
            //Image is wider than the view, so height will match
            CGFloat scaledImageWidth = imageViewBounds.size.height * imageFactor;
            CGFloat xOffset = 0.0;
            if (BM_CONTAINS_BIT(self.alignmentMode, AHTImageAlignmentModeLeft)) {
                xOffset = -(scaledImageWidth - imageViewBounds.size.width) / 2;
            } else if (BM_CONTAINS_BIT(self.alignmentMode, AHTImageAlignmentModeRight)) {
                xOffset = (scaledImageWidth - imageViewBounds.size.width) / 2;
            }
            imageViewContentsRect.origin.x = (xOffset / scaledImageWidth);
        } else if (imageFactor < imageViewFactor) {
            CGFloat scaledImageHeight = imageViewBounds.size.width / imageFactor;

            CGFloat yOffset = 0.0;
            if (BM_CONTAINS_BIT(self.alignmentMode, AHTImageAlignmentModeTop)) {
                yOffset = -(scaledImageHeight - imageViewBounds.size.height) / 2;
            } else if (BM_CONTAINS_BIT(self.alignmentMode, AHTImageAlignmentModeBottom)) {
                yOffset = (scaledImageHeight - imageViewBounds.size.height) / 2;
            }
            imageViewContentsRect.origin.y = (yOffset / scaledImageHeight);
        }
    }
    self.layer.contentsRect = imageViewContentsRect;
}

Swift version

class AlignmentImageView: UIImageView {

    enum HorizontalAlignment {
        case left, center, right
    }

    enum VerticalAlignment {
        case top, center, bottom
    }


    // MARK: Properties

    var horizontalAlignment: HorizontalAlignment = .center
    var verticalAlignment: VerticalAlignment = .center


    // MARK: Overrides

    override var image: UIImage? {
        didSet {
            updateContentsRect()
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        updateContentsRect()
    }


    // MARK: Content layout

    private func updateContentsRect() {
        var contentsRect = CGRect(origin: .zero, size: CGSize(width: 1, height: 1))

        guard let imageSize = image?.size else {
            layer.contentsRect = contentsRect
            return
        }

        let viewBounds = bounds
        let imageViewFactor = viewBounds.size.width / viewBounds.size.height
        let imageFactor = imageSize.width / imageSize.height

        if imageFactor > imageViewFactor {
            // Image is wider than the view, so height will match
            let scaledImageWidth = viewBounds.size.height * imageFactor
            var xOffset: CGFloat = 0.0

            if case .left = horizontalAlignment {
                xOffset = -(scaledImageWidth - viewBounds.size.width) / 2
            }
            else if case .right = horizontalAlignment {
                xOffset = (scaledImageWidth - viewBounds.size.width) / 2
            }

            contentsRect.origin.x = xOffset / scaledImageWidth
        }
        else {
            let scaledImageHeight = viewBounds.size.width / imageFactor
            var yOffset: CGFloat = 0.0

            if case .top = verticalAlignment {
                yOffset = -(scaledImageHeight - viewBounds.size.height) / 2
            }
            else if case .bottom = verticalAlignment {
                yOffset = (scaledImageHeight - viewBounds.size.height) / 2
            }

            contentsRect.origin.y = yOffset / scaledImageHeight
        }

        layer.contentsRect = contentsRect
    }

}

this will make the image fill the width and occupy only the height it needs to fit the image (widthly talking)

swift 4.2:

let image = UIImage(named: "my_image")!
let ratio = image.size.width / image.size.height
cardImageView.widthAnchor
  .constraint(equalTo: cardImageView.heightAnchor, multiplier: ratio).isActive = true

I had similar problem. Simplest way was to create own subclass of UIImageView. I add for subclass 3 properties so now it can be use easly without knowing internal implementation:

@property (nonatomic) LDImageVerticalAlignment imageVerticalAlignment;
@property (nonatomic) LDImageHorizontalAlignment imageHorizontalAlignment;
@property (nonatomic) LDImageContentMode imageContentMode;

You can check it here: https://github.com/LucasssD/LDAlignmentImageView