UIImage aspect fit and align to top

IosCocoaUiimageviewAspect Fit

Ios Problem Overview


It looks like aspect fit aligns the image to the bottom of the frame by default. Is there a way to override the alignment while keeping aspect fit intact?

** EDIT **

This question predates auto layout. In fact, auto layout was being revealed in WWDC 2012 the same week this question was asked

Ios Solutions


Solution 1 - Ios

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.

Solution 2 - Ios

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

Solution 3 - Ios

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
    }

}

Solution 4 - Ios

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

Solution 5 - Ios

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

Solution 6 - Ios

  1. Add the Aspect Ratio constraint with your image proportions.
  2. Do not pin UIImageView to bottom.
  3. If you want to change the UIImage dynamically remember to update aspect ratio constraint.

Solution 7 - Ios

I solved this natively in Interface Builder by setting a constraint on the height of the UIImageView, since the image would always be 'pushed' up when the image was larger than the screen size.

More specifically, I set the UIImageView to be the same height as the View it is in (via height constraint), then positioned the UIImageView with spacing constraints in IB. This results in the UIImageView having an 'Aspect Fit' which still respects the top spacing constraint I set in IB.

Solution 8 - Ios

If you are able to subclass UIImageView, then you can just override the image var.

override var image: UIImage? {
    didSet {
        self.sizeToFit()
    }
}

In Objective-C you can do the same thing by overriding the setter.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJacksonkrView Question on Stackoverflow
Solution 1 - IosMundiView Answer on Stackoverflow
Solution 2 - IosprotspaceView Answer on Stackoverflow
Solution 3 - IosWerner AltewischerView Answer on Stackoverflow
Solution 4 - Iosno_ripcordView Answer on Stackoverflow
Solution 5 - IosŁukasz DudaView Answer on Stackoverflow
Solution 6 - IosŁukasz GierałtowskiView Answer on Stackoverflow
Solution 7 - IosDavid TView Answer on Stackoverflow
Solution 8 - IosDan RosenstarkView Answer on Stackoverflow