将UIButton上的文本和图像与imageEdgeInsets和titleEdgeInsets对齐

Jus*_*zic 239 iphone uibutton uikit ios

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

该按钮类似于以下内容:

|                  |
|[Image] Add To    |
|        Favorites |
Run Code Online (Sandbox Code Playgroud)

我试图用contentEdgeInset,imageEdgeInsets和titleEdgeInsets配置它无济于事.我知道负值会扩大边缘,而正值会缩小它以使其更接近中心.

我试过了:

[button setTitleEdgeInsets:UIEdgeInsetsMake(0, -image.size.width, 0, 0)];
[button setImageEdgeInsets:UIEdgeInsetsMake(0, button.titleLabel.bounds.size.width, 0, 0)];
Run Code Online (Sandbox Code Playgroud)

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

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

我用这个SO问题作为参考,但关于我的价值观的事情是不对的. UIButton:如何使用imageEdgeInsets和titleEdgeInsets使图像和文本居中?

Kek*_*koa 371

我同意文档imageEdgeInsets并且titleEdgeInsets应该更好,但我想出了如何在不诉诸试错的情况下获得正确的定位.

一般的想法就是在这个问题上,但那就是你想要文本和图像都集中在一起.我们不希望图像和文本单独居中,我们希望图像和文本作为单个实体集中在一起.这实际上是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);
Run Code Online (Sandbox Code Playgroud)

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

的UIButton + Position.h

@interface UIButton(ImageTitleCentering)

-(void) centerButtonAndImageWithSpacing:(CGFloat)spacing;

@end
Run Code Online (Sandbox Code Playgroud)

的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
Run Code Online (Sandbox Code Playgroud)

所以现在我所要做的就是:

[button centerButtonAndImageWithSpacing:10];
Run Code Online (Sandbox Code Playgroud)

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

编辑:交换图像和文本

回应@Javal的评论

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

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

当然你可能想要为此做一个很好的方法,可能会添加第二种方法,这留给读者一个练习.

  • 你如何修复[button sizeToFit]以使其大小合适? (5认同)

rav*_*ron 371

我参加这个派对有点晚了,但我觉得我有一些有用的东西要加上.

Kekoa的答案很棒但是,正如RonLugge所提到的,它可以使按钮不再sizeToFit受到尊重,或者更重要的是,它可以使按钮在内在大小时剪切其内容.哎呀!

不过,首先,

简要解释我的信仰imageEdgeInsetstitleEdgeInsets工作方式:

对文档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
Run Code Online (Sandbox Code Playgroud)

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

间距很好,但图像和标题位于视图的框架之外.

哦耶!我忘记了,文档警告我这件事.他们说,部分:

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

但有一个属性,可以帮助,这就是contentEdgeInsets.这方面的文档部分地说:

该按钮使用此属性来确定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
Run Code Online (Sandbox Code Playgroud)

而你得到了什么?

间距和框架现在是正确的.

看起来像是我的赢家.


在斯威夫特工作,根本不想做任何想法?这是Swift中扩展的最终版本:

extension UIButton {
    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        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)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 真是个好消息!是的,晚了几年,但这解决了'intrinsicContentSize`不正确的问题,这在自原始答案被接受后的这些自动布局时代非常重要. (24认同)
  • 而且,如果您希望按钮外部与图片和标签之间的间距相等,则可以在self.contentEdgeInsets的四个值中分别添加“ spacing”,例如:self.contentEdgeInsets = UIEdgeInsetsMake(spacing,interval + insetAmount,space,space + insetAmount);` (2认同)
  • 几乎完美的答案!只有缺少的是图像和标题的插图在从右到左界面运行时必须反转. (2认同)

Fre*_*man 39

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


gbk*_*gbk 35

如果你想做类似的东西

在此输入图像描述

你需要

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)];
    
    Run Code Online (Sandbox Code Playgroud)

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

另请注意每次重播时更新此信息


迅速

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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Nis*_*ant 29

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

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
Run Code Online (Sandbox Code Playgroud)

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

斯威夫特3:

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center;
Run Code Online (Sandbox Code Playgroud)


Sah*_*hil 22

Xcode 8.0中,您可以通过更改insets尺寸检查器来完成.

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

在此输入图像描述

如果要更改右侧的图像,只需将语义属性更改为Force Right-to-left"属性"检查器即可.

在此输入图像描述


gbi*_*eau 18

我也有点迟到这个派对,但我想我有一些有用的补充: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)
Run Code Online (Sandbox Code Playgroud)

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

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

这个类想尽办法是兼容的,如果你想使用imageEdgeInsets,titleEdgeInsetscontentEdgeInsets直接或combinaison的新布局的属性.

正如@ravron解释我们的那样,我尽力使按钮内容边缘正确(正如您可以看到红色边框).

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

  1. 创建一个UIButton
  2. 更改按钮类
  3. 使用"中心","顶部","底部","左侧"或"右侧"调整可布局属性 按钮属性

这里的代码(要点):

@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()
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 12

我将根据更新的内容更新答案Xcode 13

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

放置属性有 4 个图像属性 -Top, Bottom, Leading, and Trailing

XCode 13 - 属性选项卡


orx*_*elm 9

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)
    }
}
Run Code Online (Sandbox Code Playgroud)


Hot*_*ard 7

界面生成器解决方案

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

在此输入图像描述

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

  1. 假设我们希望图像和标题之间的距离为 8pt
  2. 添加标题左插图为 8 pt
  3. 这将从右侧剪切文本,因此需要通过为标题添加 -8pt 右侧插图来平衡
  4. 那么按钮的右内嵌也需要调整增加右内嵌8pt
  5. 完毕!该按钮非常适合 iOS 14 和 15

在此输入图像描述


Hem*_*ang 6

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

button.centerTextAndImage(spacing: 10.0)
Run Code Online (Sandbox Code Playgroud)