ChatGPT解决这个技术问题 Extra ChatGPT

使用 imageEdgeInsets 和 titleEdgeInsets 对齐 UIButton 上的文本和图像

我想在两行文本的左侧放置一个图标,使图像和文本开头之间有大约 2-3 像素的空间。控件本身水平居中对齐(通过 Interface Builder 设置)

该按钮将类似于以下内容:

|                  |
|[Image] Add To    |
|        Favorites |

我正在尝试使用 contentEdgeInset、imageEdgeInsets 和 titleEdgeInsets 进行配置,但无济于事。我知道负值会扩大边缘,而正值会缩小边缘以使其更靠近中心。

我试过了:

[button setTitleEdgeInsets:UIEdgeInsetsMake(0, -image.size.width, 0, 0)];
[button setImageEdgeInsets:UIEdgeInsetsMake(0, button.titleLabel.bounds.size.width, 0, 0)];

但这不能正确显示。我一直在调整这些值,但是从左侧插入值的 -5 到 -10 似乎并没有以预期的方式移动它。 -10 会将文本一直向左移动,所以我希望 -5 将它从左侧移动到一半,但事实并非如此。

插图背后的逻辑是什么?我不熟悉图像放置和相关术语。

我用这个 SO question 作为参考,但我的价值观有些不对。 UIButton: how to center an image and a text using imageEdgeInsets and titleEdgeInsets?


b
barry

我参加这个聚会有点晚了,但我想我有一些有用的东西要补充。

Kekoa 的回答很好,但是,正如 RonLugge 所提到的,它可以使按钮不再尊重 sizeToFit,或者更重要的是,可以导致按钮在固有大小时剪切其内容。哎呀!

不过,首先,

简要说明我认为 imageEdgeInsetstitleEdgeInsets 的工作原理:

docs for imageEdgeInsets 部分内容如下:

使用此属性调整按钮图像的有效绘图矩形的大小和重新定位。您可以为四个插图(上、左、下、右)中的每一个指定不同的值。正值会缩小或插入该边缘 - 将其移近按钮的中心。负值扩展或开始该边缘。

我相信这个文档是在想象按钮没有标题的情况下编写的,只有一个图像。以这种方式思考更有意义,并且表现出 UIEdgeInsets 通常的行为。基本上,图像的框架(或标题,带有 titleEdgeInsets)向内移动以获取正插图,向外移动以获取负插图。

好的,那又怎样?

我快到那里了!这是您默认设置的内容,设置图像和标题(按钮边框为绿色只是为了显示它的位置):

https://i.stack.imgur.com/9BhvR.png

当您想要图像和标题之间的间距时,不会导致任何一个被压碎,您需要设置四个不同的插图,每个图像和标题两个。那是因为您不想更改这些元素框架的大小,而只想更改它们的位置。当您开始以这种方式思考时,对 Kekoa 的优秀类别所需的更改变得清晰:

@implementation UIButton(ImageTitleCentering)

- (void)centerButtonAndImageWithSpacing:(CGFloat)spacing {
    CGFloat insetAmount = spacing / 2.0;
    self.imageEdgeInsets = UIEdgeInsetsMake(0, -insetAmount, 0, insetAmount);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, -insetAmount);
}

@end

但是等等,你说,当我这样做时,我得到了这个:

https://i.stack.imgur.com/GmVAb.png

哦耶!我忘了,the docs 警告过我这件事。他们说,部分:

此属性仅用于在布局期间定位图像。该按钮不使用此属性来确定intrinsicContentSize 和sizeThatFits:。

一个属性可以提供帮助,那就是 contentEdgeInsetsThe docs 部分原因是:

该按钮使用此属性来确定intrinsicContentSize 和sizeThatFits:。

听起来不错。因此,让我们再次调整类别:

@implementation UIButton(ImageTitleCentering)

- (void)centerButtonAndImageWithSpacing:(CGFloat)spacing {
    CGFloat insetAmount = spacing / 2.0;
    self.imageEdgeInsets = UIEdgeInsetsMake(0, -insetAmount, 0, insetAmount);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, -insetAmount);
    self.contentEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, insetAmount);
}

@end

你得到了什么?

https://i.stack.imgur.com/uTUPc.png

对我来说似乎是赢家。

在 Swift 中工作并且根本不想做任何思考?这是 Swift 中扩展的最终版本:

