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