A simple way to put a UIImage in a UIButton

℡╲_俬逩灬. 提交于 2019-12-02 19:35:56

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.

ChikabuZ
UIImage *img = [UIImage imageNamed:@"yourImageName"];
button.imageView.contentMode = UIViewContentModeScaleAspectFit;
[button setImage:img forState:UIControlStateNormal];

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.

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.

Patricia

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]; 

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

All the best.

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.

user3667939

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

[_profilePicBtn setImage:profilePic forState:UIControlStateNormal];
_profilePicBtn.imageView.contentMode = UIViewContentModeScaleAspectFit;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!