自动布局 - UIButton 的内在大小不包括标题插图

229

如果我使用自动布局排列一个UIButton,它会根据其内容很好地调整大小。

如果我将一张图片设置为button.image,则内在大小似乎也会考虑到这一点。

但是,如果我调整按钮的titleEdgeInsets,布局就不会考虑这个因素,而是截断按钮标题。

我该如何确保按钮的内在宽度考虑到了插入?

enter image description here

编辑:

我使用以下内容:

[self.backButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 5, 0, 0)];

目标是在图像和文字之间添加一些分隔。


3
你是否将此问题提交为 Radar?这显然是 UIButton 内在大小计算中的一个 bug。 - Ryan Poolos
1
我本来准备提交一个radar,但这似乎是一种预期的行为。在UIButton的*EdgeInsets属性中有记录:“您指定的插图将应用于标题矩形,在该矩形已被调整大小以适应按钮文本之后。因此,正插入值实际上可能会剪切标题文本。[...]按钮不使用此属性来确定intrinsicContentSize和sizeThatFits:。” - Guillaume Algis
8
我认为,尽管这是一种陈述型行为,但这绝对不是使用自动布局时人们所期望发生的事情。我已经提交了一个错误报告,并鼓励其他人也提交一个。 - memmons
如果您能在此处链接雷达错误,我们可以点击它并+1吗? - gprasant
1
titleEdgeInset 文档中翻译:在调整按钮文本大小后,指定的插图将应用于标题矩形。因此,正插图值可能会剪裁标题文本。因此,通过添加插图,您确实强制按钮剪切文本。 - Marco Pappalardo
我建议不要用按钮来处理 UI,而是采用 UIView 与 UILabel 的组合,再在顶部加一个透明的按钮,这样会更灵活多变。 - amar
11个回答

235
您可以在Interface Builder中(无需编写任何代码)使用负和正标题和内容插入的组合来使其工作。如下图所示:

图片描述

更新: Xcode 7存在一个bug,您无法在插入字段中输入负值,但是您可以使用旁边的步进控件来减小该值。(感谢Stuart) 这样做将在图像和标题之间添加8pt的间距,并将按钮的内在宽度增加相同数量。如下图所示:

图片描述


2
它使用contentEdgeInsets(不会出现错误)来让自动布局增加按钮宽度。并将标签移动到右侧的空白处。聪明地解决了标题边缘插入错误的问题。 - ugur
7
这个技巧不再适用。Interface Builder不再接受“右”字段中的负值。 - Joris Mans
8
您无法在输入框中键入负值,但是我通过使用文本框右侧的步进控件向下步进到所需的负值,成功实现了这一点...想不到吧! - Stuart
3
这应该是第一个答案,为什么它在这里?在找到这个之前,我试过其他5个... - Lord Zsolt
2
我将内容插入的右边距设置为16,以使UIButton中的文本居中。 - coolcool1994
显示剩余5条评论

205

您无需覆盖任何方法或设置任意宽度约束即可解决此问题。 您可以在Interface Builder中完成所有操作,具体如下:

  • 按钮的内在宽度是由标题宽度加上图标宽度再加上左右两侧的内容边框偏移量推导出来的。

  • 如果一个按钮既有图像又有文本,则它们作为一组居中显示,之间没有填充。

  • 如果添加了左侧内容插图,则它是相对于文本而不是文本 + 图标计算的。

  • 如果设置负的左侧图像插图,则图像向左拉出,但整个按钮的宽度不受影响。

  • 如果设置负的左侧图像插图,则实际布局会使用该值的一半。因此,要获得-20点左插图偏移量,您必须在Interface Builder中使用-40点的左插图偏移量。

因此,您可以提供足够大的左侧内容插图,以创建所需的左插图偏移量和图标与文本之间的内部填充之间的空间,然后将图标向左移动,使其所需的填充量加倍。结果是一个左右内容插图相等的按钮,以及作为一组居中显示的文本和图标对,它们之间有特定的填充量。

以下是一些示例值:

// Produces a button with the layout:
// |-20-icon-10-text-20-|
// AutoLayout intrinsic width works as you'd desire.
button.contentEdgeInsets = UIEdgeInsetsMake(10, 30, 10, 20)
button.imageEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0)

为什么实际布局使用了负左插入值的一半?我也遇到了同样的问题! - Tony Lin
1
很好,有一个解决方法,但我希望这不会被用来为UIButton的奇怪行为辩护。 - funct7

103

为什么不重写UIView上的 intrinsicContentSize 方法?例如:

- (CGSize) intrinsicContentSize
{
    CGSize s = [super intrinsicContentSize];

    return CGSizeMake(s.width + self.titleEdgeInsets.left + self.titleEdgeInsets.right,
                      s.height + self.titleEdgeInsets.top + self.titleEdgeInsets.bottom);
}

这应该告诉自动布局系统应该增加按钮的大小以适应插图并显示完整文本。我不在自己的电脑上,所以我还没有测试过。


1
据我所知,按钮不应该被覆盖。问题在于每种按钮类型都由不同的子类实现。 - Sulthan
2
intrinsicContentSize 是 UIView 上的一个方法,而不是 UIButton 上的,因此您不会干扰任何 UIButton 方法。苹果公司认为这不是问题:“重写此方法允许自定义视图向布局系统通信,根据其内容确定其想要的大小。” OP 没有提到不同的按钮,只是一个按钮。 - Maarten
1
这绝对可行,也是我采用的解决方案。intrinsicContentSize确实是UIView上的一个方法,而UIButton是UIView的子类,因此你当然可以重写这个方法;苹果文档中没有任何说明说你不应该这样做。只需使用Maarten的重写方法创建一个UIButton子类,并在Interface Builder中将你的UIButton更改为YourUIButtonSubclass类型,它就能完美地工作了。 - n8tr
38
我认为UIButton的intrinsicContentSize应该包括titleEdgeInsets,我打算向Apple提交一个错误报告。 - progrmr
6
我同意,对于imageEdgeInsets也是一样。 - Ricardo Sanchez-Saez
显示剩余2条评论

96

你没有说明如何设置插入,所以我猜你正在使用titleEdgeInsets,因为我看到了你得到的相同效果。如果我改用contentEdgeInsets,则可以正常工作。

- (IBAction)ChangeTitle:(UIButton *)sender {
    self.button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
    [self.button setTitle:@"Long Long Title" forState:UIControlStateNormal];
}

我确实在使用titleEdgeInsets。我需要将标题与图像分开,而不是将图像与按钮边缘分开。也许我应该只是使用一张带有一些填充的图像?虽然这似乎有些取巧。 - Ben Packard
3
这是更好的解决方案,因为它可以完全满足您的需求,而不会涉及 intrinsicContentSize。 - RyJ
30
这并没有回答当使用图像并需要调整图像与标题之间的插入时的问题! - Brody Robertson

26

而对于Swift,它的工作方式如下:

extension UIButton {
    override open var intrinsicContentSize: CGSize {
        let intrinsicContentSize = super.intrinsicContentSize

        let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
        let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom

        return CGSize(width: adjustedWidth, height: adjustedHeight)
    }
}

爱你的 Swift


3
虽然你不应该这样做,但在这种情况下最好是进行子类化。因为 Apple 的文档明确表示内在尺寸不包括 titleEdgeInsets 在内的计算,所以使用扩展会违反不仅是 Apple 的预期,还有其他所有阅读文档的开发者的预期。 - Allison
1
在扩展中进行覆盖是不受支持的,并且会导致未定义的运行时行为。请参阅:https://dev59.com/dFoT5IYBdhLWcg3wvRlk#38274660 - Nathan Hosselton

18

这个帖子有点旧了,但我最近也遇到了这个问题,并且通过使用负的插入来解决了它。例如,在这里替换您所需的填充值:

UIButton* myButton = [[UIButton alloc] init];
// setup some autolayout constraints here
myButton.titleEdgeInsets = UIEdgeInsetsMake(-desiredBottomPadding,
                                            -desiredRightPadding,
                                            -desiredTopPadding,
                                            -desiredLeftPadding);

与正确的自动布局约束相结合,您最终会得到一个包含图像和文本的自动调整大小的按钮!如下所示,其中 desiredLeftPadding 设置为 10。

带有图像和短文本的按钮

带有图像和长文本的按钮

您可以看到,按钮的实际框架不包括标签(因为标签向右移动了10个点,超出了边界),但我们已经在文本和图片之间实现了10个点的填充。


1
这是我使用的解决方案,因为它不需要子类化。如果您的按钮有背景,则无法工作,但在iOS 7中通常不是问题。 - José Manuel Sánchez
如果您还设置了按钮的内容偏移量(正值> =标题插图),则此方法也适用于带有背景图像的按钮。 - Ben Flynn

9

我想在我的UIButton图标和标签之间添加5pt的空格。我通过以下方法实现了这一点:

UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
// more button config etc
infoButton.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 5);
infoButton.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, -5);

contentEdgeInsets,titleEdgeInsets和imageEdgeInsets之间的关系需要通过每个插图的微调来协调。因此,如果您在标题的左侧添加了一些插图,则必须在右侧添加负插图,并通过在内容右侧提供一些额外空间(通过正插图)。

通过添加右侧内容插图以匹配标题插图的移动,我的文本不会超出按钮的边界。


8

以下是基于 pegpeg 的回答,适用于 Swift 3

extension UIButton {

    override open var intrinsicContentSize: CGSize {

        let intrinsicContentSize = super.intrinsicContentSize

        let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
        let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom

        return CGSize(width: adjustedWidth, height: adjustedHeight)

    }

}

你好。我想在Interface Builder中使用自定义扩展按钮。请帮忙。 - kemdo

7

以上方法都无法在iOS 9+上使用,我的做法是:

  • 添加宽度约束(当按钮没有任何文本时,设置最小宽度。如果提供了文本,按钮将自动缩放)
  • 将关系设置为大于或等于

enter image description here

现在要在按钮周围添加边框,只需使用以下方法:

button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);

为什么不呢?它可以自动缩放内容,您只需要设置一个最小宽度(可以比要显示的文本更小)。 - Oritm
因为您定义了最小宽度。自动布局的整个理念是在不设置任何显式(最小)宽度的情况下完成它。 - Joris Mans
不是关于宽度的问题,如果您喜欢,可以将宽度设置为1,但自动布局需要知道宽度可以相等或更大。我更新了我的答案。 - Oritm
你根本不需要宽度约束,contentEdgeInset 才是关键,自动布局会使用它来计算内在内容大小。 - Chris Conover

3

这个选项也可以在界面构建器中找到。看看插入。我把左右设置为3。完美地工作。

界面构建器截图


1
是的,正如这个答案所解释的那样,它能够工作的原因是因为你在这里调整的是边缘:内容而不是边缘:标题边缘:图像 - smileyborg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接