extension UIButton {

    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        let isRTL = UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft
        if isRTL {
           imageEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: -insetAmount)
           titleEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount, bottom: 0, right: insetAmount)
           contentEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount, bottom: 0, right: -insetAmount)
        } else {
           imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount, bottom: 0, right: insetAmount)
           titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: -insetAmount)
           contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
        }
    }
}

多么棒的答案!是的,聚会晚了几年,但这解决了 intrinsicContentSize 不正确的问题,这在这些自动布局的日子里非常重要,因为原始答案被接受了。
如果您希望按钮外部与图像和标签之间的间距相同,则将 spacing 添加到 self.contentEdgeInsets 的四个值中,如下所示:self.contentEdgeInsets = UIEdgeInsetsMake(spacing, spacing + insetAmount, spacing, spacing + insetAmount);
很好的答案,可惜当图像右对齐并且文本长度可以变化时它不能很好地工作。
几乎完美的答案!唯一缺少的是,当它在 Right-to-Left 界面上运行时,图像和标题的插图必须反转。
@YestayMuratov 设置 button.imageView?.contentMode = .scaleAspectFit。我为此制作了一个小测试应用程序,您可以使用 github.com/tomas789/UIButtonEdgeInsets
C
Community

我同意 imageEdgeInsetstitleEdgeInsets 上的文档应该更好,但我想出了如何在不诉诸试验和错误的情况下获得正确的定位。

总体思路在 this question,但前提是您希望文本和图像都居中。我们不希望图像和文本单独居中,我们希望图像和文本作为一个实体一起居中。这实际上是 UIButton 已经做的,所以我们只需要调整间距。

CGFloat spacing = 10; // the amount of spacing to appear between image and title
tabBtn.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, spacing);
tabBtn.titleEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, 0);

我还把它变成了 UIButton 的一个类别,所以它很容易使用:

UIButton+Position.h

@interface UIButton(ImageTitleCentering)

-(void) centerButtonAndImageWithSpacing:(CGFloat)spacing;

@end

UIButton+位置.m

@implementation UIButton(ImageTitleCentering)

-(void) centerButtonAndImageWithSpacing:(CGFloat)spacing {
    self.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, spacing);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, 0);
}

@end

所以现在我要做的就是:

[button centerButtonAndImageWithSpacing:10];

我每次都能得到我需要的东西。不再手动弄乱边缘插图。

编辑:交换图像和文本

在评论中回复@Javal

使用相同的机制,我们可以交换图像和文本。要完成交换,只需使用负间距,但还要包括文本和图像的宽度。这将需要已知框架并且已经执行布局。

[self.view layoutIfNeeded];
CGFloat flippedSpacing = -(desiredSpacing + button.currentImage.size.width + button.titleLabel.frame.size.width);
[button centerButtonAndImageWithSpacing:flippedSpacing];

当然,您可能希望为此创建一个不错的方法,可能会添加第二类方法,这留给读者作为练习。


如果我的正常标题和突出显示的标题不同,当用户突出显示和取消突出显示按钮时,如何重新居中?
@user102008 完全不同的标题?还是只是颜色不同?如果您使用 [UIButton setTitleColor:forState:] 甚至 [UIButton setTitle:forState:],则定位不应改变。
您如何修复 [button sizeToFit] 使其尺寸正确?
@RonLugge 我不确定您为什么需要 sizeToFit,所以我无法回答您的问题。不使用 sizeToFit 对我来说效果很好。
我需要 sizeToFit 因为按钮/文本是动态的,所以我需要调整按钮的大小以适合(用户定义的)标签。问题是它不能补偿增加的空间。我最终覆盖了它,并手动将框架的宽度增加了 10。
h
hbk

另外,如果你想做类似的东西

https://i.stack.imgur.com/qjino.png

你需要

1.设置按钮的水平和垂直对齐方式

https://i.stack.imgur.com/mMHjv.png

找到所有需要的值并设置 UIImageEdgeInsets CGSize buttonSize = button.frame.size; NSString *buttonTitle = button.titleLabel.text; CGSize titleSize = [buttonTitle sizeWithAttributes:@{ NSFontAttributeName : [UIFont camFontZonaProBoldWithSize:12.f] }]; UIImage *buttonImage = button.imageView.image; CGSize buttonImageSize = buttonImage.size; CGFloat offsetBetweenImageAndText = 10; //图文垂直间距 [button setImageEdgeInsets:UIEdgeInsetsMake((buttonSize.height - (titleSize.height + buttonImageSize.height)) / 2 - offsetBetweenImageAndText, (buttonSize.width - buttonImageSize.width) / 2, 0,0) ]; [按钮 setTitleEdgeInsets:UIEdgeInsetsMake((buttonSize.height - (titleSize.height + buttonImageSize.height)) / 2 + buttonImageSize.height + offsetBetweenImageAndText, titleSize.width + [button imageEdgeInsets].left > buttonSize.width ? -buttonImage.size.宽度 + (buttonSize.width - titleSize.width) / 2 : (buttonSize.width - titleSize.width) / 2 - buttonImage.size.width, 0,0)];

