iPhone UIButton - image position

87,992

Solution 1

Here is my own way to do the thing, (after about 10 years)

  1. Subclass from UIButton (Button, as we're living in Swift era)
  2. Put an image and a label in a stack view.
class CustomButton: Button {

    var didLayout: Bool = false // The code must be called only once

    override func layoutSubviews() {
        super.layoutSubviews()
        if !didLayout, let imageView = imageView, let titleLabel = titleLabel {
            didLayout = true
            let stack = UIStackView(arrangedSubviews: [titleLabel, imageView])
            addSubview(stack)
            stack.edgesToSuperview() // I use TinyConstraints library. You could handle the constraints directly
            stack.axis = .horizontal
        }
    }
}

Solution 2

My solution to this is quite simple

[button sizeToFit];
button.titleEdgeInsets = UIEdgeInsetsMake(0, -button.imageView.frame.size.width, 0, button.imageView.frame.size.width);
button.imageEdgeInsets = UIEdgeInsetsMake(0, button.titleLabel.frame.size.width, 0, -button.titleLabel.frame.size.width);

Solution 3

On iOS 9 onwards, seems that a simple way to achieve this is to force the semantic of the view.

enter image description here

Or programmatically, using:

button.semanticContentAttribute = .ForceRightToLeft

Solution 4

Set the imageEdgeInset and titleEdgeInset to move the components around within your image. You could also create a button using those graphics that is full size, and use that as the background image for the button (then use titleEdgeInsets to move the title around).

Solution 5

Raymond W's answer is best here. Subclass UIButton with custom layoutSubviews. Extremely simple to do, here's a layoutSubviews implementation that worked for me:

- (void)layoutSubviews
{
    // Allow default layout, then adjust image and label positions
    [super layoutSubviews];

    UIImageView *imageView = [self imageView];
    UILabel *label = [self titleLabel];

    CGRect imageFrame = imageView.frame;
    CGRect labelFrame = label.frame;

    labelFrame.origin.x = imageFrame.origin.x;
    imageFrame.origin.x = labelFrame.origin.x + CGRectGetWidth(labelFrame);

    imageView.frame = imageFrame;
    label.frame = labelFrame;
}
Share:
87,992
Pavel Yakimenko
Author by

Pavel Yakimenko

Updated on November 19, 2020

Comments

  • Pavel Yakimenko
    Pavel Yakimenko over 3 years

    I have a UIButton with text "Explore the app" and UIImage (>) In Interface Builder it looks like:

    [ (>) Explore the app ]
    

    But I need to place this UIImage AFTER the text:

    [ Explore the app (>) ]
    

    How can I move the UIImage to the right?

  • Steven Kramer
    Steven Kramer almost 13 years
    This is so much easier than all the other advice (like the one above) about trying to place everything correctly using hand-tuned insets.
  • Pavel Yakimenko
    Pavel Yakimenko over 12 years
    This way is better in the case you need to manage many buttons, but I need to change only one button :)
  • Kim
    Kim about 11 years
    It is much less code to simply set the insets than to implement a subclass. This is the whole point of the insets. Manipulating frames for sub views (that you have not created) feels more like a hack.
  • Scakko
    Scakko about 11 years
    If the button image is nil the label results misplaced, probably because the UIImageView is not inserted (Tested on iOS6.0). You should consider editing frames only if imageView.image is not nil.
  • PokerIncome.com
    PokerIncome.com almost 11 years
    This works great! It's hard to understand the edge inset concept. Any idea why we need to set both left and right edge inset? Theoretically, if I move the title to left and the image to right, that would be enough. Why do I need to set both left and right?
  • Kirk Woll
    Kirk Woll over 10 years
    @kimsnarf, really? It's a lot less work (and less of a hack) to tweak the insets whenever you make a minor change in the size of the image or the length of the title?
  • Bimawa
    Bimawa over 10 years
    THE BEST SOLUTION I'VE FOUND
  • i-konov
    i-konov about 10 years
    I would suggest the following improvement to this answer so both views stay centered: 'CGFloat cumulativeWidth = CGRectGetWidth(imageFrame) + CGRectGetWidth(labelFrame) + 10; CGFloat excessiveWidth = CGRectGetWidth(self.bounds) - cumulativeWidth; labelFrame.origin.x = excessiveWidth / 2; imageFrame.origin.x = CGRectGetMaxX(labelFrame) + 10;'
  • mclaughj
    mclaughj about 10 years
    This answer works perfectly. The accepted answer is correct, and points to the correct API docs, but this is the copy and paste solution to do what the OP requested.
  • Brad G
    Brad G about 9 years
    Becomes a bit complicated when you start changing the button text. The subclassed solution worked better for me in this case.
  • rounak
    rounak almost 9 years
    This breaks on iOS 7 for me. Anyone else? Works fine on iOS 8.
  • Gil Sand
    Gil Sand over 8 years
    Don't support iOS7 at all and your problem will be gone. You shouldn't supoprt it anyway.
  • Toby
    Toby over 8 years
    Thank you for showing me you have to apply equal widths to left and right sides, I don't 100% understand how it works (because sometimes it will affect the size as well as origin) but I've been able to solve similar issues using this method.
  • farzadshbfn
    farzadshbfn almost 8 years
    As much as i liked your answer (+1), I hate to say it might not be the "proper" way to do this, but it's one of the easiest.
  • Alvivi
    Alvivi almost 8 years
    @farzadshbfn you are right. I changed that word for "simple", seems more consistent.
  • Joe Susnick
    Joe Susnick almost 8 years
    Thanks! I like this a lot more than the answers that subclass and override layoutSubviews() :)
  • DrPatience
    DrPatience about 7 years
    Best way to go about this in my humble opinion :)
  • manmal
    manmal almost 6 years
    Of course .forceRightToLeft is an option! Just use the opposite value (.forceLeftToRight) if UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft.
  • Samman Bikram Thapa
    Samman Bikram Thapa about 5 years
    @farzadshbfn Why would this not be the "proper" way to do?
  • farzadshbfn
    farzadshbfn about 5 years
    @SammanBikramThapa IMHO the proper way would be to subclass the UIButton and override layoutSubviews and respect semanticContentAttribute in our layout logic, instead of changing semanticContentAttribute itself. (changing semantic approach, will not work well with internationalization)
  • Pavel Yakimenko
    Pavel Yakimenko over 4 years
    @farzadshbfn is totally right. No matter this answer scores most points it's not correct - It may break UX for right to left interface.
  • Pavel Yakimenko
    Pavel Yakimenko almost 4 years
    It is not good as it will break the layout for all RTL users. You will need to force left to right for them. You better try other solution from here.
  • Vyachaslav Gerchicov
    Vyachaslav Gerchicov almost 3 years
    "right-to-left"l is used for "right-to-left" languages like Arabic. So it is a bad approach to use it for something else.
  • kiran
    kiran over 2 years
    @Alvivi it do have top button too ?