28

I have a UIButton and I am trying to set a title and an image on it.

I would like to align the title for a UIButton to the left side and place an image aligned to the right. I am trying to get the look and feel of the button in Timer in Clocks app (the one which says "When Timer Ends").

I fiddled with contentHorizontalAlignment, contentEdgeInsets, titleEdgeInsets and imageEdgeInsets to achieve my goal, but to no avail. The documentation is also quite sparse for the same.

How can I achieve the same?

Also related questions, Timer in Clocks app has two set of Text, one aligned to the left and other aligned right with the image? How can that be done in a UIButton? ( I do not need that functionality though at the moment).

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
siasl
  • 497
  • 1
  • 8
  • 17

9 Answers9

49

Here is the code I use for that:

//Right-align the button image
CGSize size = [[myButton titleForState:UIControlStateNormal] sizeWithFont:myButton.titleLabel.font];
[myButton setImageEdgeInsets:UIEdgeInsetsMake(0, 0, 0, -size.width)];
[myButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 0, 0, myButton.imageView.image.size.width + 5)];
ingham
  • 1,636
  • 15
  • 30
  • This is IMO a somewhat better, if somewhat overly concise, answer using code versus the top-voted answer by @Bill which tells you to use these API calls but doesn't show calling them. It would be better if it showed calling the setImage:forState:. Also, I have found in some cases like actionSheets that once you have setImageEdgeInsets you don't need to adjust the titleEdgeInsets -- they have taken your image's width into account already. YMMV. – natbro Jan 15 '12 at 14:31
  • 3
    In case you set the `contentHorizontalAlignment` to `UIControlContentHorizontalAlignmentRight`, you also need to add an inset to the left in the `titleEdgeInsets`. This is: `[myButton setTitleEdgeInsets:UIEdgeInsetsMake(0, -imageSize.width, 0, imageSize.width)];` – vilanovi Feb 13 '13 at 09:52
35

Change the imageEdgeInsets property on the UIButton and then just use setImage:forState:

Darshan Kunjadiya
  • 3,323
  • 1
  • 29
  • 31
Bill
  • 44,502
  • 24
  • 122
  • 213
14

The following code works:

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = buttonRect;
[button setTitle:@"A title" forState:UIControlStateNormal];
[button setImage:buttonImage forState:UIControlStateNormal];
button.imageEdgeInsets = UIEdgeInsetsMake(0.0, WIDTH(button.titleLabel) + 10.0, 0.0, 0.0);
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
yaris
  • 141
  • 1
  • 2
9

Remember that UIButton inherits from UIView, and so you can add subviews to it just like any other view. In your situation, you would create a button, then add a UILabel on the left side and a UIImage on the right:

// Create the button
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];

// Now load the image and create the image view
UIImage *image = [UIImage imageNamed:@"yourImage.png"];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(/*frame*/)];
[imageView setImage:image];

// Create the label and set its text
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(/*frame*/)];
[label setText:@"Your title"];

// Put it all together
[button addSubview:label];
[button addSubview:imageView];
Tim
  • 59,527
  • 19
  • 156
  • 165
  • This does not seem to work too well. I have a background image on the button and the label is not visible if I set a background image. If I remove the background image from the button, the label becomes visible? I added the lable to the parent view of UIButton and aligned to sit on top of UIButton and it works then. Any idea why it is behaving this way? – siasl Jul 12 '09 at 16:48
  • 2
    Usually that kind of behavior presents itself when the background image is added as a subview of the UIButton on top of your custom views. Instead of setting the button's backgroundImage property, think about adding a UIImageView yourself for the background image, then adding the other custom content on top of that image view. – Tim Jul 12 '09 at 18:13
  • No need to roll your own logic - use the `imageEdgeInsets` property on UIButton and `setImage:forState:`. – Bill Jan 26 '11 at 23:00
  • The OP specifically said: "I fiddled with ... `imageEdgeInsets` to achieve my goal, but to no avail." – Tim Jan 27 '11 at 03:06
  • This answer suggests creating a new `UIImageView` inside button, which already has one. It certainly works, but the recommended solution would involve `imageRectForContentRect:` and `titleRectForContentRect:`, which are expressly provided for this purpose. – cleverbit Nov 18 '13 at 15:17
  • @Tim thanks for your code please tell me how I can change the image of imageView programmatically on button click event ?? – Varun Naharia Jun 01 '15 at 14:09
  • @VarunNaharia: The best thing to do for advice on that matter is to ask a new question, not use the comments on this one (which is almost six years old!). – Tim Jun 01 '15 at 15:18
  • @Tim please see here http://stackoverflow.com/questions/30577908/how-to-change-image-of-uiimageview-that-is-inside-a-uibutton-programatically – Varun Naharia Jun 01 '15 at 15:56
  • @Tim Do you have an answer for this question or not ? – Varun Naharia Jun 02 '15 at 05:14
  • It looks like you've already accepted an answer on that question. Please stop reusing the comments on this old question to discuss other topics! – Tim Jun 02 '15 at 20:11