这将在按钮上排列您的标题和图像。

另请注意在每次重新布局时更新此内容

迅速

import UIKit

extension UIButton {
    // MARK: - UIButton+Aligment

    func alignContentVerticallyByCenter(offset:CGFloat = 10) {
        let buttonSize = frame.size

        if let titleLabel = titleLabel,
            let imageView = imageView {

            if let buttonTitle = titleLabel.text,
                let image = imageView.image {
                let titleString:NSString = NSString(string: buttonTitle)
                let titleSize = titleString.sizeWithAttributes([
                    NSFontAttributeName : titleLabel.font
                    ])
                let buttonImageSize = image.size

                let topImageOffset = (buttonSize.height - (titleSize.height + buttonImageSize.height + offset)) / 2
                let leftImageOffset = (buttonSize.width - buttonImageSize.width) / 2
                imageEdgeInsets = UIEdgeInsetsMake(topImageOffset,
                                                   leftImageOffset,
                                                   0,0)

                let titleTopOffset = topImageOffset + offset + buttonImageSize.height
                let leftTitleOffset = (buttonSize.width - titleSize.width) / 2 - image.size.width

                titleEdgeInsets = UIEdgeInsetsMake(titleTopOffset,
                                                   leftTitleOffset,
                                                   0,0)
            }
        }
    }
}

F
Freeman

在界面生成器中。选择 UIButton -> Attributes Inspector -> Edge=Title 并修改边缘插图


I
Itachi

使用这个可以避免很多麻烦——

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;

这会将您的所有内容自动对齐到左侧(或您想要的任何位置)

斯威夫特 3:

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center;

myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center; //纠正错字
S
Sahil

Xcode 8.0 中,您只需更改尺寸检查器中的 insets 即可。

选择 UIButton -> Attributes Inspector -> 转到大小检查器并修改内容、图像和标题插图。

https://i.stack.imgur.com/9pdLu.png

如果您想更改右侧的图像,您只需在 Attribute inspector 中将语义属性更改为 Force Right-to-left 即可。

https://i.stack.imgur.com/22gz8.png


在我的情况下,按钮图像永远不会使用 xcode 10 移动到文本的右侧?你能帮我吗?
嗨 Satish,这也适用于 xcode 10。希望您设置的图像不是背景图像,您还可以使用尺寸检查器更改图像昆虫。
这个答案对我很有帮助。请注意,如果要支持 RTL 和 LTR,则需要在代码中进行 - 您需要为所有情况切换左右值。但至少如果你使用这个答案,你可以在 Interface Builder 中看到你的布局。之后,您需要编写匹配的代码。
是的,安迪。如果我们支持 RTL 和 LTR,我们必须通过代码来完成。但是,如果我们只更改插图,那么我们可以使用情节提要来完成,否则必须以编程方式完成。
g
gbitaudeau

我参加这个聚会也有点晚了,但我想我有一些有用的东西要补充:o)。

我创建了一个 UIButton 子类,其目的是能够选择按钮图像的布局位置,垂直或水平。

https://i.stack.imgur.com/PEtYh.png

这里是关于如何用我的类创建这些按钮的详细信息:

func makeButton (imageVerticalAlignment:LayoutableButton.VerticalAlignment, imageHorizontalAlignment:LayoutableButton.HorizontalAlignment, title:String) -> LayoutableButton {
    let button = LayoutableButton ()

    button.imageVerticalAlignment = imageVerticalAlignment
    button.imageHorizontalAlignment = imageHorizontalAlignment

    button.setTitle(title, for: .normal)

    // add image, border, ...

    return button
}

let button1 = makeButton(imageVerticalAlignment: .center, imageHorizontalAlignment: .left, title: "button1")
let button2 = makeButton(imageVerticalAlignment: .center, imageHorizontalAlignment: .right, title: "button2")
let button3 = makeButton(imageVerticalAlignment: .top, imageHorizontalAlignment: .center, title: "button3")
let button4 = makeButton(imageVerticalAlignment: .bottom, imageHorizontalAlignment: .center, title: "button4")
let button5 = makeButton(imageVerticalAlignment: .bottom, imageHorizontalAlignment: .center, title: "button5")
button5.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

