A simple way to put a UIImage in a UIButton
Asked Answered
T

8

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.

Teno answered 21/12, 2010 at 17:16 Comment(0)
E
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.

Ellmyer answered 21/12, 2010 at 21:7 Comment(3)
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"?)Teno
Does anyone know if this "annoyance" has been addressed in a later version of xcode?Tish
As of iOS 6.1.3, they still don't respect this property.Nessy
B
6
UIImage *img = [UIImage imageNamed:@"yourImageName"];
button.imageView.contentMode = UIViewContentModeScaleAspectFit;
[button setImage:img forState:UIControlStateNormal];
Bihari answered 15/8, 2013 at 12:42 Comment(1)
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.Skate
P
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.

Pomegranate answered 21/12, 2010 at 21:38 Comment(3)
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?Teno
@Teno 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.Pomegranate
@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.Guthrey
I
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]; 
Intertexture answered 21/12, 2010 at 21:0 Comment(2)
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.Teno
> 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.Teno
T
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.

Teno answered 21/12, 2010 at 21:59 Comment(0)
A
1

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

All the best.

Amphi answered 21/12, 2010 at 17:20 Comment(2)
Actually, I'd place the UIImageView under the button, and make the button "Custom" so it's invisible.Entomology
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"???Teno
U
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.

Upstream answered 21/12, 2010 at 18:52 Comment(4)
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.Teno
There are many ways to resize an image, such as #2659238 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.Their
I can't us the legal, undocumented stuff. And I can't force everyone to update use v4.2.1 frameworks.Teno
@Upstream - 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?Infirm
C
0

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

[_profilePicBtn setImage:profilePic forState:UIControlStateNormal];
_profilePicBtn.imageView.contentMode = UIViewContentModeScaleAspectFit;
Cheekbone answered 14/10, 2014 at 8:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.