如果可以避免,我不想使用子视图。我想要一个带有背景图像,文本和图像的UIButton。现在,当我这样做的时候,图像在文本的左边。背景图像、文本和图像都有不同的高亮状态。


当前回答

由于转换解决方案在iOS 11中不起作用,我决定编写一种新的方法。

调整按钮semanticContentAttribute可以让我们很好地将图像放到右边,而不必在文本更改时重新布局。因此它是理想溶液。然而,我仍然需要RTL的支持。事实上,一个应用程序不能改变它的布局方向在同一会话解决这个问题很容易。

话虽如此,它还是相当直截了当的。

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

其他回答

iOS 15带来了一个更新,现在你可以用一种更简单而不混乱的方式来处理按钮中的图像放置。没有insets。

在XIB /故事板: 简单地设置按钮的“位置”属性为领先/训练/顶部/底部后,添加一个图像属性按钮。由于它是领导/培训,所以它支持RTL有一个额外的优势

**在代码中(编程方式):** 以编程方式使用“按钮配置”属性

这不是一个向后兼容的功能,只能在iOS15+中工作,正如WWDC '21 - https://developer.apple.com/videos/play/wwdc2021/10064/?time=236所演示的那样

开发人员文档:https://developer.apple.com/documentation/uikit/uibutton/configuration?changes=_4

把@Piotr的答案变成了一个Swift扩展。请确保在调用此函数之前设置好图像和标题,以便按钮大小适当。

 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)
          }
        }
     }

子类化UIButton是完全没有必要的。相反,你可以简单地为图像设置一个高的左插入值,为标题设置一个小的右插入值。就像这样:

button.imageEdgeInsets = UIEdgeInsetsMake(0., button.frame.size.width - (image.size.width + 15.), 0., 0.);
button.titleEdgeInsets = UIEdgeInsetsMake(0., 0., 0., image.size.width);

我最终创建了一个自定义按钮,允许从检查器设置图像。下面是我的代码:

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)
            ])
        }
    }
}

你可以从检查器中调整空白填充的值来调整文本和图像之间的间距。

PS:使用了一些来自@Mark Hennings答案的代码部分

这里提到的解决方案停止工作,一旦我启用自动布局。我不得不提出我自己的想法:

子类UIButton和覆盖layoutSubviews方法:

//
//  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;

}

结果: