scale Image in an UIButton to AspectFit?
Asked Answered
S

17

113

I want to add an image to a UIButton, and also want to scale my image to fit with the UIButton (make image smaller). Please show me how to do it.

This is what I have tried, but it does't work:

  • Adding image to button and using setContentMode:
[self.itemImageButton setImage:stretchImage forState:UIControlStateNormal];
[self.itemImageButton setContentMode:UIViewContentModeScaleAspectFit];
  • Making a "stretch image":
UIImage *stretchImage = [updatedItem.thumbnail stretchableImageWithLeftCapWidth:0 topCapHeight:0];
Sharpen answered 8/1, 2010 at 3:34 Comment(2)
I think this has been changed to be the default behavior in firmware 4.0 and up.Matlock
See https://mcmap.net/q/130503/-ios-gt-gt-uibutton-imageview-property-gt-gt-how-to-set-content-mode for the solution.Coefficient
G
27

If you really want to scale an image, do it, but you should resize it before using it. Resizing it at run time will just lose CPU cycles.

This is the category I'm using to scale an image :

UIImage+Extra.h

@interface UIImage (Extras)
- (UIImage *)imageByScalingProportionallyToSize:(CGSize)targetSize;
@end;

UIImage+Extra.m

@implementation UIImage (Extras)

- (UIImage *)imageByScalingProportionallyToSize:(CGSize)targetSize {

UIImage *sourceImage = self;
UIImage *newImage = nil;

CGSize imageSize = sourceImage.size;
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;

CGFloat targetWidth = targetSize.width;
CGFloat targetHeight = targetSize.height;

CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetWidth;
CGFloat scaledHeight = targetHeight;

CGPoint thumbnailPoint = CGPointMake(0.0,0.0);

if (!CGSizeEqualToSize(imageSize, targetSize)) {

        CGFloat widthFactor = targetWidth / width;
        CGFloat heightFactor = targetHeight / height;

        if (widthFactor < heightFactor) 
                scaleFactor = widthFactor;
        else
                scaleFactor = heightFactor;

        scaledWidth  = width * scaleFactor;
        scaledHeight = height * scaleFactor;

        // center the image

        if (widthFactor < heightFactor) {
                thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5; 
        } else if (widthFactor > heightFactor) {
                thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
        }
}


// this is actually the interesting part:

UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0);

CGRect thumbnailRect = CGRectZero;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width  = scaledWidth;
thumbnailRect.size.height = scaledHeight;

[sourceImage drawInRect:thumbnailRect];

newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

if(newImage == nil) NSLog(@"could not scale image");


return newImage ;
}

@end

You can use it to the size you want. Like :

[self.itemImageButton setImage:[stretchImage imageByScalingProportionallyToSize:CGSizeMake(20,20)]];
Gloriagloriana answered 8/1, 2010 at 4:0 Comment(5)
Thanks for your answer, Gcamp. I did have an method for resizing by ratio. But I still try to find a way to tell UIButton do it for me. My image is load from Internet so I can't resize it at compile time. BTW, thank you very much for your response.Sharpen
Because the image is placed into a CALayer, rendered, and cached by Quartz, performance should not be any worse if you place it in there at full size. Either way, you're resizing ~1 time. gcamp's answer is much more important if you're constantly resizing the button or doing other things that would trigger Quartz to redraw the image layer.Fricandeau
it seems like image quality is reduced. i am testing on iphone 6 plus. Is it true? I also have 3x image.Cath
Yes it will, this code is pretty old. It was UIGraphicsBeginImageContext instead of UIGraphicsBeginImageContextWithOptions. Just fixed it.Gloriagloriana
yeah but in many cases it doesn't matter if you loose a few cycles, so no point in filling up the ramOdonnell
F
210

I had the same problem. Just set the ContentMode of the ImageView that is inside the UIButton.

[[self.itemImageButton imageView] setContentMode: UIViewContentModeScaleAspectFit];
[self.itemImageButton setImage:[UIImage imageNamed:stretchImage] forState:UIControlStateNormal];
Footage answered 19/7, 2010 at 13:22 Comment(7)
Make sure you're setting the imageView. I also failed to notice, for a bit, that I was still using the backgroundImageView and it wasn't working for that.Bultman
It seems this method has some problems when the button state is 'highlighted'. The image will back to the fill mode. Any idea?Nominative
It is not working to me. However, I make it work by using [self.itemImageButton setbackgroundImage:[UIImage imageNamed:stretchImage] forState:UIControlStateNormal];Somehow
This worked for me. I set the content mode as above. However, you also need to set the same image of the highlighted state in order to avoid the image going back to "fill mode". For example, [imageButton setImage:image forState:UIControlStateHighlighted];Ovarian
@Footage from Apple doc about imageView The button’s image view. (read-only) that's why it does not workTobacco
You can do this in Interface Builder with key path imageView.contentMode, type Number, and value 1Ulrikaumeko
apple help for how to do "bendytree" comment process in Interface Builder - developer.apple.com/library/mac/recipes/…Sherbrooke
D
126