为此,我添加了 2 个属性:imageVerticalAlignmentimageHorizontalAlignment。当然,如果您的按钮只有图像或标题......根本不要使用这个类!

我还添加了一个名为 imageToTitleSpacing 的属性,它允许您调整标题和图像之间的间距。

如果您想直接使用 imageEdgeInsetstitleEdgeInsetscontentEdgeInsets 或与新的布局属性结合使用,该类会尽力兼容。

正如@ravron 向我们解释的那样,我尽力使按钮内容边缘正确(如您所见的红色边框)。

您也可以在 Interface Builder 中使用它:

创建 UIButton 更改按钮类 使用“center”、“top”、“bottom”、“left”或“right”调整可布局属性

这里的代码 (gist) :

@IBDesignable
class LayoutableButton: UIButton {

    enum VerticalAlignment : String {
        case center, top, bottom, unset
    }


    enum HorizontalAlignment : String {
        case center, left, right, unset
    }


    @IBInspectable
    var imageToTitleSpacing: CGFloat = 8.0 {
        didSet {
            setNeedsLayout()
        }
    }


    var imageVerticalAlignment: VerticalAlignment = .unset {
        didSet {
            setNeedsLayout()
        }
    }

    var imageHorizontalAlignment: HorizontalAlignment = .unset {
        didSet {
            setNeedsLayout()
        }
    }

    @available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'imageVerticalAlignment' instead.")
    @IBInspectable
    var imageVerticalAlignmentName: String {
        get {
            return imageVerticalAlignment.rawValue
        }
        set {
            if let value = VerticalAlignment(rawValue: newValue) {
                imageVerticalAlignment = value
            } else {
                imageVerticalAlignment = .unset
            }
        }
    }

    @available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'imageHorizontalAlignment' instead.")
    @IBInspectable
    var imageHorizontalAlignmentName: String {
        get {
            return imageHorizontalAlignment.rawValue
        }
        set {
            if let value = HorizontalAlignment(rawValue: newValue) {
                imageHorizontalAlignment = value
            } else {
                imageHorizontalAlignment = .unset
            }
        }
    }

    var extraContentEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

    override var contentEdgeInsets: UIEdgeInsets {
        get {
            return super.contentEdgeInsets
        }
        set {
            super.contentEdgeInsets = newValue
            self.extraContentEdgeInsets = newValue
        }
    }

    var extraImageEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

    override var imageEdgeInsets: UIEdgeInsets {
        get {
            return super.imageEdgeInsets
        }
        set {
            super.imageEdgeInsets = newValue
            self.extraImageEdgeInsets = newValue
        }
    }

    var extraTitleEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

    override var titleEdgeInsets: UIEdgeInsets {
        get {
            return super.titleEdgeInsets
        }
        set {
            super.titleEdgeInsets = newValue
            self.extraTitleEdgeInsets = newValue
        }
    }

    //Needed to avoid IB crash during autolayout
    override init(frame: CGRect) {
        super.init(frame: frame)
    }


    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        self.imageEdgeInsets = super.imageEdgeInsets
        self.titleEdgeInsets = super.titleEdgeInsets
        self.contentEdgeInsets = super.contentEdgeInsets
    }

    override func layoutSubviews() {
        if let imageSize = self.imageView?.image?.size,
            let font = self.titleLabel?.font,
            let textSize = self.titleLabel?.attributedText?.size() ?? self.titleLabel?.text?.size(attributes: [NSFontAttributeName: font]) {

            var _imageEdgeInsets = UIEdgeInsets.zero
            var _titleEdgeInsets = UIEdgeInsets.zero
            var _contentEdgeInsets = UIEdgeInsets.zero

            let halfImageToTitleSpacing = imageToTitleSpacing / 2.0

            switch imageVerticalAlignment {
            case .bottom:
                _imageEdgeInsets.top = (textSize.height + imageToTitleSpacing) / 2.0
                _imageEdgeInsets.bottom = (-textSize.height - imageToTitleSpacing) / 2.0
                _titleEdgeInsets.top = (-imageSize.height - imageToTitleSpacing) / 2.0
                _titleEdgeInsets.bottom = (imageSize.height + imageToTitleSpacing) / 2.0
                _contentEdgeInsets.top = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                _contentEdgeInsets.bottom = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                //only works with contentVerticalAlignment = .center
                contentVerticalAlignment = .center
            case .top:
                _imageEdgeInsets.top = (-textSize.height - imageToTitleSpacing) / 2.0
                _imageEdgeInsets.bottom = (textSize.height + imageToTitleSpacing) / 2.0
                _titleEdgeInsets.top = (imageSize.height + imageToTitleSpacing) / 2.0
                _titleEdgeInsets.bottom = (-imageSize.height - imageToTitleSpacing) / 2.0
                _contentEdgeInsets.top = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                _contentEdgeInsets.bottom = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                //only works with contentVerticalAlignment = .center
                contentVerticalAlignment = .center
            case .center:
                //only works with contentVerticalAlignment = .center
                contentVerticalAlignment = .center
                break
            case .unset:
                break
            }

            switch imageHorizontalAlignment {
            case .left:
                _imageEdgeInsets.left = -halfImageToTitleSpacing
                _imageEdgeInsets.right = halfImageToTitleSpacing
                _titleEdgeInsets.left = halfImageToTitleSpacing
                _titleEdgeInsets.right = -halfImageToTitleSpacing
                _contentEdgeInsets.left = halfImageToTitleSpacing
                _contentEdgeInsets.right = halfImageToTitleSpacing
            case .right:
                _imageEdgeInsets.left = textSize.width + halfImageToTitleSpacing
                _imageEdgeInsets.right = -textSize.width - halfImageToTitleSpacing
                _titleEdgeInsets.left = -imageSize.width - halfImageToTitleSpacing
                _titleEdgeInsets.right = imageSize.width + halfImageToTitleSpacing
                _contentEdgeInsets.left = halfImageToTitleSpacing
                _contentEdgeInsets.right = halfImageToTitleSpacing
            case .center:
                _imageEdgeInsets.left = textSize.width / 2.0
                _imageEdgeInsets.right = -textSize.width / 2.0
                _titleEdgeInsets.left = -imageSize.width / 2.0
                _titleEdgeInsets.right = imageSize.width / 2.0
                _contentEdgeInsets.left = -((imageSize.width + textSize.width) - max (imageSize.width, textSize.width)) / 2.0
                _contentEdgeInsets.right = -((imageSize.width + textSize.width) - max (imageSize.width, textSize.width)) / 2.0
            case .unset:
                break
            }

            _contentEdgeInsets.top += extraContentEdgeInsets.top
            _contentEdgeInsets.bottom += extraContentEdgeInsets.bottom
            _contentEdgeInsets.left += extraContentEdgeInsets.left
            _contentEdgeInsets.right += extraContentEdgeInsets.right

            _imageEdgeInsets.top += extraImageEdgeInsets.top
            _imageEdgeInsets.bottom += extraImageEdgeInsets.bottom
            _imageEdgeInsets.left += extraImageEdgeInsets.left
            _imageEdgeInsets.right += extraImageEdgeInsets.right

            _titleEdgeInsets.top += extraTitleEdgeInsets.top
            _titleEdgeInsets.bottom += extraTitleEdgeInsets.bottom
            _titleEdgeInsets.left += extraTitleEdgeInsets.left
            _titleEdgeInsets.right += extraTitleEdgeInsets.right

            super.imageEdgeInsets = _imageEdgeInsets
            super.titleEdgeInsets = _titleEdgeInsets
            super.contentEdgeInsets = _contentEdgeInsets

        } else {
            super.imageEdgeInsets = extraImageEdgeInsets
            super.titleEdgeInsets = extraTitleEdgeInsets
            super.contentEdgeInsets = extraContentEdgeInsets
        }

        super.layoutSubviews()
    }
}

我修复了一些东西,不要用 error: IB Designables: Failed to update auto layout status: The agent crashedgist.github.com/nebiros/ecf69ff9cb90568edde071386c6c4ddb 破坏 IB
@nebiros 您能解释一下出了什么问题以及如何解决它吗?
@gbitaudeau 当我复制和粘贴脚本时,我得到了那个错误,error: IB Designables: Failed to update auto layout status: The agent crashed,因为 init(frame: CGRect) 没有被覆盖,另外,我添加了 @available 注释......,如果你愿意,你可以 diff -Naur,;-)
H
Hemang

斯威夫特 4.x

extension UIButton {
    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        let writingDirection = UIApplication.shared.userInterfaceLayoutDirection
        let factor: CGFloat = writingDirection == .leftToRight ? 1 : -1

