Friday, May 18, 2012

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 have the same problem?



Thanks in advance.


Source: Tips4all

6 comments:

  1. Found how.

    First, configure the text of titleLabel (because of styles, i.e, bold, italic, etc). Then, use setTitleEdgeInsets considering the width of your image:

    [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [button setTitle:title forState:UIControlStateNormal];
    [button.titleLabel setFont:[UIFont boldSystemFontOfSize:10.0]];
    [button setTitleEdgeInsets:UIEdgeInsetsMake(0.0, -image.size.width, -25.0, 0.0)]; // Left inset is the negative of image width.


    After that, use setTitleEdgeInsets considering the width of text bounds:

    [button setImage:image forState:UIControlStateNormal];
    [button setImageEdgeInsets:UIEdgeInsetsMake(-15.0, 0.0, 0.0, -button.titleLabel.bounds.size.width)]; // Right inset is the negative of text bounds width.


    Now the image and the text will be centered (in this example, the image appears above the text).

    Cheers.

    ReplyDelete
  2. For what it's worth, here's a general solution to positioning the image centered above the text without using any magic numbers:

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

    // get the size of the elements here for readability
    CGSize imageSize = button.imageView.frame.size;
    CGSize titleSize = button.titleLabel.frame.size;

    // lower the text and push it left to center it
    button.titleEdgeInsets = UIEdgeInsetsMake(
    0.0, - imageSize.width, - (imageSize.height + spacing), 0.0);

    // the text width might have changed (in case it was shortened before due to
    // lack of space and isn't anymore now), so we get the frame size again
    titleSize = button.titleLabel.frame.size;

    // raise the image and push it right to center it
    button.imageEdgeInsets = UIEdgeInsetsMake(
    - (titleSize.height + spacing), 0.0, 0.0, - titleSize.width);

    ReplyDelete
  3. Using button.titleLabel.frame.size.width works fine as long the label is short enough not to be truncated. When the label text gets truncated positioning doesn't work. Tanking

    CGSize titleSize = [[[button titleLabel] text] sizeWithFont:[[botton titleLabel] font]];


    works for me even when the label text is truncated.

    ReplyDelete
  4. I looked at existing answers but I also found that setting the button frame is an important first step.

    Here is a function that I use that takes care of this:

    const CGFloat kImageTopOffset = -15;
    const CGFloat kTextBottomOffset = -25;

    + (void) centerButtonImageTopAndTextBottom: (UIButton*) button
    frame: (CGRect) buttonFrame
    text: (NSString*) textString
    textColor: (UIColor*) textColor
    font: (UIFont*) textFont
    image: (UIImage*) image
    forState: (UIControlState) buttonState
    {
    button.frame = buttonFrame;

    [button setTitleColor: (UIColor*) textColor
    forState: (UIControlState) buttonState];

    [button setTitle: (NSString*) textString
    forState: (UIControlState) buttonState ];


    [button.titleLabel setFont: (UIFont*) textFont ];

    [button setTitleEdgeInsets: UIEdgeInsetsMake( 0.0, -image.size.width, kTextBottomOffset, 0.0)];

    [button setImage: (UIImage*) image
    forState: (UIControlState) buttonState ];

    [button setImageEdgeInsets: UIEdgeInsetsMake( kImageTopOffset, 0.0, 0.0,- button.titleLabel.bounds.size.width)];
    }

    ReplyDelete
  5. There are some great examples in here, but I couldn't get this to work in all cases when also dealing with multiple lines of text (text wrapping). To finally get it to work I combined a couple of the techniques:


    I used Jesse Crossen example above. However, I fixed a text height
    issue and I added the ability to specify a horizontal text margin.
    The margin is useful when allowing text to wrap so it doesn't hit
    the edge of the button:

    // the space between the image and text
    CGFloat spacing = 10.0;
    float textMargin = 6;

    // get the size of the elements here for readability
    CGSize imageSize = picImage.size;
    CGSize titleSize = button.titleLabel.frame.size;
    CGFloat totalHeight = (imageSize.height + titleSize.height + spacing); // get the height they will take up as a unit

    // lower the text and push it left to center it
    button.titleEdgeInsets = UIEdgeInsetsMake( 0.0, -imageSize.width +textMargin, - (totalHeight - titleSize.height), +textMargin ); // top, left, bottom, right

    // the text width might have changed (in case it was shortened before due to
    // lack of space and isn't anymore now), so we get the frame size again
    titleSize = button.titleLabel.bounds.size;

    button.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + spacing), 0.0, 0.0, -titleSize.width ); // top, left, bottom, right

    Make sure you setup the text label to wrap


    button.titleLabel.numberOfLines = 2;
    button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;
    button.titleLabel.textAlignment = UITextAlignmentCenter;


    This will mostly work now. However, I had some buttons that wouldn't render their image correctly. The image was either shifted to far to the right or left (it wasn't centered). So I used an UIButton layout override technique to force the imageView to be centered.


    @interface CategoryButton : UIButton
    @end

    @implementation CategoryButton

    - (void)layoutSubviews
    {
    // Allow default layout, then center imageView
    [super layoutSubviews];

    UIImageView *imageView = [self imageView];
    CGRect imageFrame = imageView.frame;
    imageFrame.origin.x = (int)((self.frame.size.width - imageFrame.size.width)/ 2);
    imageView.frame = imageFrame;
    }
    @end

    ReplyDelete
  6. I made a method for @TodCunningham's answer

    -(void) AlignTextAndImageOfButton:(UIButton *)button
    {
    CGFloat spacing = 2; // the amount of spacing to appear between image and title
    button.imageView.backgroundColor=[UIColor clearColor];
    button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;
    button.titleLabel.textAlignment = UITextAlignmentCenter;
    // get the size of the elements here for readability
    CGSize imageSize = button.imageView.frame.size;
    CGSize titleSize = button.titleLabel.frame.size;

    // lower the text and push it left to center it
    button.titleEdgeInsets = UIEdgeInsetsMake(0.0, - imageSize.width, - (imageSize.height + spacing), 0.0);

    // the text width might have changed (in case it was shortened before due to
    // lack of space and isn't anymore now), so we get the frame size again
    titleSize = button.titleLabel.frame.size;

    // raise the image and push it right to center it
    button.imageEdgeInsets = UIEdgeInsetsMake(- (titleSize.height + spacing), 0.0, 0.0, - titleSize.width);
    }

    ReplyDelete