12

I have a UIButton in my iPhone app. I set its size to 100x100.

I have an image that is 400x200 that I wish to display in the button.

The button STILL needs to stay at 100x100... and I want the image to downsize to fit... but keep the correct aspect ratio.

I thought that's what "Aspect Fit" was used for.

Do I include my image with setImage, or setBackgroundImage?

Do I set AspectFit for the button? or the image? or the background image?

(I need the smaller images to INCREASE in size. While larger images should DESCREASE in size. Always keeping the button at 100x100.)

I've tried countless combinations of all of the above... and I just can't seem to get everything to work at the same time:

  • Don't change the button's size of 100x100.
  • Don't destroy the image's aspect ratio.
  • Always increase small images to fit the button.
  • Always decrease large images to fit the button.
  • Never clip any of the image edges.
  • Never require the "put UIButtons over all your UIimages" hack.
  • Don't require everyone to upgrade to v4.2.1 just to use new framework methods.

I see so many apps, with so many fully-working image-buttons... that I can't believe I can't figure out this very simple, very common thing.

Ugh.

Alice
  • 141
  • 1
  • 5

8 Answers8

12

UIButton is broken. That's the short answer. The UIImageViews in its image and backgroundImage properties don't respect UIViewContentMode settings. They're read-only properties, and while the UIImage contents of those UIImageViews can be set through setImage: and setBackgroundImage: methods, the content mode can't be.

The solution is either to provide properly-sized images in your bundle to begin with, or to put a UIImageView down, configure it the way you want it, and then put a clear "custom" UIButton over top of it. That's the hack all those fancy professional apps you've seen have used, I promise. We're all having to do it.

Dan Ray
  • 21,623
  • 6
  • 63
  • 87
  • The images aren't in my bundle... they are people's pictures that will be loaded later. So all the existing button features don't allow this simple thing???? A button's image is useless. A button's background image is useless. All the various aspect-fit choice are useless. (Why did Apple put them in... if they can't even do something as simple is "make this image fit this button"?) – Alice Dec 21 '10 at 21:39
  • Does anyone know if this "annoyance" has been addressed in a later version of xcode? – Jimmy Jun 18 '12 at 11:11
  • 2
    As of iOS 6.1.3, they still don't respect this property. – Enrico Susatyo Apr 19 '13 at 00:50
6
UIImage *img = [UIImage imageNamed:@"yourImageName"];
button.imageView.contentMode = UIViewContentModeScaleAspectFit;
[button setImage:img forState:UIControlStateNormal];
iOS Dev
  • 4,143
  • 5
  • 30
  • 58
ChikabuZ
  • 10,031
  • 5
  • 63
  • 86
  • Thanks for this update. For all other people who read that far: YES! Actually Apple seemed to have solved the aspec-ratio issue with UIButton. And the above "work-arounds" can be skipped by the few lines of the above answer. – DerWOK Nov 01 '13 at 22:38
4

To do this correctly, I would actually programmatically resize and manipulate the image to get the desired aspect ratio. This avoids the need for any view hierarchy hacks, and also reduces any performance hit to a single operation, instead of every redraw.

This (untested) code should help illustrate what I mean:

CGSize imageSize = image.size;
CGFloat currentAspect = imageSize.width / imageSize.height;

// for purposes of illustration
CGFloat targetWidth = 100;
CGFloat targetHeight = 100;
CGFloat targetAspect = targetWidth / targetHeight;

CGFloat newWidth, newHeight;

if (currentAspect > targetAspect) {
    // width will end up at 100, height needs to be smaller
    newWidth = targetWidth;
    newHeight = targetWidth / currentAspect;
} else {
    // height will end up at 100, width needs to be smaller
    newHeight = targetHeight;
    newWidth = targetHeight * currentAspect;
}

size_t bytesPerPixel = 4;

// although the image will be resized to { newWidth, newHeight }, it needs
// to be padded with empty space to provide the aspect fit behavior
//
// use calloc() to clear the data as it's allocated
void *imageData = calloc(targetWidth * targetHeight, bytesPerPixel);

if (!imageData) {
    // error out
    return;
}

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (!colorSpace) {
    // error out
    return;
}

CGContextRef context = CGBitmapContextCreate(
    imageData,
    targetWidth,
    targetHeight,
    8, // bits per component
    targetWidth * bytesPerPixel, // bytes per row
    colorSpace,
    kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst
);

CGColorSpaceRelease(colorSpace);

// now we have a context to draw the original image into
// in doing so, we want to center it, so prepare the geometry
CGRect drawRect = CGRectMake(
    floor((targetWidth - newWidth) / 2),
    floor((targetHeight - newHeight) / 2),
    round(newWidth),
    round(newHeight)
);

CGContextDrawImage(context, drawRect, image.CGImage);

// now that the bitmap context contains the aspect fit image with transparency
// letterboxing, we want to pull out a new image from it
CGImageRef newImage = CGBitmapContextCreateImage(context);

// destroy the temporary context
CGContextRelease(context);
free(imageData);

// and, finally, create a new UIImage
UIImage *newUIImage = [UIImage imageWithCGImage:newImage];
CGImageRelease(newImage);