4

You can also override the titleRectForContentRect: and imageRectForContentRect: methods of the UIButton in order position your text and image anywhere you want.

ohnit
  • 423
  • 3
  • 9
4

In Swift for anyone that cares (with a 10pt spacing):

let size = (self.titleForState(UIControlState.Normal)! as NSString).sizeWithAttributes([NSFontAttributeName:self.titleLabel!.font])
let offset = (size.width + 10)
self.imageEdgeInsets = UIEdgeInsetsMake(0, offset, 0, -offset)
self.titleEdgeInsets = UIEdgeInsetsMake(0, 0, 0, self.imageView!.frame.size.width + 10)
Brenden
  • 7,708
  • 11
  • 61
  • 75
4

For using on iOS 9, i tried many answers in this thread but none of them suitable for me. I found answer for myself as follow:

class MyButton: UIButton {

  override func layoutSubviews() {
    super.layoutSubviews()

    self.contentHorizontalAlignment = UIControlContentHorizontalAlignment.Left

    if self.imageView?.image != nil {
      // Move icon to right side
      self.imageEdgeInsets = UIEdgeInsets(
        top: 0,
        left: self.bounds.size.width - self.imageView!.image!.size.width,
        bottom: 0,
        right: 0)

      // Move title to left side
      self.titleEdgeInsets = UIEdgeInsetsMake(0, -self.imageView!.frame.size.width + 8, 0, 0)

    }
  }
}

SWIFT 3:

class MyButton: UIButton {

    override func layoutSubviews() {
        super.layoutSubviews()

        self.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left

        if self.imageView?.image != nil {
            // Move icon to right side
            self.imageEdgeInsets = UIEdgeInsets(
                top: 0,
                left: self.bounds.size.width - self.imageView!.image!.size.width,
                bottom: 0,
                right: 0)

            // Move title to left side
            self.titleEdgeInsets = UIEdgeInsetsMake(0, -self.imageView!.frame.size.width + 8, 0, 0)

        }
    }
}

I hope it will help somebody in the case as me!

Mikel Sanchez
  • 2,870
  • 1
  • 20
  • 28
Quang Tran
  • 1,309
  • 13
  • 14
1

Here's a subclass of UIButton in Swift that aligns the image on the left and the text in the centre. Set imageLeftInset to your desired value.

class LeftImageAlignedButton : UIButton {

var imageLeftInset : CGFloat = 10.0

override func layoutSubviews() {
    super.layoutSubviews()
    self.contentHorizontalAlignment = .Left
    if let font = titleLabel?.font {
        let textWidth = ((titleForState(state) ?? "") as NSString).sizeWithAttributes([NSFontAttributeName:font]).width
        let imageWidth = imageForState(state)?.size.width ?? 0.0
        imageEdgeInsets = UIEdgeInsets(top: 0, left: imageLeftInset, bottom: 0, right: 0)
        titleEdgeInsets = UIEdgeInsets(top: 0, left: bounds.width/2 - textWidth/2.0 - imageWidth, bottom: 0, right: 0)
    }
}
}
Jared Forsyth
  • 12,808
  • 7
  • 45
  • 54
Mike Pollard
  • 10,195
  • 2
  • 37
  • 46
-2

Create a UIButton subclass and override its layoutSubviews method to align your text & image programmatically. Or use something like https://github.com/stevestreza/BlockKit/blob/master/Source/UIView-BKAdditions.h so you can implement layoutSubviews using a block.

Steven Kramer
  • 8,473
  • 2
  • 37
  • 43
  • No problem, although I'm not sure it's worth using this mechanism vs. plain subclassing. – Steven Kramer Sep 03 '13 at 13:20
  • While this is a good strategy for views in general, `-(CGRect) imageRectForContentRect:(CGRect)contentRect` and `-(CGRect) titleRectForContentRect:(CGRect)contentRect` are expressly for this purpose. Unfortunately `layoutSubviews` would be called every time the view layout is changed, making it unsuitable in this case. – cleverbit Nov 18 '13 at 15:14
  • But I'll bet `imageRectForContentRect:` et al. are also called on layout - and I wonder why you think "this case" makes it unsuitable? Apart from that, layoutSubviews gives you full control over the layout in one go, which is also nice. I guess it depends - IIRC correctly I had some trouble making a button with top-aligned icon and bottom-aligned text (using …forContentRect). – Steven Kramer Nov 18 '13 at 20:10