jasongregori
jasongregori

Reputation: 11646

How do I put the image on the right side of the text in a UIButton?

I don't want to use a subview if I can avoid it. I want a UIButton with a background image, text, and an image in it. Right now, when I do that, the image is on the left side of the text. The background image, text, and image all have different highlight states.

Upvotes: 383

Views: 258693

Answers (30)

Will Ullrich
Will Ullrich

Reputation: 2228

I mean the simplest solution I think here is just to tell the button horizontal layout to "fill" the width of the button, rather than align .left as you might initially think to do:

contentHorizontalAlignment = .fill

should do the trick here. Just make sure you tell your button to have a trailing image placement: configuration.imagePlacement = .trailing, and you should get the following:

enter image description here

Just sent your configuration.contentInsets to whatever you prefer to give the button some aesthetic padding. (The default is about 12 on each end.)

Typical full example

lazy var someButton: UIButton = {
    let v = UIButton(type: .custom)
    v.translatesAutoresizingMaskIntoConstraints = false
    
    v.contentHorizontalAlignment = .fill
    
    v.configuration = .filled()
    v.configuration?.imagePlacement = .trailing
    v.configuration?.baseBackgroundColor = .white
    v.configuration?.baseForegroundColor = .systemBlue
    v.configuration?.background.cornerRadius = 5
    
    let conf = UIImage.SymbolConfiguration(pointSize: 12, weight: .light)
    let img = UIImage(systemName: "minus.circle", withConfiguration: conf)
    v.setImage(img, for: [])
    
    v.configuration?.titleTextAttributesTransformer =
       UIConfigurationTextAttributesTransformer { inc in
        var out = inc; out.font = .systemFont(ofSize: 20); return out
    }
    v.setTitle("Thing", for: .normal)
    return v
}()

enter image description here

Upvotes: 10

ChengEn
ChengEn

Reputation: 107

The better approach is to use an UIImage view:

  1. Add an UIImage view to the right side of the button
  2. Setup constraints

If your button needs cornerRadius, set image trailing aligns to the trailing of the button and right content insets of button to 20.

enter image description here

This solution can prevent potential overlapping issue with long text, ie, localization text.

Upvotes: 0

Victor Rius
Victor Rius

Reputation: 4734

UPDATED FOR XCODE 9 (Via Interface Builder)

There's an easier way from the Interface Builder.

Select the UIButton and select this option in the View Utilities > Semantic:

left-to-right enter image description here That's it! Nice and simple!

OPTIONAL - 2nd step:

If you want to adjust the spacing between the image and the title you can change the Image Inset here:

enter image description here

Upvotes: 344

Mary Jones
Mary Jones

Reputation: 125

With Xcode 13.3 I solved in the following few steps and as well adding padding to the image.

After creating the button then do this as listed below:

  1. First define the image:
    let symbol = UIImage(named: "put name of your symbol here")
    
  2. Then in viewDidLoad where you created the button, initialise the above defined image in 1, to add the image to the button & set the properties:
    button.setImage(symbol, for: .normal)
    button.semanticContentAttribute = .forceRightToLeft
    button.configuration?.imagePadding = 2
    

And don't forget to add your button to the view.

Upvotes: 3

Noman Haroon
Noman Haroon

Reputation: 193

This work for me on swift 5.2

let sizeOfTitle: CGFloat = 80
let sizeOfImage: CGFloat = 20
yourButton.titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -sizeOfImage , bottom: 0.0, right: sizeOfImage)
yourButton.imageEdgeInsets = UIEdgeInsets(top: 0.0, left: sizeOfTitle, bottom: 0.0, right: -sizeOfTitle)

Upvotes: 0

akashlal.com
akashlal.com

Reputation: 366

iOS 15 brought an update where you can now handle image placements in buttons in a simpler non messier way, ie. without insets.

In XIB/Storyboards: Modify button image placement in editor Simply set the button 'placement' property to leading/training/top/bottom after adding an image property to button. Since it's leading/training, there is an added advantage of it supporting RTL

**In code (Programmatically): ** Use Button Configuration property programmatically Button configuration, programatically

This is not a backward compatible feature, and will work only in iOS15+ as was demonstrated in WWDC '21 - https://developer.apple.com/videos/play/wwdc2021/10064/?time=236

Developer documentation: https://developer.apple.com/documentation/uikit/uibutton/configuration?changes=_4

Upvotes: 15

Liau Jian Jie
Liau Jian Jie

Reputation: 7081

Simplest solution:

iOS 10 & up, Swift:

button.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
button.titleLabel?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
button.imageView?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)

Before iOS 10, Swift/Obj-C:

button.transform = CGAffineTransformMakeScale(-1.0, 1.0);
button.titleLabel.transform = CGAffineTransformMakeScale(-1.0, 1.0);
button.imageView.transform = CGAffineTransformMakeScale(-1.0, 1.0);

iOS 9 & up, Swift: (Recommended)

button.semanticContentAttribute = .forceRightToLeft

Upvotes: 643

Nick Yap
Nick Yap

Reputation: 887

Took @Piotr's answer and made it into a Swift extension. Make sure to set the image and title before calling this, so that the button sizes properly.

 extension UIButton {
    
    /// Makes the ``imageView`` appear just to the right of the ``titleLabel``.
    func alignImageRight() {
        if let titleLabel = self.titleLabel, imageView = self.imageView {
            // Force the label and image to resize.
            titleLabel.sizeToFit()
            imageView.sizeToFit()
            imageView.contentMode = .ScaleAspectFit
            
            // Set the insets so that the title appears to the left and the image appears to the right. 
            // Make the image appear slightly off the top/bottom edges of the button.
            self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -1 * imageView.frame.size.width,
                bottom: 0, right: imageView.frame.size.width)
            self.imageEdgeInsets = UIEdgeInsets(top: 4, left: titleLabel.frame.size.width,
                bottom: 4, right: -1 * titleLabel.frame.size.width)
          }
        }
     }

Upvotes: 3

Mahendra Liya
Mahendra Liya

Reputation: 13218

I ended up creating a custom button, which allows setting the Image from Inspector. Below is my code:

import UIKit

@IBDesignable
class CustomButton: UIButton {

    @IBInspectable var leftImage: UIImage? = nil
    @IBInspectable var gapPadding: CGFloat = 0

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        setup()
    }

    func setup() {

        if(leftImage != nil) {
            let imageView = UIImageView(image: leftImage)
            imageView.translatesAutoresizingMaskIntoConstraints = false

            addSubview(imageView)

            let length = CGFloat(16)
            titleEdgeInsets.left += length

            NSLayoutConstraint.activate([
                imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: gapPadding),
                imageView.centerYAnchor.constraint(equalTo: self.titleLabel!.centerYAnchor, constant: 0),
                imageView.widthAnchor.constraint(equalToConstant: length),
                imageView.heightAnchor.constraint(equalToConstant: length)
            ])
        }
    }
}

You can adjust the value of Gap Padding from Inspector to adjust the spacing between text and the image.

PS: Used some code portion from @Mark Hennings answer

Upvotes: 0

Mark H
Mark H

Reputation: 801

I decided not to use the standard button image view because the proposed solutions to move it around felt hacky. This got me the desired aesthetic, and it is intuitive to reposition the button by changing the constraints:

extension UIButton {
    func addRightIcon(image: UIImage) {
        let imageView = UIImageView(image: image)
        imageView.translatesAutoresizingMaskIntoConstraints = false

        addSubview(imageView)

        let length = CGFloat(15)
        titleEdgeInsets.right += length

        NSLayoutConstraint.activate([
            imageView.leadingAnchor.constraint(equalTo: self.titleLabel!.trailingAnchor, constant: 10),
            imageView.centerYAnchor.constraint(equalTo: self.titleLabel!.centerYAnchor, constant: 0),
            imageView.widthAnchor.constraint(equalToConstant: length),
            imageView.heightAnchor.constraint(equalToConstant: length)
        ])
    }
}

button with right arrow

Upvotes: 34

rbaldwin
rbaldwin

Reputation: 4860

Xcode 11.4 Swift 5.2

For anyone trying to mirror the Back button style with the chevron like this:

enter image description here

import UIKit

class NextBarButton: UIBarButtonItem {

    convenience init(target: Any, selector: Selector) {

        // Create UIButton
        let button = UIButton(frame: .zero)

        // Set Title
        button.setTitle("Next", for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 17)

        // Configure Symbol
        let config = UIImage.SymbolConfiguration(pointSize: 19.0, weight: .semibold, scale: .large)
        let image = UIImage(systemName: "chevron.right", withConfiguration: config)
        button.setImage(image, for: .normal)

        // Add Target
        button.addTarget(target, action: selector, for: .touchUpInside)

        // Put the Image on the right hand side of the button
        // Credit to liau-jian-jie for this part
        button.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
        button.titleLabel?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
        button.imageView?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)