Let me know if any part of that is unclear.

Justin Spahr-Summers
  • 16,893
  • 2
  • 61
  • 79
  • Wow. I'll try that also. But this simple thing is really *THAT* huge mess of code? Make this image fit inside this button. It really can never be done with "set button size" and "set aspect-fit" and "set this image" type stuff? – Alice Dec 21 '10 at 21:49
  • @Alice Well, this snippet of code is pretty similar to what it would do under the hood if you set one of those content modes. In my projects, I typically use the code above hidden behind some easily-accessible global function or method (like a category on `UIImage`), and then any manually scaling I need to do is a breeze. – Justin Spahr-Summers Dec 21 '10 at 21:51
  • @justin Spahr-Summers, Thanx it really works for me. but my query is that is it working with any size of images? I'm asking on the perception of HD images and thumbnails. – Jekil Patel Jan 09 '14 at 08:57
3

I think what Dan is trying to say (but without ever saying it) is to do this:

  • Use a "temp image" to do the resizing for you.
  • The temp-image needs to be set to ASPECT FIT and HIDDEN.
  • Make sure your button is set to your desired size, and NOT set to ASPECT FIT.
// Make a frame the same size as your button
CGRect aFrame = CGRectMake(0, 0, myButton.frame.size.width, myButton.frame.size.height);

// Set your temp-image to the size of your button
imgTemp.frame = aFrame;

// Put your image into the temp-image
imgTemp.image = anImage;

// Copy that resized temp-image to your button
[myButton setBackgroundImage:tempImage forState:UIControlStateNormal]; 
Jasarien
  • 58,279
  • 31
  • 157
  • 188
Patricia
  • 289
  • 3
  • 6
  • I just tried that. The temp-image comes out *PERFECT*. But there's no way to copy that image (and its new size) into my button. If I use setBackgroundImage, the image is stretched and the aspect ratio is destroyed. – Alice Dec 21 '10 at 21:42
  • > Make sure your button is NOT set to ASPECT FIT..................................... How???? You can't set it to "nothing". I thought the "non-stretch" choice was "CENTER" but that stretches it anyway. Ugh. – Alice Dec 21 '10 at 21:43
2

Since none of my attempts have worked....

Maybe I should be asking this instead. When using a UIButton:

When DO I use setImage instead of setBackgroundImage? (Why are there both?)

When DO I use "Aspect Fit" instead of "Center"? (Why do both seem to stretch my images when I expect them to "keep aspect ratio" and "don't resize anything", respective.)

And the big question: Why is such a common thing... such a huge mess?

It would all be solved if I could find a work-around method like: Just use UIImage instead and detect TAPS. (But that seems to be even a LARGER nightmare of code.)

Apple, if you've tried to make my job easier... you have instead made it 400 times more confusing.

Alice
  • 141
  • 1
  • 5
1

Place a imageview over the button, set your image for the imageview and not for button.

All the best.

Warrior
  • 39,156
  • 44
  • 139
  • 214
  • Actually, I'd place the `UIImageView` **under** the button, and make the button "Custom" so it's invisible. – sudo rm -rf Dec 21 '10 at 17:42
  • 2
    I wanted to do this "correctly" and avoid the hack of 25 buttons... over 25 images... having to create 50 objects and all the memory, maintenance, outlets, actions, etc. What good are all the powerful, built-in button methods... if everyone's solution is "just don't use them"??? – Alice Dec 21 '10 at 20:19
1

I would resize the image to 100x100 maintaining the aspect ratio of the content contained in the image. Then set the backgroundImage property of the UIButton to the image.

Dan
  • 17,375
  • 3
  • 36
  • 39
  • 1
    That's exactly what I need to do... but *HOW*? (Don't you just love it when someone's 'answer' is "just do it"... instead of *HOW* to do it?) HOW do I resize the image? HOW do I maintain the aspect ratio of it? (Keep in mind, I do NOT want to resize the image to 100x100... I want to have it *FIT* inside 100x100 (regardless of its original size, or new size)... as long as its width (or height) becomes exactly 100. – Alice Dec 21 '10 at 20:23
  • There are many ways to resize an image, such as http://stackoverflow.com/questions/2658738/the-simplest-way-to-resize-an-uiimage Have you tried simply creating a UIImageView as a subview of the button? I'm not sure if it'll work, but it seems like it'll be the easiest way to do it if it works. – Daniel Dickison Dec 21 '10 at 21:19
  • I can't us the legal, undocumented stuff. And I can't force everyone to update use v4.2.1 frameworks. – Alice Dec 21 '10 at 21:47
  • @Dan - the problem is, that UIButton will always 'scale to fill' the backgroundImage. See Dan Ray's answer above. Have you tried the answer the you proposed? – npellow May 12 '11 at 23:32
0

I faced same issue few days back and resolved it. Please try with this

[_profilePicBtn setImage:profilePic forState:UIControlStateNormal];
_profilePicBtn.imageView.contentMode = UIViewContentModeScaleAspectFit;
jam
  • 3,640
  • 5
  • 34
  • 50