99

firstButton is a UIButton of type Custom. I'm programmatically putting three of them across each cell of a table, thusly:

[firstButton setImage:markImage forState:UIControlStateNormal];
[firstButton setContentMode:UIViewContentModeScaleAspectFit];
[cell.contentView addSubview:firstButton];

Elsewhere, I'm telling it to clipToBounds. What I get is a crop of the center square of the image, rather than an aspect-scaled rendering of it. I've tried this lots of ways, including setting the mode property on firstButton.imageView, which also doesn't seem to work.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Dan Ray
  • 21,623
  • 6
  • 63
  • 87
  • 1
    See solution by Chris Nolet below: button.contentVerticalAlignment = UIControlContentVerticalAlignmentTop; – Matt Sep 09 '13 at 21:54
  • See http://stackoverflow.com/a/28205566/481207 which shows that setting both contentVerticalAlignment and contentHorizontalAlignment is required for UIViewContentModeScaleAspectFit. – Matt Jan 29 '15 at 01:36

17 Answers17

188

I had the same problem. I see this question is a little old, but I want to provide a clear and correct answer to save other folks (like me) some time when it pops up in their search results.

It took me a bit of searching and experimenting, but I found the solution. Simply set the ContentMode of the "hidden" ImageView that is inside the UIButton.

[[firstButton imageView] setContentMode: UIViewContentModeScaleAspectFit];
[firstButton setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];

Perhaps that's what Dan Ray was alluding to in his accepted answer, but I suspect not.

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
Dave
  • 7,552
  • 4
  • 22
  • 26
  • 1
    No, it wasn't. The solution I settled on was to use a UIImageView to hold the image, then a clear, transparant UIButton of type "custom" on top of it. – Dan Ray Jul 19 '10 at 16:03
  • Dave, on iOS 3.2 at least, your solution doesn't seem to work for me. Too bad, because I was hoping to get the highlighting of the image on click "for free". I even made sure that button.imageView wasn't nil where I was setting contentMode, and set the image after setting contentMode, just like you're doing. – Jacques Aug 23 '10 at 05:24
  • 2
    Just to clarify, "doesn't work" means "the image is shown at its full resolution, not scaled down as I was hoping". – Jacques Aug 23 '10 at 05:29
  • Jacques, the above works for me in iOS 4.x. Unfortunately, I can reproduce the "won't resize" issue when building with a v3.2 target. I tried changing the UIButton view's frame size, but that didn't seem to help either. ugh. – Dave Sep 22 '10 at 20:20
  • 4
    See solution by Chris Nolet below: button.contentVerticalAlignment = UIControlContentVerticalAlignmentTop; – Matt Sep 09 '13 at 21:53
  • try both UIViewContentModeScaleAspectFill and UIViewContentModeScaleAspectFit. Fill will expand your image, while Fit to me is a fixed small size. UIViewContentModeScaleAspectFill solve my problem. I want a larger image for the button – Wingzero Feb 27 '15 at 09:53
  • Content fill modes ALSO need to be set, and the button type needs to be custom. See this answer: http://stackoverflow.com/a/28205566/3324388 – Aggressor Mar 02 '15 at 22:03
  • I love this answer. For years, I keep forgetting about it and coming back to it. – Şafak Gezer Apr 14 '15 at 11:08
  • Won't work in Swift because the hidden `.imageView` is not accessible. – Chris Harrison Jun 22 '15 at 09:39
  • Make sure your `UIButton` is of type `.Custom`! – Quantaliinuxite Apr 05 '16 at 13:00
80

