R31n4ld0_ R31n4ld0_ - 6 months ago 578
Objective-C Question

UIButton: how to center an image and a text using imageEdgeInsets and titleEdgeInsets?

If I put only an image in a button and set the imageEdgeInsets more close to the top, the image stays centered and all works as expected:

[button setImage:image forState:UIControlStateNormal];
[button setImageEdgeInsets:UIEdgeInsetsMake(-15.0, 0.0, 0.0, 0.0)];


If I put only a text in a button and set titleEdgeInsets more close to the bottom, the text stays centered and all works as expected:

[button setTitle:title forState:UIControlStateNormal];
[button setTitleEdgeInsets:UIEdgeInsetsMake(0.0, 0.0, -30, 0.0)];


But, if i put the 4 lines together the text interfere with the image and both lost the center alignment.

All my images has 30 pixels width, and if i put 30 in the left parameter of UIEdgeInsetMake for setTitleEdgeInsets, the text is centered again. The problem is that the image never gets centered because appears that it is dependent of the button.titleLabel size. I already tried many calculations with button size, image size, titleLabel size and never get both perfectly centered.

Someone already had the same problem?

Answer

For what it's worth, here's a general solution to positioning the image centered above the text without using any magic numbers. Note that the following code is outdated and you should probably use one of the updated versions below:

// the space between the image and text
CGFloat spacing = 6.0;

// lower the text and push it left so it appears centered 
//  below the image
CGSize imageSize = button.imageView.frame.size;
button.titleEdgeInsets = UIEdgeInsetsMake(
  0.0, - imageSize.width, - (imageSize.height + spacing), 0.0);

// raise the image and push it right so it appears centered
//  above the text
CGSize titleSize = button.titleLabel.frame.size;
button.imageEdgeInsets = UIEdgeInsetsMake(
  - (titleSize.height + spacing), 0.0, 0.0, - titleSize.width);

The following version contains changes to support iOS 7+ that have been recommended in comments below. I haven't tested this code myself, so I'm not sure how well it works or whether it would break if used under previous versions of iOS.

// the space between the image and text
CGFloat spacing = 6.0;

// lower the text and push it left so it appears centered 
//  below the image
CGSize imageSize = button.imageView.image.size;
button.titleEdgeInsets = UIEdgeInsetsMake(
  0.0, - imageSize.width, - (imageSize.height + spacing), 0.0);

// raise the image and push it right so it appears centered
//  above the text
CGSize titleSize = [button.titleLabel.text sizeWithAttributes:@{NSFontAttributeName: button.titleLabel.font}];
button.imageEdgeInsets = UIEdgeInsetsMake(
  - (titleSize.height + spacing), 0.0, 0.0, - titleSize.width);

// increase the content height to avoid clipping
CGFloat edgeOffset = fabsf(titleSize.height - imageSize.height) / 2.0;
button.contentEdgeInsets = UIEdgeInsetsMake(edgeOffset, 0.0, edgeOffset, 0.0);

Swift version

func setupButton(button: UIButton) {
    let spacing: CGFloat = 6.0
    let imageSize: CGSize = button.imageView!.image!.size
    button.titleEdgeInsets = UIEdgeInsetsMake(0.0, -imageSize.width, -(imageSize.height + spacing), 0.0)
    let labelString = NSString(string: button.titleLabel!.text!)
    let titleSize = labelString.sizeWithAttributes([NSFontAttributeName: button.titleLabel!.font])
    button.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + spacing), 0.0, 0.0, -titleSize.width)
    let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0;
    button.contentEdgeInsets = UIEdgeInsetsMake(edgeOffset, 0.0, edgeOffset, 0.0)
}