86

for my UIImageView I choose Aspect Fit (InterfaceBuilder) but how can I change the vertical alignment?

pableiros
  • 14,932
  • 12
  • 99
  • 105
Maurice Raguse
  • 4,437
  • 2
  • 29
  • 46

12 Answers12

41

[EDIT - this code is a bit moldy being from 2011 and all but I incorporated @ArtOfWarefare's mods]

You can't do this w/ UIImageView. I created a simple UIView subclass MyImageView that contains a UIImageView. Code below.

// MyImageView.h
#import <UIKit/UIKit.h>

@interface MyImageView : UIView {
    UIImageView *_imageView;
}

@property (nonatomic, assign) UIImage *image;

@end

and

// MyImageView.m

#import "MyImageView.h"

@implementation MyImageView

@dynamic image;

- (id)initWithCoder:(NSCoder*)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        self.clipsToBounds = YES;
        _imageView = [[UIImageView alloc] initWithFrame:self.bounds];
        _imageView.contentMode = UIViewContentModeScaleAspectFill;
        [self addSubview:_imageView];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.clipsToBounds = YES;
        _imageView = [[UIImageView alloc] initWithFrame:self.bounds];
        _imageView.contentMode = UIViewContentModeScaleAspectFill;
        [self addSubview:_imageView];
    }
    return self;
}

- (id)initWithImage:(UIImage *)anImage
{
    self = [self initWithFrame:CGRectZero];
    if (self) {
        _imageView.image = anImage;
        [_imageView sizeToFit];

        // initialize frame to be same size as imageView
        self.frame = _imageView.bounds;        
    }
    return self;
}

// Delete this function if you're using ARC
- (void)dealloc
{
    [_imageView release];
    [super dealloc];
}

- (UIImage *)image
{
    return _imageView.image;
}

- (void)setImage:(UIImage *)anImage
{
    _imageView.image = anImage;
    [self setNeedsLayout];
}

- (void)layoutSubviews
{
    if (!self.image) return;

    // compute scale factor for imageView
    CGFloat widthScaleFactor = CGRectGetWidth(self.bounds) / self.image.size.width;
    CGFloat heightScaleFactor = CGRectGetHeight(self.bounds) / self.image.size.height;

    CGFloat imageViewXOrigin = 0;
    CGFloat imageViewYOrigin = 0;
    CGFloat imageViewWidth;
    CGFloat imageViewHeight;


    // if image is narrow and tall, scale to width and align vertically to the top
    if (widthScaleFactor > heightScaleFactor) {
        imageViewWidth = self.image.size.width * widthScaleFactor;
        imageViewHeight = self.image.size.height * widthScaleFactor;
    }

    // else if image is wide and short, scale to height and align horizontally centered
    else {
        imageViewWidth = self.image.size.width * heightScaleFactor;
        imageViewHeight = self.image.size.height * heightScaleFactor;
        imageViewXOrigin = - (imageViewWidth - CGRectGetWidth(self.bounds))/2;
    }

    _imageView.frame = CGRectMake(imageViewXOrigin,
                                  imageViewYOrigin,
                                  imageViewWidth,
                                  imageViewHeight);
}

- (void)setFrame:(CGRect)frame
{
    [super setFrame:frame];
    [self setNeedsLayout];
}

@end
CMash
  • 1,987
  • 22
  • 35
XJones
  • 21,959
  • 10
  • 67
  • 82
  • 3
    Two small things I had to do to make this work: 1 - I implemented `initWithCoder` (I basically copied your `initWithFrame` but replaced the `[super initWithFrame:]` with `[super initWithCoder:]`) and 2 - I added in a line `if (!self.image) return;` to `layoutSubviews` so that it wouldn't crash with invalid geometry if an image isn't assigned when it's called. With these two small changes it works amazingly. – ArtOfWarfare Apr 14 '13 at 06:28
  • *sidenote:* with ARC, `dealloc` method has to be removed. – Raptor Jul 21 '14 at 05:58
36

If using Storyboards this can be achieved with constraints...