None of the answers here really worked for me, I solved the problem with the following code:

button.contentMode = UIViewContentModeScaleToFill;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
button.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;

You can do this in the Interface Builder as well.

enter image description here

Dinadinah answered 15/7, 2015 at 11:51 Comment(3)
This doesn't give you aspect fit behavior as asked by the OP.Tifanie
@OrtwinGentz you can select the mode and set Aspect fit instead of scale to fill.Subordinate
@Subordinate in Xcode 14.1 the Aspect fit option acts just like Scale to fill, so this doesn't work.Lothar
L
67

The easiest way to programmatically set a UIButton imageView in aspect fit mode :

Swift

button.contentHorizontalAlignment = .fill
button.contentVerticalAlignment = .fill
button.imageView?.contentMode = .scaleAspectFit

Objective-C

button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
button.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
button.imageView.contentMode = UIViewContentModeScaleAspectFit;

Note: You can change .scaleAspectFit (UIViewContentModeScaleAspectFit) to .scaleAspectFill (UIViewContentModeScaleAspectFill) to set an aspect fill mode

Legofmutton answered 21/8, 2015 at 16:1 Comment(1)
This is the only solution I've found that works. The key is the content*Alignment calls. It also works inside UIStackViews. Thank you!Mandiemandingo
G
27

If you really want to scale an image, do it, but you should resize it before using it. Resizing it at run time will just lose CPU cycles.

This is the category I'm using to scale an image :

UIImage+Extra.h

@interface UIImage (Extras)
- (UIImage *)imageByScalingProportionallyToSize:(CGSize)targetSize;
@end;

UIImage+Extra.m

@implementation UIImage (Extras)

- (UIImage *)imageByScalingProportionallyToSize:(CGSize)targetSize {

UIImage *sourceImage = self;
UIImage *newImage = nil;

CGSize imageSize = sourceImage.size;
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;

CGFloat targetWidth = targetSize.width;
CGFloat targetHeight = targetSize.height;

CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetWidth;
CGFloat scaledHeight = targetHeight;

CGPoint thumbnailPoint = CGPointMake(0.0,0.0);

if (!CGSizeEqualToSize(imageSize, targetSize)) {

        CGFloat widthFactor = targetWidth / width;
        CGFloat heightFactor = targetHeight / height;

        if (widthFactor < heightFactor) 
                scaleFactor = widthFactor;
        else
                scaleFactor = heightFactor;

        scaledWidth  = width * scaleFactor;
        scaledHeight = height * scaleFactor;

        // center the image

        if (widthFactor < heightFactor) {
                thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5; 
        } else if (widthFactor > heightFactor) {
                thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
        }
}


// this is actually the interesting part:

UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0);

CGRect thumbnailRect = CGRectZero;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width  = scaledWidth;
thumbnailRect.size.height = scaledHeight;

[sourceImage drawInRect:thumbnailRect];

newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

if(newImage == nil) NSLog(@"could not scale image");


return newImage ;
}

@end

You can use it to the size you want. Like :

[self.itemImageButton setImage:[stretchImage imageByScalingProportionallyToSize:CGSizeMake(20,20)]];
Gloriagloriana answered 8/1, 2010 at 4:0 Comment(5)
Thanks for your answer, Gcamp. I did have an method for resizing by ratio. But I still try to find a way to tell UIButton do it for me. My image is load from Internet so I can't resize it at compile time. BTW, thank you very much for your response.Sharpen
Because the image is placed into a CALayer, rendered, and cached by Quartz, performance should not be any worse if you place it in there at full size. Either way, you're resizing ~1 time. gcamp's answer is much more important if you're constantly resizing the button or doing other things that would trigger Quartz to redraw the image layer.Fricandeau
it seems like image quality is reduced. i am testing on iphone 6 plus. Is it true? I also have 3x image.Cath
Yes it will, this code is pretty old. It was UIGraphicsBeginImageContext instead of UIGraphicsBeginImageContextWithOptions. Just fixed it.Gloriagloriana
yeah but in many cases it doesn't matter if you loose a few cycles, so no point in filling up the ramOdonnell
H
22

I had problems with the image not resizing proportionately so the way I fixed it was using edge insets.

fooButton.contentEdgeInsets = UIEdgeInsetsMake(10, 15, 10, 15);
Hippel answered 7/6, 2012 at 7:44 Comment(1)
working pretty well, thanksGlimmering
S
19

This can now be done through IB's UIButton properties. The key is to set your image as a the background, otherwise it won't work.

enter image description here

Stephanotis answered 9/9, 2014 at 21:37 Comment(0)
T
14

Expanding on Dave's answer, you can set the contentMode of the button's imageView all in IB, without any code, using Runtime Attributes:

enter image description here

  • 1 means UIViewContentModeScaleAspectFit,
  • 2 would mean UIViewContentModeScaleAspectFill.
Tifanie answered 3/3, 2016 at 14:56 Comment(0)
A
13

1 - clear Button default text (important)