        self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount*factor, bottom: 0, right: insetAmount*factor)
        self.titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount*factor, bottom: 0, right: -insetAmount*factor)
        self.contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
    }
}

用法:

button.centerTextAndImage(spacing: 10.0)

如何使用自定义图像尺寸?
y
yagnik suthar

我将根据更新后的 Xcode 13 更新答案。

用于图像和文本对齐。我们不必使用任何扩展或一行代码。 Xcode 在属性选项卡中提供预定义属性,如下图所示。

展示位置属性有 4 个图像属性 - Top, Bottom, Leading, and Trailing

https://i.stack.imgur.com/8SX2L.png


该配置仅适用于 IOS 15 及更高版本,请参阅我的答案以了解向后兼容性stackoverflow.com/a/70138193/1710571
o
orxelm

Riley Avron 对帐户区域设置更改的一个小补充:

extension UIButton {
    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        let writingDirection = UIApplication.sharedApplication().userInterfaceLayoutDirection
        let factor: CGFloat = writingDirection == .LeftToRight ? 1 : -1

        self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount*factor, bottom: 0, right: insetAmount*factor)
        self.titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount*factor, bottom: 0, right: -insetAmount*factor)
        self.contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
    }
}

H
HotJard

界面构建器解决方案

情况正在发生变化,现在使用 XCode 13.1 并且对于 iOS 15+,Size inspector 不会影响插入,而是在 Attribute inspector 下有 PaddingContent insets 属性,它们会带来预期的效果

https://i.stack.imgur.com/g2KZj.png

正如@ravron 所说,为了向后兼容,需要在 Size inspector 下插入。在IB中需要做一些组合:

假设我们希望图像和标题之间的距离为 8pt 添加标题左插图为 8 pt 这将从右侧剪切文本,因此需要与为标题添加 -8pt 右插图平衡,然后按钮的右插图也需要调整增加右插图 8pt 完成!该按钮在 iOS 14 和 15 上看起来很棒

https://i.stack.imgur.com/uo4IM.png


完全忘记了负面插图,我把头撞在墙上,想知道为什么文字被剪掉了。谢谢!
p
pableiros

iOS 15+ 中,您可以使用 UIButton.Configuration

var configuration = button.configuration
configuration?.imagePadding = 16
configuration?.titlePadding = 10
            
button.configuration = configuration

在看到你的答案之前,我几乎发布了这个。现在使用 ravron 的出色答案会导致弃用通知:'imageEdgeInsets' was deprecated in iOS 15.0: This property is ignored when using UIButtonConfiguration
J
Jules

我在下面写代码。它在产品版本中运行良好。支持 Swift 4.2 +

extension UIButton{
 enum ImageTitleRelativeLocation {
    case imageUpTitleDown
    case imageDownTitleUp
    case imageLeftTitleRight
    case imageRightTitleLeft
}
 func centerContentRelativeLocation(_ relativeLocation: 
                                      ImageTitleRelativeLocation,
                                   spacing: CGFloat = 0) {
    assert(contentVerticalAlignment == .center,
           "only works with contentVerticalAlignment = .center !!!")

    guard (title(for: .normal) != nil) || (attributedTitle(for: .normal) != nil) else {
        assert(false, "TITLE IS NIL! SET TITTLE FIRST!")
        return
    }

    guard let imageSize = self.currentImage?.size else {
        assert(false, "IMGAGE IS NIL! SET IMAGE FIRST!!!")
        return
    }
    guard let titleSize = titleLabel?
        .systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) else {
            assert(false, "TITLELABEL IS NIL!")
            return
    }

    let horizontalResistent: CGFloat
    // extend contenArea in case of title is shrink
    if frame.width < titleSize.width + imageSize.width {
        horizontalResistent = titleSize.width + imageSize.width - frame.width
        print("horizontalResistent", horizontalResistent)
    } else {
        horizontalResistent = 0
    }

    var adjustImageEdgeInsets: UIEdgeInsets = .zero
    var adjustTitleEdgeInsets: UIEdgeInsets = .zero
    var adjustContentEdgeInsets: UIEdgeInsets = .zero

    let verticalImageAbsOffset = abs((titleSize.height + spacing) / 2)
    let verticalTitleAbsOffset = abs((imageSize.height + spacing) / 2)

    switch relativeLocation {
    case .imageUpTitleDown:

        adjustImageEdgeInsets.top = -verticalImageAbsOffset
        adjustImageEdgeInsets.bottom = verticalImageAbsOffset
        adjustImageEdgeInsets.left = titleSize.width / 2 + horizontalResistent / 2
        adjustImageEdgeInsets.right = -titleSize.width / 2 - horizontalResistent / 2

        adjustTitleEdgeInsets.top = verticalTitleAbsOffset
        adjustTitleEdgeInsets.bottom = -verticalTitleAbsOffset
        adjustTitleEdgeInsets.left = -imageSize.width / 2 + horizontalResistent / 2
        adjustTitleEdgeInsets.right = imageSize.width / 2 - horizontalResistent / 2

        adjustContentEdgeInsets.top = spacing
        adjustContentEdgeInsets.bottom = spacing
        adjustContentEdgeInsets.left = -horizontalResistent
        adjustContentEdgeInsets.right = -horizontalResistent
    case .imageDownTitleUp:
        adjustImageEdgeInsets.top = verticalImageAbsOffset
        adjustImageEdgeInsets.bottom = -verticalImageAbsOffset
        adjustImageEdgeInsets.left = titleSize.width / 2 + horizontalResistent / 2
        adjustImageEdgeInsets.right = -titleSize.width / 2 - horizontalResistent / 2

        adjustTitleEdgeInsets.top = -verticalTitleAbsOffset
        adjustTitleEdgeInsets.bottom = verticalTitleAbsOffset
        adjustTitleEdgeInsets.left = -imageSize.width / 2 + horizontalResistent / 2
        adjustTitleEdgeInsets.right = imageSize.width / 2 - horizontalResistent / 2

        adjustContentEdgeInsets.top = spacing
        adjustContentEdgeInsets.bottom = spacing
        adjustContentEdgeInsets.left = -horizontalResistent
        adjustContentEdgeInsets.right = -horizontalResistent
    case .imageLeftTitleRight:
        adjustImageEdgeInsets.left = -spacing / 2
        adjustImageEdgeInsets.right = spacing / 2

        adjustTitleEdgeInsets.left = spacing / 2
        adjustTitleEdgeInsets.right = -spacing / 2

        adjustContentEdgeInsets.left = spacing
        adjustContentEdgeInsets.right = spacing
    case .imageRightTitleLeft:
        adjustImageEdgeInsets.left = titleSize.width + spacing / 2
        adjustImageEdgeInsets.right = -titleSize.width - spacing / 2

        adjustTitleEdgeInsets.left = -imageSize.width - spacing / 2
        adjustTitleEdgeInsets.right = imageSize.width + spacing / 2

        adjustContentEdgeInsets.left = spacing
        adjustContentEdgeInsets.right = spacing
    }

    imageEdgeInsets = adjustImageEdgeInsets
    titleEdgeInsets = adjustTitleEdgeInsets
    contentEdgeInsets = adjustContentEdgeInsets

    setNeedsLayout()
}
}

E
Elijah

这是一个如何使用 imageEdgeInsets 的简单示例 这将使一个 30x30 按钮的可点击区域变大 10 像素(50x50)

    var expandHittableAreaAmt : CGFloat = 10
    var buttonWidth : CGFloat = 30
    var button = UIButton.buttonWithType(UIButtonType.Custom) as UIButton
    button.frame = CGRectMake(0, 0, buttonWidth+expandHittableAreaAmt, buttonWidth+expandHittableAreaAmt)
    button.imageEdgeInsets = UIEdgeInsetsMake(expandHittableAreaAmt, expandHittableAreaAmt, expandHittableAreaAmt, expandHittableAreaAmt)
    button.setImage(UIImage(named: "buttonImage"), forState: .Normal)
    button.addTarget(self, action: "didTouchButton:", forControlEvents:.TouchUpInside)

R
Reimond Hill

在 swift 5.3 中并受@ravron 的启发回答:

extension UIButton {
    /// Fits the image and text content with a given spacing
    /// - Parameters:
    ///   - spacing: Spacing between the Image and the text
    ///   - contentXInset: The spacing between the view to the left image and the right text to the view
    func setHorizontalMargins(imageTextSpacing: CGFloat, contentXInset: CGFloat = 0) {
        let imageTextSpacing = imageTextSpacing / 2
        
        contentEdgeInsets = UIEdgeInsets(top: 0, left: (imageTextSpacing + contentXInset), bottom: 0, right: (imageTextSpacing + contentXInset))
        imageEdgeInsets = UIEdgeInsets(top: 0, left: -imageTextSpacing, bottom: 0, right: imageTextSpacing)
        titleEdgeInsets = UIEdgeInsets(top: 0, left: imageTextSpacing, bottom: 0, right: -imageTextSpacing)
    }
}

