IOS:使用自动布局根据标题文本调整UIButton的高度?

10
我有一个UIButton,它可以在运行时更改标题。因此,我想使用AutoLayout根据标题文本增加UIButton的高度,以便显示完整文本。我可以通过将高度约束设置为“大于或等于”来增加UILabel的高度,但是对于UIButton却无效。我已经使用[myButton sizeToFit],但它只会增加UIButon的宽度(不会增加高度)。我的当前UIButton属性如下:约束高度:30,前导:15,尾随:15,顶部:5,字体大小:12。
更新:我为UIButton的约束高度创建了一个IBOutlet,以便根据@NSNood的建议更改高度。然后我需要在标题文本中使用\n来分割行。但我不知道应该在哪里放置\n?
这是我想要的纵向按钮。

enter image description here

Here is the Button that I want in landscape enter image description here 如何确定放置 \n 的位置?
请指导我如何使用 AutoLayout 实现。任何帮助将不胜感激。

1
创建按钮高度约束的IBOutlet,并在需要时更改它的常数。例如,如果你想将高度设置为30,可以这样做:btnHeightConstraint.constant = 30 - NSNoob
1
当我只增加按钮的约束高度时,文本不会分成两行,仍然在一行中。 - Linh
如果标题能够适应按钮给定的宽度,它将不会分成两行。如果您仍然想设置多行标题,可以使用带有\n的属性字符串作为标题。 - NSNoob
如果您已经应用了适当的约束条件,那么在更大的设备上它看起来不会很糟糕。至于旋转场景,我不确定。 - NSNoob
我已经更新了我的 UIButton 约束,请查看。 - Linh
显示剩余4条评论
4个回答

11

很抱歉我最近没有遵循这篇帖子,因此现在提供一个很晚的解决方案。我仍会写出答案作为参考,如果未来有人发现它有用。

首先让我们展示按钮的故事板配置,如下图所示:

约束配置

图片显示,我只添加了按钮的左、上和右侧约束,没有其他约束。这允许按钮具有一些intrinsicContentSize高度,但其宽度仍由其左右约束确定。

下一步是编写一些ViewController类,其中包含该按钮。在我的VC中,我通过名称button创建了一个按钮的outlet:

@property(nonatomic,weak) IBOutlet UIButton* button;

并将其附加到Storyboard按钮上。现在我已经覆盖了两种方法,即viewDidLoadviewWillLayoutSubviews,如下所示:

-(void)viewDidLoad {
    [super viewDidLoad];

    self.button.titleLabel.numberOfLines = 0;
    self.button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
-(void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    [self.button setTitle:@"Chapter One\n "
     "A Stop on the Salt Route\n "
     "1000 B.C.\n "
     "As they rounded a bend in the path that ran beside the river, Lara recognized the silhouette of a fig tree atop a nearby hill. The weather was hot and the days were long. The fig tree was in full leaf, but not yet bearing fruit." forState:UIControlStateNormal];
}
  1. viewDidLoad方法确保titleLabel(保存按钮文本的标签)是多行的,如果一些大文本被输入,它会通过换行单词来换行。
  2. viewWillLayoutSubviews方法确保在主视图边界改变时(例如由于接口方向的更改),按钮布局过程发生。

最后并且最有效的部分是手动处理按钮的布局过程。为此,我们需要从UIButton进行子类化。我已经编写了一个名为MyButton的子类,继承自UIButton,您可以使用任何您喜欢的名称。在Identity Inspector中将其设置为按钮的自定义类。

这个子类覆盖了两个方法,即intrinsicContentSizelayoutSubviews。该类主体看起来像以下内容:

#import "MyButton.h"

@implementation MyButton

-(CGSize)intrinsicContentSize {
    return [self.titleLabel sizeThatFits:CGSizeMake(self.titleLabel.preferredMaxLayoutWidth, CGFLOAT_MAX)];;
}
-(void)layoutSubviews {
    self.titleLabel.preferredMaxLayoutWidth = self.frame.size.width;
    [super layoutSubviews];
}
@end
UIButon子类通过重写layoutSubviews方法来掌控布局过程。基本思想是确定按钮的宽度,一旦它被布置。然后将宽度设置为其子标签titleLabel(保存按钮文本的标签)的preferredMaxLayoutWidth(布局引擎应占用的多行标签的最大宽度)。最后,根据其titleLabel的大小为按钮返回一个intrinsicContentSize,以使按钮完全包裹其titleLabel
  1. 当按钮已经被布局并确定了其框架大小时,会调用重写的layoutSubviews。在第一步中,将按钮的渲染宽度设置为按钮的titleLabelpreferredMaxLayoutWidth
  2. 第二步通过调用[super layoutSubviews]重新调用布局引擎,从而基于其titleLabelpreferredMaxLayoutWidth(现在已设置为按钮的渲染宽度),重新确定按钮的intrinsicContentSize
  3. 在重写的intrinsicContentSize方法中,我们返回完全包裹其titleLabel的按钮的最小适合尺寸,并设置preferredMaxLayoutWidth。我们在按钮的titleLabel上使用sizeThatFits方法,它简单地作为titleLabel不遵循任何基于约束的布局。

结果应该类似于您所要求的内容。

Portrait Landscape

如有任何其他澄清/关注事项,请随时让我知道。

谢谢。


非常感谢您的解决方案。但是,我有一个问题,使用您的代码,按钮在纵向模式下会有4行,在旋转后仍然会有4行,对吗?但是在我的图像帖子中,我的按钮在纵向模式下有4行,在横向模式下只有3行。如果我错了,请指正我。谢谢。 - Linh
你在你的设备上试过了吗?我还没有在真实设备上尝试过我的代码,但我已经在运行iOS 9.2的模拟器上尝试过了。你可以在我的更新答案中看到每个方向的输出。根据可用空间,按钮应该只显示所需数量的行。此外,我已经添加了一些边框到按钮层以显示它的边界矩形。请告诉我。谢谢! - Ayan Sengupta
我认为UIButton的子类是一个好的解决方案。如果您不想创建UIButton的子类,您仍然可以创建包装器视图。 - Timur Bernikovich
这个答案中有一个小错误。需要考虑contentEdgeInsetstitleEdgeInsets来计算正确的宽度,以便正确地使用preferredMaxLayoutWidth - Claus Jørgensen
@ClausJørgensen 你说得有点对。如果我们想的话,我们也可以考虑那些参数,以及 imageEdgeInsets。但我只是不想让我的答案变得复杂,并且只提供基本思路。考虑到其他参数留给任何感兴趣的人作为练习;但这根据定义并不是一个错误。 - Ayan Sengupta

4

Ayan Sengupta提供的Swift解决方案,支持contentEdgeInsets(感谢Claus Jørgensen):

(如果需要,您还可以进一步自定义代码以考虑titleEdgeInsets)

  1. Subclass your UIButton to take the ownership of the layout process:

     /// https://dev59.com/BFsW5IYBdhLWcg3wyJt3#50575588
     class AutoLayoutButton: UIButton {
         override var intrinsicContentSize: CGSize {
             var size = titleLabel!.sizeThatFits(CGSize(width: titleLabel!.preferredMaxLayoutWidth - contentEdgeInsets.left - contentEdgeInsets.right, height: .greatestFiniteMagnitude))
             size.height += contentEdgeInsets.left + contentEdgeInsets.right
             return size
         }
         override func layoutSubviews() {
             titleLabel?.preferredMaxLayoutWidth = frame.size.width
             super.layoutSubviews()
         }
     }
    
  2. Use this class in your storyboard, and set constraints for Leading, Trailing, Top, Bottom. But don't set any Height constraint.

如果不想使用子类化的替代方法是添加一个包装视图,正如Bartłomiej Semańczyk回答Timur Bernikowich评论所建议的。


FYI,两个答案都存在一个错误。 preferredMaxLayoutWidth 需要考虑 contentEdgeInsetstitleEdgeInsets 来计算正确的宽度。 - Claus Jørgensen
1
@ClausJørgensen 谢谢,我已经添加了对 contentEdgeInsets 的支持。关于 titleEdgeInsets,它的文档中写道“按钮不使用此属性来确定 intrinsicContentSize”,所以我会在我的回答中忽略它,但是您可以根据自己的需求自定义代码。 - Cœur

1
如果您设置了sizeToFit属性,那么文本将始终在一行中,并且按钮的宽度将增加,除非您放置一个换行符\n来明确表示您希望它成为多行。
您可以像"line \n line"这样在第一行的末尾放置'\n'来表示。
line
line

如果您想拥有两个不同的字符串值(\n位置不同)来表示纵向横向,您可以使用UIDeviceOrientationUIDevice.currentDevice.orientation在此处描述检查方向条件,并根据设备的方向设置字符串值。

sizeToFit并不适用于自动布局。 - Ayan Sengupta
@sizeToFit 我解释了如何正确使用 sizeToFit,因为他最初尝试使用它。 - tmac_balla

0

我一直使用的方法如下:

  1. 添加另一个参考UILabel,它的lineNumber=0,并且与目标按钮具有相同的宽度。
  2. 不要为ref-UILable设置高度约束,应该为按钮设置高度约束以调整其高度
  3. 将相同的文本设置到ref UILabel和button.titleLabel中,sizeToFit它并获取其frame.size.height
  4. 使用高度值将其用于目标按钮的高度约束。(当然,按钮.titleLabel linenumber应设置为0或更多行)

完成。:)

PS1.此方法可用于滚动视图中的按钮和参考标签。

PS2.在某些情况下,由于在滚动视图中无法获得正确的frame.width,特别是当我们使用trailling约束时,因此无法正确地获得ref-label的正确高度。在sizeTofit之前,我们可以考虑为ref-label定义固定宽度,以获得用于目标按钮的正确高度。


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