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
///Works perfectly with AspectFill. Note there's no protection for obscure issues like (say) scaledImageWidth is zero.
class AlignmentImageView: UIImageView {
enum HorizontalAlignment { case left, center, right }
enum VerticalAlignment { case top, center, bottom }
var horizontalAlignment: HorizontalAlignment = .center { didSet { updateContentsRect() } }
var verticalAlignment: VerticalAlignment = .center { didSet { updateContentsRect() } }
override var image: UIImage? { didSet { updateContentsRect() } }
override func layoutSubviews() {
super.layoutSubviews()
updateContentsRect()
}
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
}
}