If you're dealing with the UIButton's image (as opposed to it's backgroundImage), setting the contentMode on the UIButton itself or on its imageView has no effect (despite what other answers say).

Alternatively do this instead:

self.button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
self.button.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;

Or size your image accordingly.

OR just use a UIImageView (which properly respects contentMode) with a UITapGestureRecognizer attached to it, or a transparent UIButton on top of it.

arlomedia
  • 8,534
  • 5
  • 60
  • 108
Alfie Hanssen
  • 16,964
  • 12
  • 68
  • 74
  • 4
    This works for my case in iOS 7 whereas all the other ones here does not. – Byte Apr 07 '14 at 04:49
  • 5
    Setting contentHorizontalAlignment and contentVerticalAlignment is the correct solution. See http://stackoverflow.com/a/28205566/481207 for an explanation. – Matt Jan 29 '15 at 01:39
  • 1
    This the answer I was looking for. – ninjaneer Dec 04 '15 at 03:10
  • 4
    This answer is incorrect. You *do* need to set `contentMode` on the `imageView` if you want to do any kind of aspect fill or fit, as other answers have clarified. Refer: https://stackoverflow.com/a/7944316/746890. – Chris Nolet Feb 02 '18 at 18:19
62

Rather than setting the contentMode on the button itself, you'll want to set contentHorizontalAlignment and contentVerticalAlignment properties and (crucially) the contentMode for the button's imageView for any kind of aspect fill or fit:

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

You can also do other things like aligning the button's image to the top. If you don't need an aspect fill or fit, you just can set the alignment by itself:

button.contentVerticalAlignment = .top
Chris Nolet
  • 8,714
  • 7
  • 67
  • 92
  • 3
    Thanks Chris - that solved my issue, or rather, it did in combination with contentEdgeInsets, in order to back my image off the top edge just a bit. – Rob Reuss Feb 04 '12 at 01:55
7

After a couple of hours of confusion, here's how I got it to work under iOS 3.2. As dusker mentioned, using setBackgroundImage instead of setImage did the job for me.

CGRect myButtonFrame = CGRectMake(0, 0, 250, 250);
UIImage *myButtonImage = [UIImage imageNamed:@"buttonImage"];

UIButton *myButton = [UIButton buttonWithType:UIButtonTypeCustom];

[myButton setBackgroundImage:myButtonImage forState:UIControlStateNormal];
[myButton setFrame: myButtonFrame];
[myButton setContentMode: UIViewContentModeScaleAspectFit];
Pierre
  • 522
  • 1
  • 8
  • 19
6

The answer is to use a UIImageView with all the lovely Content Mode settings you want, and then layer a custom button on top of it. Dumb that you can't do that all in one shot, but it appears that you can't.

Dan Ray
  • 21,623
  • 6
  • 63
  • 87
6

These two things (which are quite hard to find initially) will stretch your UIButton image to fit the button size:

enter image description here enter image description here

one should always try to set such in the Storyboard rather than code.

Corbin Miller
  • 395
  • 4
  • 13
4

Found a fix for this. Set the adjustsImageWhenHighlighted property of UIButton to NO.

  UIButton *b = [[UIButton alloc] initWithFrame:rect];
        [b setImage:image forState:UIControlStateNormal];
        [b.imageView setContentMode:UIViewContentModeScaleAspectFill];
        [b setAdjustsImageWhenHighlighted:NO];

Hope this helps. Feel free to comment below, I will follow up on any questions that you have.

iOSDevSF
  • 1,169
  • 1
  • 13
  • 24
4

My answer is similar to Kuba's. I needed my image to be programatically set.

UIImage *image = [[UIImage alloc] initWithContentsOfFile:...];
[button setBackgroundImage:image forState:UIControlStateNormal];
button.imageView.contentMode = UIViewContentModeScaleAspectFill; //this is needed for some reason, won't work without it.
for(UIView *view in button.subviews) {
    view.contentMode = UIViewContentModeScaleAspectFill;
}
ninjaneer
  • 6,951
  • 8
  • 60
  • 104
3

Only solution which worked for me:

[button setImage:image forState:UIControlStateNormal];
button.imageView.contentMode = UIViewContentModeScaleAspectFill;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
button.contentVerticalAlignment = UIControlContentVerticalAlignmentFill;
Tariq
  • 9,861
  • 12
  • 62
  • 103
3

Swift 3

self.firstButton.imageView?.contentMode = .scaleAspectFill
Vaibhav Saran
  • 12,848
  • 3
  • 65
  • 75
2

For anyone experiencing this on iOS 15 and Xcode 13, see Matt's answer in this other question.

The behavior of Xcode changed and now defaults UIButtons from the library to the plain style, which prevents the child image from scaling as expected.

1

I believe we have a simple interface builder issue here - apparently the IB ignores any content-mode changes AFTER you have set the image-property.

the solution is as simple: set the content mode, remove previously set image-names (make sure you remove it in all states, default, highlighted etc.), then re-enter the desired image-names in all desired states - et voilà.

JohnPayne
  • 185
  • 1
  • 10
  • 1
    the same goes to programmatically set content-mode - do not set the image BEFORE setting the content-mode – JohnPayne Jan 19 '13 at 20:21
1

Instead of setImage try setBackgroundImage

dusker
  • 580
  • 3
  • 11
  • 3
    Hmp. A, that's dumb, it should respect the CONTENT MODE setting for its CONTENT, right? And B, it STILL doesn't seem to respect the mode setting--it's now locked to "stretch" (which is, in this case, more like "squeeze"), and isn't scaling the content with respect to its aspect ratio. – Dan Ray Jun 01 '10 at 15:10
0

If anyone looking for answer that work in iOS 6 and iOS 7 and storyboard:

You can set image in your storyboard:

enter image description here

And then:

for(UIView* testId in self.subviews) {
    if([testId isKindOfClass:[UIImageView class]])
        [testId setContentMode:UIViewContentModeScaleAspectFill];
}
Jakub
  • 13,712
  • 17
  • 82
  • 139
0

I also advice to have a look at the adjustsImageWhenHighlighted UIButton property to avoid weird deformations of the image, when the button is pressed.

MonsieurDart
  • 6,005
  • 2
  • 43
  • 45
0

If the UIButton does not seem to listen to the layout constraint settings, do check whether the images are larger than the button size. Always use the @2x and @3x images for retina resolutions.

Totoro
  • 3,398
  • 1
  • 24
  • 39
0

In trying to figure this out, my method got a bit hackier as time went on, and I wound up subclassing UIButton and overriding setHighlighted:

For me it works to just knock down the image alpha to .5, because they're on a black background.

However, it only works if I comment out [super setHighlighted:] (where it appears the image-stretchy code is going on), which just doesn't feel like the right way to solve this at all...everything seems to be working fine, though. We'll see how it holds up as I keep working on it.

- (void)setHighlighted:(BOOL)highlight {
    if (highlight) {
        [self.imageView setAlpha:.5];
    }  else {
        [self.imageView setAlpha:1];        
    }

//    [super setHighlighted:highlight];
}
jankins
  • 670
  • 5
  • 16
  • 1
    On the off-chance anyone finds this relevant, my above method seemed to work OK until I put the buttons on a UIScrollView, where sometimes the button's highlight got "stuck," which is what I suspected might eventually happen. – jankins Mar 03 '12 at 05:11