2 - set alignment like image

3 - set content mode like image

enter image description here

Alienable answered 17/2, 2019 at 8:23 Comment(1)
Nice answer bro..helped me a lot. Thanks!Brenan
T
8

If you simply want to reduce your button image:

yourButton.contentMode = UIViewContentModeScaleAspectFit;
yourButton.imageEdgeInsets = UIEdgeInsetsMake(10, 10, 10, 10);
Tiresome answered 18/4, 2014 at 8:54 Comment(2)
That should be yourButton.imageView.contentMode = UIViewContentModeScaleAspectFit;Exactly
Didn't try with .imageView. It is working without pretty well.Tiresome
P
6

I have a method that does it for me. The method takes UIButton and makes the image aspect fit.

-(void)makeImageAspectFitForButton:(UIButton*)button{
    button.imageView.contentMode=UIViewContentModeScaleAspectFit;
    button.contentHorizontalAlignment=UIControlContentHorizontalAlignmentFill;
    button.contentVerticalAlignment=UIControlContentVerticalAlignmentFill;
}
Painterly answered 20/9, 2015 at 8:50 Comment(0)
A
6

in xCode 13.4.1, configure Style to Default and State Config to Default

enter image description here

Amrita answered 13/9, 2022 at 17:5 Comment(1)
Do this, before setting. Control Alignment - Fill. Content mode to - Aspect FitAbbreviation
A
5

Swift 5.0

 myButton2.contentMode = .scaleAspectFit
 myButton2.contentHorizontalAlignment = .fill
 myButton2.contentVerticalAlignment = .fill
Amyamyas answered 8/6, 2019 at 19:52 Comment(0)
R
4

The cleanest solution is to use Auto Layout. I lowered Content Compression Resistance Priority of my UIButton and set the image (not Background Image) via Interface Builder. After that I added a couple of constraints that define size of my button (quite complex in my case) and it worked like a charm.

Rector answered 16/12, 2014 at 17:6 Comment(2)
This worked for me -- but only if I selected to use the "Background Image", not the actual image. Whatever works! This was driving me NUTS.Ivett
Under Xcode 6.2 this worked for me but like @AndrewHeinlein said, used Background ImageSnick
H
3

make sure that you have set the image to Image property, but not to the Background

Haiku answered 13/5, 2011 at 10:0 Comment(0)
A
2

Background image can actually be set to scale aspect fill pretty easily. Just need to do something like this in a subclass of UIButton:

- (CGRect)backgroundRectForBounds:(CGRect)bounds
{
    // you'll need the original size of the image, you 
    // can save it from setBackgroundImage:forControlState
    return CGRectFitToFillRect(__original_image_frame_size__, bounds);
}

// Utility function, can be saved elsewhere
CGRect CGRectFitToFillRect( CGRect inRect, CGRect maxRect )
{
    CGFloat origRes = inRect.size.width / inRect.size.height;
    CGFloat newRes = maxRect.size.width / maxRect.size.height;

    CGRect retRect = maxRect;

    if (newRes < origRes)
    {
        retRect.size.width = inRect.size.width * maxRect.size.height / inRect.size.height;
        retRect.origin.x = roundf((maxRect.size.width - retRect.size.width) / 2);
    }
    else
    {
        retRect.size.height = inRect.size.height * maxRect.size.width / inRect.size.width;
        retRect.origin.y = roundf((maxRect.size.height - retRect.size.height) / 2);
    }

    return retRect;
}
Abortifacient answered 13/9, 2013 at 21:15 Comment(4)
Isn't this possible without a subclass?Extremely
This answer was specifically for backgroundImage. For the main image you can monkey with the the imageEdgeInsets.Abortifacient
I know, and I wanted if it's possible to achieve this without a subclass.Extremely
As far as I know, you cannot adjust the backgroundImage without subclassing.Abortifacient
B
1

For Xamarin.iOS (C#):

    myButton.VerticalAlignment = UIControlContentVerticalAlignment.Fill;
    myButton.HorizontalAlignment = UIControlContentHorizontalAlignment.Fill;
    myButton.ImageView.ContentMode = UIViewContentMode.ScaleAspectFit;
Beeline answered 25/10, 2016 at 7:38 Comment(0)
M
-1

You just need to set content mode of UIButton imageview for three events. -

[cell.button setImage:[UIImage imageWithData:data] forState:UIControlStateNormal];

[cell.button setImage:[UIImage imageWithData:data] forState:UIControlStateHighlighted];

[cell.imgIcon setImage:[UIImage imageWithData:data] forState:UIControlStateSelected];

We have code for three event bcoz while highlighting or selecting if button size is SQUARE and image size is rectangle then it will show square image at the time of highlighting or selecting.

I am sure it will work for you.

Madewell answered 7/9, 2011 at 6:53 Comment(1)
You're not setting a content mode, you're setting the image. And they're not events, they're states. You're making a big mess. This is totally unrelated to the question.Proffer

© 2022 - 2024 — McMap. All rights reserved.