它增加了从 View 到 Image 以及从 Label 到 View 的额外水平边距


l
lukess

Swift 3 中的一种优雅方式,更易于理解:

override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
    let leftMargin:CGFloat = 40
    let imgWidth:CGFloat = 24
    let imgHeight:CGFloat = 24
    return CGRect(x: leftMargin, y: (contentRect.size.height-imgHeight) * 0.5, width: imgWidth, height: imgHeight)
}

override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
    let leftMargin:CGFloat = 80
    let rightMargin:CGFloat = 80
    return CGRect(x: leftMargin, y: 0, width: contentRect.size.width-leftMargin-rightMargin, height: contentRect.size.height)
}
override func backgroundRect(forBounds bounds: CGRect) -> CGRect {
    let leftMargin:CGFloat = 10
    let rightMargin:CGFloat = 10
    let topMargin:CGFloat = 10
    let bottomMargin:CGFloat = 10
    return CGRect(x: leftMargin, y: topMargin, width: bounds.size.width-leftMargin-rightMargin, height: bounds.size.height-topMargin-bottomMargin)
}
override func contentRect(forBounds bounds: CGRect) -> CGRect {
    let leftMargin:CGFloat = 5
    let rightMargin:CGFloat = 5
    let topMargin:CGFloat = 5
    let bottomMargin:CGFloat = 5
    return CGRect(x: leftMargin, y: topMargin, width: bounds.size.width-leftMargin-rightMargin, height: bounds.size.height-topMargin-bottomMargin)
}

A
Alexey Barton

我的垂直居中方法:

extension UIButton {
    
    /// Layout image and title with vertical centering.
    /// - Parameters:
    ///   - size: The button size.
    ///   - imageTopOffset: Top offset for image.
    ///   - spacing: Distance between image and title.
    func verticalAlignmentByCenter(size: CGSize, imageTopOffset: CGFloat, spacing: CGFloat) {
        let contentRect = contentRect(forBounds: CGRect(origin: .zero, size: size))
        let imageRect = imageRect(forContentRect: contentRect)
        let titleRect = titleRect(forContentRect: contentRect)

        let imageTop = imageTopOffset - imageRect.origin.y
        let imageLeft = contentRect.width/2 - imageRect.width/2
        imageEdgeInsets = UIEdgeInsets(top: imageTop, left: imageLeft, bottom: 0, right: 0)

        let titleTop = imageTopOffset + spacing + imageRect.height - titleRect.origin.y
        let titleLeft = titleRect.origin.x - contentRect.width/2 - titleRect.width/2
        titleEdgeInsets = UIEdgeInsets(top: titleTop, left: titleLeft, bottom: 0, right: 0)
    }
    
}

O
OhadM

@ravron 在提供这个答案方面做得非常出色。

就我而言,我不仅需要在图像和标题之间添加水平宽度,还需要在按钮的“前导”和“尾随”中添加水平空间。

因此,我使用了内部图像和标签的 intrinsicContentSize:

接收视图的自然大小,仅考虑视图本身的属性。

|                                                                                   |
|[LEADING SPACE] [Image] [SPACE BETWEEN IMAGE AND TITLE] Add To     [TRAILING SPACE]|
|                                                        Favorites      |

let leadingTrailingSpace = 10
let horizontalWidthBetweenImageAndTitle = 4
let insetAmount = horizontalWidthBetweenImageAndTitle / CGFloat(2)
    button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -CGFloat(insetAmount), bottom: 0, right: insetAmount);
    button.titleEdgeInsets = UIEdgeInsets(top: 0, left: CGFloat(insetAmount), bottom: 0, right: -insetAmount);
    button.contentEdgeInsets = UIEdgeInsets(top: 0, left: CGFloat(insetAmount), bottom: 0, right: insetAmount);
    
    let buttonWidth =
        (button.titleLabel?.intrinsicContentSize.width ?? 0) +
        (button.imageView?.intrinsicContentSize.width ?? 0)
        + insetAmount
        + leadingTrailingSpace
    button.widthAnchor.constraint(equalToConstant: buttonWidth).isActive = true

S
Soheil Novinfard

swift 4.2 版本的解决方案如下:

let spacing: CGFloat = 10 // the amount of spacing to appear between image and title
self.button?.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: spacing)
self.button?.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: 0)