        // Customise spacing to match system Back button
        button.imageEdgeInsets = UIEdgeInsets(top: 0.0, left: -18.0, bottom: 0.0, right: 0.0)
        button.titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -12.0, bottom: 0.0, right: 0.0)

        self.init(customView: button)
    }
}

Implementation:

override func viewDidLoad() {
    super.viewDidLoad()
    let nextButton = NextBarButton(target: self, selector: #selector(nextTapped))
    navigationItem.rightBarButtonItem = nextButton
}

@objc func nextTapped() {
    // your code
}

Upvotes: 1

Rashid Latif
Rashid Latif

Reputation: 2901

Swift 4 & 5

Change the direction of UIButton image (RTL and LTR)

extension UIButton {
    func changeDirection(){
       isArabic ? (self.contentHorizontalAlignment = .right) : (self.contentHorizontalAlignment = .left)
        // left-right margin 
        self.imageEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
        self.titleEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
    }
}

Upvotes: 0

Sushil Vyas
Sushil Vyas

Reputation: 70

for this issue you can create UIView inside "label with UIImage view" and set UIView class as a UIControl and create IBAction as tuch up in side

enter image description here

Upvotes: 1

Nazmul Hasan
Nazmul Hasan

Reputation: 10590

Do Yourself. Xcode10, swift4,

For programmatically UI design

enter image description here

 lazy var buttonFilter : ButtonRightImageLeftTitle = {
    var button = ButtonRightImageLeftTitle()
    button.setTitle("Playfir", for: UIControl.State.normal)
    button.setImage(UIImage(named: "filter"), for: UIControl.State.normal)
    button.backgroundColor = UIColor.red
    button.contentHorizontalAlignment = .left
    button.titleLabel?.font = UIFont.systemFont(ofSize: 16)
    return button
}()

Edge inset values are applied to a rectangle to shrink or expand the area represented by that rectangle. Typically, edge insets are used during view layout to modify the view’s frame. Positive values cause the frame to be inset (or shrunk) by the specified amount. Negative values cause the frame to be outset (or expanded) by the specified amount.

class ButtonRightImageLeftTitle: UIButton {

    override func layoutSubviews() {
        super.layoutSubviews()

        guard imageView != nil else { return }

        imageEdgeInsets = UIEdgeInsets(top: 5, left: (bounds.width - 35), bottom: 5, right: 5)
        titleEdgeInsets = UIEdgeInsets(top: 0, left: -((imageView?.bounds.width)! + 10), bottom: 0, right: 0 )

    }
}

for StoryBoard UI design

enter image description here enter image description here

Upvotes: 13

jeet.chanchawat
jeet.chanchawat

Reputation: 2575

After trying multiple solutions from around the internet, I was not achieving the exact requirement. So I ended up writing custom utility code. Posting to help someone in future. Tested on swift 4.2

// This function should be called in/after viewDidAppear to let view render
    func addArrowImageToButton(button: UIButton, arrowImage:UIImage = #imageLiteral(resourceName: "my_image_name") ) {
        let btnSize:CGFloat = 32
        let imageView = UIImageView(image: arrowImage)
        let btnFrame = button.frame
        imageView.frame = CGRect(x: btnFrame.width-btnSize-8, y: btnFrame.height/2 - btnSize/2, width: btnSize, height: btnSize)
        button.addSubview(imageView)
        //Imageview on Top of View
        button.bringSubviewToFront(imageView)
    }

Upvotes: 0

Musa almatri
Musa almatri

Reputation: 5842

Extension Way

Using extension to set image on the right side with custom offset

   extension UIButton {
    func addRightImage(image: UIImage, offset: CGFloat) {
        self.setImage(image, for: .normal)
        self.imageView?.translatesAutoresizingMaskIntoConstraints = false
        self.imageView?.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0.0).isActive = true
        self.imageView?.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -offset).isActive = true
    }
}

Upvotes: 6

Dhaval H. Nena
Dhaval H. Nena

Reputation: 4120

To right align image within UIButton try below code

btn.contentHorizontalAlignment = .right

Upvotes: -1

Benjamin
Benjamin

Reputation: 8248

Despite some of the suggested answers being very creative and extremely clever, the simplest solution is as follows:

button.semanticContentAttribute = UIApplication.shared
    .userInterfaceLayoutDirection == .rightToLeft ? .forceLeftToRight : .forceRightToLeft

As simple as that. As a bonus, the image will be at the left side in right-to-left locales.

EDIT: as the question has been asked a few times, this is iOS 9 +.

Upvotes: 353

