【问题标题】:Aligning text and image on UIButton with imageEdgeInsets and titleEdgeInsets使用 imageEdgeInsets 和 titleEdgeInsets 对齐 UIButton 上的文本和图像
【发布时间】:2011-06-01 15:59:35
【问题描述】:

我想在两行文本的左侧放置一个图标,使图像和文本开头之间有大约 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 问题作为参考,但我的价值观有些问题。 UIButton: how to center an image and a text using imageEdgeInsets and titleEdgeInsets?

【问题讨论】:

    标签: iphone ios uibutton uikit


    【解决方案1】:

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

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

    首先,

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

    docs for imageEdgeInsets 部分内容如下:

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

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

    好的,那又怎样?

    我来了!这是你默认的,设置图像和标题(按钮边框是绿色的,只是为了显示它的位置):

    当您希望图像和标题之间有间距时,不会导致任何一个被压碎,您需要设置四个不同的插图,每个图像和标题两个。那是因为您不想更改这些元素框架的 大小,而只想更改它们的位置。当您开始以这种方式思考时,对 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
    

    但是等等,你说,当我这样做时,我明白了:

    哦,是的!我忘了,the docs 警告过我这一点。他们说,部分:

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

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

    按钮使用此属性来确定intrinsicContentSizesizeThatFits:

    听起来不错。所以让我们再次调整类别:

    @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
    

    你得到了什么?

    在我看来是个赢家。


    在 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 的四个值中,如下所示:@987654344 @
    • 很好的答案,可惜当图像右对齐并且文本长度可以变化时它不能很好地工作。
    • 几乎完美的答案!唯一不足的是,在 Right-to-Left 界面上运行时,图像和标题的插图必须反转。
    • @YestayMuratov 设置button.imageView?.contentMode = .scaleAspectFit。我为此制作了一个小测试应用程序,您可以使用github.com/tomas789/UIButtonEdgeInsets
    【解决方案2】:

    我同意 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+Position.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];
    

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

    编辑:交换图像和文本

    响应 cmets 中的@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。
    【解决方案3】:

    如果你想做类似的东西

    你需要

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

    1. 找到所有需要的值并设置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; //vertical space between image and text
      
              [button setImageEdgeInsets:UIEdgeInsetsMake((buttonSize.height - (titleSize.height + buttonImageSize.height)) / 2 - offsetBetweenImageAndText,
                                                          (buttonSize.width - buttonImageSize.width) / 2,
                                                          0,0)];                
              [button setTitleEdgeInsets:UIEdgeInsetsMake((buttonSize.height - (titleSize.height + buttonImageSize.height)) / 2 + buttonImageSize.height + offsetBetweenImageAndText,
                                                          titleSize.width + [button imageEdgeInsets].left > buttonSize.width ? -buttonImage.size.width  +  (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)
                }
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

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

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

        斯威夫特 3:

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

        【讨论】:

        • myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center; //错别字更正
        【解决方案6】:

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

        选择 UIButton -> Attributes Inspector -> 进入尺寸检查器并修改内容、图像和标题插图。

        如果您想更改右侧的图像,您只需在属性检查器中将语义属性更改为Force Right-to-left

        【讨论】:

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

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

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

        这意味着您可以制作这种按钮:

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

        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 中使用它:

        1. 创建一个 UIButton
        2. 更改按钮类
        3. 使用“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()
            }
        }
        

        【讨论】:

        • 我修复了一些东西,以免破坏 IB 与 error: IB Designables: Failed to update auto layout status: The agent crashed, gist.github.com/nebiros/ecf69ff9cb90568edde071386c6c4ddb
        • @nebiros 你能解释一下出了什么问题以及如何解决吗?
        • @gbitaudeau 当我复制并粘贴脚本时,我得到了那个错误,error: IB Designables: Failed to update auto layout status: The agent crashed,因为init(frame: CGRect) 没有被覆盖,另外,我添加了@available 注释......,你可以@987654340 @如果你愿意,;-)
        【解决方案8】:

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

        【讨论】:

        • 如何使用自定义图像尺寸?
        【解决方案9】:

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

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

        placement 属性有 4 个 Image 属性 - Top, Bottom, Leading, and Trailing

        【讨论】:

        【解决方案10】:

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

        【讨论】:

          【解决方案11】:

          我在下面编写代码。它在产品版本中运行良好。支持 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()
          }
          }
          

          【讨论】:

            【解决方案12】:

            这里有一个简单的例子来说明如何使用 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)
            

            【讨论】:

              【解决方案13】:

              在 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 添加了一个额外的水平边距

              【讨论】:

                【解决方案14】:

                接口构建器解决方案

                情况正在发生变化,现在使用 XCode 13.1 和 iOS 15+ Size inspector 不会影响插入,而是在 Attribute inspector 下有 PaddingContent insets 属性带来所需的效果

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

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

                【讨论】:

                • 完全忘记了负面插图,我把头撞在墙上想知道为什么文本被剪裁了。谢谢!
                【解决方案15】:

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

                【讨论】:

                  【解决方案16】:

                  我的垂直居中方法:

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

                  【讨论】:

                    【解决方案17】:

                    @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
                    

                    【讨论】:

                      【解决方案18】:

                      iOS 15+ 中你可以使用UIButton.Configuration:

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

                      【讨论】:

                        【解决方案19】:

                        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)
                        

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 2011-01-27
                          • 2013-03-04
                          • 1970-01-01
                          • 2012-06-12
                          • 1970-01-01
                          • 1970-01-01
                          • 2017-05-23
                          相关资源
                          最近更新 更多