Firstly a UIView with the desired final frame / constraints. Add a UIImageView to the UIView. Set the contentMode to Aspect Fill. Make the UIImageView frame be the same ratio as the image (this avoids any Storyboard warnings later). Pin the sides to the UIView using standard constraints. Pin the top OR bottom (depending where you want it aligned) to the UIView using standard constraints. Finally add an aspect ratio constraint to the UIImageView (making sure ratio as the image).

Michael Platt
  • 516
  • 1
  • 5
  • 13
  • @Michael Platt, This sounds so confusing, so this can't work in iOS 7 , since you require equivalency of aspect ratios, that is available in iOS 8+ ? Do you have any example code? – Özgür Oct 23 '15 at 08:06
  • 2
    I have implemented this as well, it works. It may seem confusing because the solution is describing auto layout constraint setup. For other devs, my implementation used images loaded from the web, so I had to remove and re-add the UIImageView aspect ratio constraint programmatically based on the image's width/height. – Michael DePhillips Nov 22 '15 at 04:47
  • 3
    I created UIImageView extension with @MichaelPlatt solution. https://gist.github.com/megimix/d0bbea45bd3e1e4d615fbd5c30b54da7 – megimix Apr 10 '17 at 06:51
21

This is a bit tricky one since there is no option to set further alignment rules if you already selected a content mode (.scaleAspectFit).

But here's a workaround to this:

First need to resize your source image explicitly by calculating dimensions (if it'd be in a UIImageView with contentMode = .scaleAspectFit).

extension UIImage {

    func aspectFitImage(inRect rect: CGRect) -> UIImage? {
        let width = self.size.width
        let height = self.size.height
        let aspectWidth = rect.width / width
        let aspectHeight = rect.height / height
        let scaleFactor = aspectWidth > aspectHeight ? rect.size.height / height : rect.size.width / width

        UIGraphicsBeginImageContextWithOptions(CGSize(width: width * scaleFactor, height: height * scaleFactor), false, 0.0)
        self.draw(in: CGRect(x: 0.0, y: 0.0, width: width * scaleFactor, height: height * scaleFactor))

        defer {
            UIGraphicsEndImageContext()
        }

        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

Then you simply need to call this function on your original image by passing your imageView's frame and assign the result to your UIImageView.image property. Also, make sure you set your imageView's desired contentMode here (or even in the Interface Builder)!

let image = UIImage(named: "MySourceImage")
imageView.image = image?.aspectFitImage(inRect: imageView.frame)
imageView.contentMode = .left
Hitesh Surani
  • 12,733
  • 6
  • 54
  • 65
choofie
  • 2,575
  • 1
  • 25
  • 30
4

Try setting:

imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.clipsToBounds = YES;

This worked for me.

JAL
  • 41,701
  • 23
  • 172
  • 300
Jonathan Molina
  • 703
  • 6
  • 13
3

I used UIImageViewAligned for changing the alignment of image thanks to the developer

UIImageViewAligned

Milap Kundalia
  • 1,566
  • 1
  • 16
  • 24
2

I know this is an old thread, but I thought I'd share what I did to easily change the clipped region from the top to the bottom of the image view in Interface Builder, in case anyone had the same problem I did. I had a UIImageView that filled the View of my ViewController, and was trying to make the top stay the same, independent of the size of the device's screen.

  1. I applied the retina 4 form factor (Editor->Apply Retina 4 Form Factor).

  2. I pinned the height and width.

Now, when the screen changes size, the UIImageView is actually the same size, and the view controller just clips what is off the screen. The frame origin stays at 0,0, so the bottom and right of the image are clipped, not the top.

Hope this helps.

GLee
  • 5,003
  • 5
  • 35
  • 39
2

I came up to the following solution:

  1. Set UIImageView content mode to top: imageView.contentMode = .top
  2. Resize image to fit UIImageView bounds

To load and resize image I use Kingfisher:

let size = imageView.bounds.size
let processor = ResizingImageProcessor(referenceSize: size, mode: .aspectFit)
imageView.kf.setImage(with: URL(string: imageUrl), options: [.processor(processor), .scaleFactor(UIScreen.main.scale)])
Murlakatam
  • 2,729
  • 2
  • 26
  • 20
  • This works but the image gets blurry. I fixed it by using the device screen size and device scaling factor like this: let processor = ResizingImageProcessor(referenceSize: view.frame.size, mode: .aspectFit); imageView.kf.setImage(with: url, options:[.processor(processor), .scaleFactor(UIScreen.main.scale)]) – Alexander Maslew Nov 02 '19 at 23:36
  • Awesome. Thanks dude. I wanted to align left and this worked – Kunal Gupta Apr 20 '20 at 08:26
1

You can do it by first scaling and then resizing. The thing to mention here is that I was conditioned by height. I mean , I had to have the image of 34px high and no matter how width.

So , get the ratio between the actual content height and the height of the view ( 34 px ) and then scale the width too.

Here's how I did it:

CGSize size = [imageView sizeThatFits:imageView.frame.size];

CGSize actualSize;
actualSize.height = imageView.frame.size.height;
actualSize.width = size.width / (1.0 * (size.height / imageView.frame.size.height));

CGRect frame = imageView.frame;
frame.size = actualSize;
[imageView setFrame:frame];

Hope this helps.

George
  • 4,029
  • 1
  • 23
  • 31
1

If you need to achieve aspectFit and get rid empty spaces

Dont forget to remove width constraint of your imageview from storyboard and enjoy

class SelfSizedImageView :UIImageView {
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        guard let imageSize = image?.size else {
            return
        }
        
        let viewBounds = bounds
        let imageFactor = imageSize.width / imageSize.height
        let newWidth = viewBounds.height * imageFactor
        
        let myWidthConstraint = self.constraints.first(where: { $0.firstAttribute == .width })
        myWidthConstraint?.constant = min(newWidth, UIScreen.main.bounds.width / 3)
        
        layoutIfNeeded()
    }}
Giorgio
  • 1,973
  • 4
  • 36
  • 51
  • ideally you'd also do that when ".image" is set, and, very ideally, you'd manipulate `IntrinsicContentSize` correctly – Fattie Jan 24 '23 at 19:08
0

I solved this by subclassing UIImageView and overriding the setImage: method. The subclass would first store it's original values for origin and size so it could use the original set size as a bounding box.

I set the content mode to UIViewContentModeAspectFit. Inside of setImage: I grabbed the image width to height ratio and then resized the image view to fit the same ratio as the image. After the resize, I adjusted my frame properties to set the image view on the same spot it was before, and then I called the super setImage:.

This results in an image view who's frame is adjusted to fit the image exactly, so aspect fit works and the image view frame properties are doing the heavy lifting in putting the image view where it should be to get the same effect.

Here's some code that I used:

First up, and I find it pretty useful in general, is a category on UIView that makes it easy to set frame properties on a view via properties like left, right, top, bottom, width, height, etc.

UIImageView+FrameAdditions

@interface UIView (FrameAdditions)

@property CGFloat left, right, top, bottom, width, height;
@property CGPoint origin;

@end

@implementation UIView (FrameAdditions)

- (CGFloat)left {
    return self.frame.origin.x;
}

- (void)setLeft:(CGFloat)left {
    self.frame = CGRectMake(left, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
}

- (CGFloat)right {
    return self.frame.origin.x + self.frame.size.width;
}

- (void)setRight:(CGFloat)right {
    self.frame = CGRectMake(right - self.frame.size.width, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
}

- (CGFloat)top {
    return self.frame.origin.y;
}

- (void)setTop:(CGFloat)top {
    self.frame = CGRectMake(self.frame.origin.x, top, self.frame.size.width, self.frame.size.height);
}

- (CGFloat)bottom {
    return self.frame.origin.y + self.frame.size.height;
}

- (void)setBottom:(CGFloat)bottom {
    self.frame = CGRectMake(self.frame.origin.x, bottom - self.frame.size.height, self.frame.size.width, self.frame.size.height);
}

- (CGFloat)width {
    return self.frame.size.width;
}

- (void)setWidth:(CGFloat)width {
    self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, width, self.frame.size.height);
}

- (CGFloat)height {
    return self.frame.size.height;
}

- (void)setHeight:(CGFloat)height {
    self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, height);
}

- (CGPoint)origin {
    return self.frame.origin;
}

- (void)setOrigin:(CGPoint)origin {
    self.frame = CGRectMake(origin.x, origin.y, self.frame.size.width, self.frame.size.height);
}

@end

This is the subclass of UIImageView. It is not fully tested, but should get the idea across. This could be expanded to set your own new modes for alignment.

BottomCenteredImageView

@interface BottomCenteredImageView : UIImageView

@end


@interface BottomCenteredImageView() {
    CGFloat originalLeft;
    CGFloat originalBottom;
    CGFloat originalHeight;
    CGFloat originalWidth;
}

@end

@implementation BottomCenteredImageView

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if(self) {
        [self initialize];
    }
    return self;
}

- (void)awakeFromNib {
    [self initialize];
}

- (void)initialize {
    originalLeft = self.frame.origin.x;
    originalHeight = CGRectGetHeight(self.frame);
    originalWidth = CGRectGetWidth(self.frame);
    originalBottom = self.frame.origin.y + originalHeight;
}

- (void)setImage:(UIImage *)image {
    if(image) {
        self.width = originalWidth;
        self.height = originalHeight;
        self.left = originalLeft;
        self.bottom = originalBottom;

        float myWidthToHeightRatio = originalWidth/originalHeight;
        float imageWidthToHeightRatio = image.size.width/image.size.height;
        if(myWidthToHeightRatio >= imageWidthToHeightRatio) {
            // Calculate my new width
            CGFloat newWidth = self.height * imageWidthToHeightRatio;
            self.width = newWidth;
            self.left = originalLeft + (originalWidth - self.width)/2;
            self.bottom = originalBottom;
        } else {
            // Calculate my new height
            CGFloat newHeight = self.width / imageWidthToHeightRatio;
            self.height = newHeight;
            self.bottom = originalBottom;
        }
        self.contentMode = UIViewContentModeScaleAspectFit;
        [super setImage:image];
    } else {
        [super setImage:image];
    }
}

@end
slemke
  • 31
  • 1
  • Instead of using a UIImageView in your VC, use the BottomCenteredImageView. – slemke Aug 24 '12 at 17:56
  • This didn't work for me... it changed the aspect ratio of my view to match the aspect ratio of my image which wasn't what I wanted... The answer XJones provided worked beautifully with the few changes I noted in the comments below his answer. – ArtOfWarfare Apr 14 '13 at 06:31
0

Credit to @michael-platt

Key Points

  • imageView.contentMode = .scaleAspectFit
  • let width = Layout.height * image.size.width / image.size.height
  • imageView.widthAnchor.constraint(equalToConstant: width).isActive = true

Code:

func setupImageView(for image: UIImage) {
    let imageView = UIImageView()
    imageView.backgroundColor = .orange

    //Content mode
    imageView.contentMode = .scaleAspectFill
    
    view.addSubview(imageView)

    //Constraints
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true

    //Feel free to set the height to any value
    imageView.heightAnchor.constraint(equalToConstant: 150).isActive = true

    //ImageView width calculation
    let width = Layout.height * image.size.width / image.size.height
    imageView.widthAnchor.constraint(equalToConstant: width).isActive = true
}
user1046037
  • 16,755
  • 12
  • 92
  • 138
-6

To align a scale-to-fit image use auto layout. Example right aligned image after scale-to-fit in UIImageView:

  1. Create UIImageView that holds the image
  2. Add auto constraints for right, top, bottom, width
  3. Set the image:
myUIImageView.contentMode = .ScaleAspectFit
myUIImageView.translatesAutoresizingMaskIntoConstraints = false
myUIImageView.image = UIImage(named:"pizza.png")

Your aspect fit scaled image is now right aligned in the UIImageView

+----------------------------------+
|                           [IMAGE]|
+----------------------------------+

Change the constraints to align differently within the imageview.

pizzamonster
  • 1,141
  • 10
  • 9