cnotethegr8
cnotethegr8

Reputation: 7510

Being that the transform solution doesn't work in iOS 11 I decided to write a new approach.

Adjusting the buttons semanticContentAttribute gives us the image nicely to the right without having to relayout if the text changes. Because of this it's the ideal solution. However I still need RTL support. The fact that an app can not change it's layout direction in the same session resolves this issue easily.

With that said, it's pretty straight forward.

extension UIButton {
    func alignImageRight() {
        if UIApplication.shared.userInterfaceLayoutDirection == .leftToRight {
            semanticContentAttribute = .forceRightToLeft
        }
        else {
            semanticContentAttribute = .forceLeftToRight
        }
    }
}

Upvotes: 5

Pramod
Pramod

Reputation: 1133

Swift -Extend the UiButton and put these lines

    if let imageWidth = self.imageView?.frame.width {
        self.titleEdgeInsets = UIEdgeInsetsMake(0, -imageWidth, 0, imageWidth);
    }

    if let titleWidth = self.titleLabel?.frame.width {
        let spacing = titleWidth + 20
        self.imageEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, -spacing);
    }

Upvotes: 4

rock workinghouse
rock workinghouse

Reputation: 11

How about Constraints? Unlike semanticContentAttribute, they don't change semantics. Something like this perhaps:

 button.rightAnchorconstraint(equalTo: button.rightAnchor).isActive = true

or in Objective-C:

[button.imageView.rightAnchor constraintEqualToAnchor:button.rightAnchor].isActive = YES;

Caveats: Untested, iOS 9+

Upvotes: 0

tt.Kilew
tt.Kilew

Reputation: 6084

If this need to be done in UIBarButtonItem, additional wrapping in view should be used
This will work

let view = UIView()
let button = UIButton()
button.setTitle("Skip", for: .normal)
button.setImage(#imageLiteral(resourceName:"forward_button"), for: .normal)
button.semanticContentAttribute = .forceRightToLeft
button.sizeToFit()
view.addSubview(button)
view.frame = button.bounds
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: view)

This won't work

let button = UIButton()
button.setTitle("Skip", for: .normal)
button.setImage(#imageLiteral(resourceName:"forward_button"), for: .normal)
button.semanticContentAttribute = .forceRightToLeft
button.sizeToFit()
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: button)

Upvotes: 17

Jean-Baptiste
Jean-Baptiste

Reputation: 503

Update: Swift 3

class ButtonIconRight: UIButton {
    override func imageRect(forContentRect contentRect:CGRect) -> CGRect {
        var imageFrame = super.imageRect(forContentRect: contentRect)
        imageFrame.origin.x = super.titleRect(forContentRect: contentRect).maxX - imageFrame.width
        return imageFrame
    }

    override func titleRect(forContentRect contentRect:CGRect) -> CGRect {
        var titleFrame = super.titleRect(forContentRect: contentRect)
        if (self.currentImage != nil) {
            titleFrame.origin.x = super.imageRect(forContentRect: contentRect).minX
        }
        return titleFrame
    }
}

Original answer for Swift 2:

A solution that handles all horizontal alignments, with a Swift implementation example. Just translate to Objective-C if needed.

class ButtonIconRight: UIButton {
    override func imageRectForContentRect(contentRect:CGRect) -> CGRect {
        var imageFrame = super.imageRectForContentRect(contentRect)
        imageFrame.origin.x = CGRectGetMaxX(super.titleRectForContentRect(contentRect)) - CGRectGetWidth(imageFrame)
        return imageFrame
    }

    override func titleRectForContentRect(contentRect:CGRect) -> CGRect {
        var titleFrame = super.titleRectForContentRect(contentRect)
        if (self.currentImage != nil) {
            titleFrame.origin.x = CGRectGetMinX(super.imageRectForContentRect(contentRect))
        }
        return titleFrame
    }
}

Also worth noting that it handles quite well image & title insets.

Inspired from jasongregori answer ;)

Upvotes: 26

Henadzi Rabkin
Henadzi Rabkin

Reputation: 7033

In interface builder you can configure options Edge Insets for UIButton, separately each of three parts: content, image, title

enter image description here enter image description here

Xcode 8:

enter image description here

Upvotes: 54

Sourabh Sharma
Sourabh Sharma

Reputation: 8322

swift 3.0 Migration solution given by jasongregori

class ButtonIconRight: UIButton {
        override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
            var imageFrame = super.imageRect(forContentRect: contentRect)
           imageFrame.origin.x = super.titleRect(forContentRect: contentRect).maxX - imageFrame.width
        return imageFrame
        }

        override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
            var titleFrame = super.titleRect(forContentRect: contentRect)
            if (self.currentImage != nil) {
                titleFrame.origin.x = super.imageRect(forContentRect: contentRect).minX
            }
            return titleFrame
        }

Upvotes: 1

Alexander Volkov
Alexander Volkov

Reputation: 8372

Swift 3:

open override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
    var frame = super.imageRect(forContentRect: contentRect)
    let  imageWidth = frame.size.width
    var titleRect = CGRect.zero
    titleRect.size = self.title(for: self.state)!.size(attributes: [NSFontAttributeName: self.titleLabel!.font])
    titleRect.origin.x = (self.frame.size.width - (titleRect.size.width + imageWidth)) / 2.0 + self.titleEdgeInsets.left - self.titleEdgeInsets.right;
    frame.origin.x = titleRect.origin.x + titleRect.size.width - self.imageEdgeInsets.right + self.imageEdgeInsets.left;
    return frame
}

open override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
    var frame = super.titleRect(forContentRect: contentRect)
    if let imageWidth = self.image(for: self.state)?.size.width {
        frame.origin.x = (self.frame.size.width - (frame.size.width + imageWidth)) / 2.0 + self.titleEdgeInsets.left - self.titleEdgeInsets.right;
    }
    return frame
}

Upvotes: 0

barndog
barndog

Reputation: 7163

All of these answers, as of January 2016, are unnecessary. In Interface Builder, set the View Semantic to Force Right-to-Left, or if you prefer programmatic way, semanticContentAttribute = .forceRightToLeft That will cause the image to appear on the right of your text.

Upvotes: 58

Nikolay Shubenkov
Nikolay Shubenkov

Reputation: 3223

Thanks to Vitaliy Gozhenko

I just want to add that you can add IB_DESIGNABLE before your button @interface and set your button class in storyborad. Then you can watch it layout in real time without app launch just at interface building stage

enter image description here

Upvotes: -1

Erik van der Neut
Erik van der Neut

Reputation: 2235

Building on Piotr Tomasik's elegant solution: if you want to have a bit of spacing between the button label and image as well, then include that in your edge insets as follows (copying my code here that works perfectly for me):

    CGFloat spacing          = 3;
    CGFloat insetAmount      = 0.5 * spacing;

    // First set overall size of the button:
    button.contentEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, insetAmount);
    [button sizeToFit];

    // Then adjust title and image insets so image is flipped to the right and there is spacing between title and image:
    button.titleEdgeInsets   = UIEdgeInsetsMake(0, -button.imageView.frame.size.width - insetAmount, 0,  button.imageView.frame.size.width  + insetAmount);
    button.imageEdgeInsets   = UIEdgeInsetsMake(0, button.titleLabel.frame.size.width + insetAmount, 0, -button.titleLabel.frame.size.width - insetAmount);

Thanks Piotr for your solution!

Erik

Upvotes: 3

Lukasz
Lukasz

Reputation: 19916

Solutions mentioned here stopped working, once I enabled Auto Layout. I had to come up with my own:

Subclass UIButton and override layoutSubviews method:

//
//  MIThemeButtonImageAtRight.m
//  Created by Lukasz Margielewski on 7/9/13.
//

#import "MIThemeButtonImageAtRight.h"

static CGRect CGRectByApplyingUIEdgeInsets(CGRect frame, UIEdgeInsets insets);

@implementation MIThemeButtonImageAtRight

- (void)layoutSubviews
{
    [super layoutSubviews];

    CGRect contentFrame = CGRectByApplyingUIEdgeInsets(self.bounds, self.contentEdgeInsets);

    CGRect frameIcon = self.imageView.frame;
    CGRect frameText = self.titleLabel.frame;

    frameText.origin.x = CGRectGetMinX(contentFrame) + self.titleEdgeInsets.left;
    frameIcon.origin.x = CGRectGetMaxX(contentFrame) - CGRectGetWidth(frameIcon);

    self.imageView.frame = frameIcon;
    self.titleLabel.frame = frameText;
}

@end

static CGRect CGRectByApplyingUIEdgeInsets(CGRect frame, UIEdgeInsets insets){

    CGRect f = frame;

    f.origin.x += insets.left;
    f.size.width -= (insets.left + insets.right);
    f.origin.y += (insets.top);
    f.size.height -= (insets.top + insets.bottom);

    return f;

}

Result:

enter image description here

Upvotes: 1

Related Questions