UILabel的sizeToFit在iOS 6中与自动布局无法正常工作。

159
我应该如何在程序中配置一个高度依赖于文本的UILabel(并且应该使用哪种方法)?我一直在尝试使用Storyboard和代码设置,但是一直没有成功。很多人推荐使用`sizeToFit`,同时设置`lineBreakMode`和`numberOfLines`。然而,无论我把这些代码放在`viewDidLoad:`、`viewDidAppear:`或者`viewDidLayoutSubviews`中,我都不能让它正常工作。要么我会将框变得太小以至于长文本无法展示,要么我会将其变得太大以至于无法收缩。

就我所知,相同的代码:在Xcode/viewController中,我不需要使用label.sizeToFit(),约束已经足够了。在Playground中没有创建标签。到目前为止,我发现在Playground中使其工作的唯一方法是执行label.sizeToFit() - mfaani
13个回答

409
请注意,在大多数情况下,Matt的解决方案都能按预期工作。但如果对您不起作用,请继续阅读。
要使标签自动调整高度,请执行以下操作:
  1. 设置标签的布局约束
  2. 设置具有低优先级的高度约束。它应该低于ContentCompressionResistancePriority
  3. 设置numberOfLines = 0
  4. 将ContentHuggingPriority设置为高于标签的高度优先级
  5. 为标签设置preferredMaxLayoutWidth。该值由标签用于计算其高度
例如:
self.descriptionLabel = [[UILabel alloc] init];
self.descriptionLabel.numberOfLines = 0;
self.descriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.descriptionLabel.preferredMaxLayoutWidth = 200;

[self.descriptionLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:self.descriptionLabel];

NSArray* constrs = [NSLayoutConstraint constraintsWithVisualFormat:@"|-8-[descriptionLabel_]-8-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)];
[self addConstraints:constrs];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-8-[descriptionLabel_]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];
[self.descriptionLabel addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[descriptionLabel_(220@300)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];

使用界面生成器

  1. 设置四个约束条件,高度约束是必需的。 enter image description here

  2. 然后进入标签属性检查器并将行数设置为0。 enter image description here

  3. 进入标签大小检查器并增加垂直 ContentHuggingPriority 和垂直 ContentCompressionResistancePriority。
    enter image description here

  4. 选择并编辑高度约束。
    enter image description here

  5. 减小高度约束优先级。
    enter image description here

愉快使用! :)


4
没问题!这正是我在寻找的答案。唯一需要做的就是将contentHuggingPriority和contentCompressionResistancePriority设置为required。我可以在IB中完成所有操作!非常感谢你。 - circuitlego
43
你想得太多了。要么设置 preferredMaxLayoutWidth 或固定标签的宽度(或其左右两侧),就这些!然后它会根据内容自动垂直伸缩以适应内容。 - matt
我不得不设置preferredMaxLayoutWidth属性并固定标签的宽度。效果非常好 :) - MartinMoizard
这些在Xcode 6,iOS8中都不再起作用了???我应该去踢某人的坟墓吗? - Josh
2
我终于找到原因了,可能值得一提的是,当你将单元格粘在视图底部时,需要将“至底部”约束的优先级设置为小于1000。我将其设置为750,现在一切都完美地工作了。我猜它缩小了视图。 - Mathijs Segers
显示剩余4条评论

66

在 iOS 6 中,使用自动布局(autolayout)时,如果将一个 UILabel 的两侧(或宽度)和顶部固定,它将会根据其内容自动垂直增长和收缩,而无需编写任何代码或者干扰它的压缩阻力等设置。这非常简单。

在更复杂的情况下,只需要设置标签的preferredMaxLayoutWidth属性。

无论哪种方式,正确的事情都会自动发生。


12
请将numberOfLines设置为0,否则您将无法自动换行。 - devongovett
2
这对我来说还不够。我还需要@Mark答案中的第2点。 - Danyal Aytekin
在不编写代码的情况下,是否也可以使标签自动增长到多行? - Noor
这比被接受的答案要简单得多。我做了两个示例(这里这里)来说明这个答案。 - Suragch
@Suragch 使用约束条件可以解决这个问题。但是在 Playground 中,相同的代码不起作用。在 Playground 中,你必须使用 label.sizeToFit() - mfaani

42

虽然问题中提到了程序化,但我也曾遇到过同样的问题,并且更喜欢使用Interface Builder工具。因此,我认为通过Interface Builder解决此问题可能会对现有答案有所帮助。

首先,要忘记sizeToFit方法。Auto Layout会根据内在内容大小自动处理此问题。

因此,问题是如何使用Auto Layout使标签适应其内容呢?具体来说-因为问题提到了-高度。请注意,相同的原则适用于宽度。

因此,让我们从一个高度设置为41像素的示例UILabel开始:

enter image description here

正如您在上面的屏幕截图中看到的那样,"This is my text"上下有填充。即是在UILabel高度和文本之间的填充。

如果在模拟器中运行此应用程序,则可以看到相同的结果:

enter image description here

现在,让我们在Interface Builder中选择UILabel,并查看“大小”检查器中的默认设置:

enter image description here

请注意上面突出显示的约束。这是内容挤压优先级。正如Erica Sadun在她出色的iOS Auto Layout Demystified中所描述的那样,它是:

视图希望避免其核心内容周围的额外填充方式

对于我们来说,对于UILabel,核心内容就是文本。

这里是基本情景的核心。我们为文本标签设置了两个约束条件,它们相互冲突。一个说 "高度必须等于 41 像素高"。另一个说 "贴近视图到其内容,使得我们没有任何多余的填充"。在我们的情况下,贴近视图到其文本,所以我们没有任何额外的填充。

现在,使用自动布局,有两个不同的指令要求做不同的事情,运行时必须选择其中一个。它不能同时完成两个指令。UILabel 既不能是 41 像素高,也不能没有填充。

解决方法是指定优先级。必须有一个指令比另一个指令具有更高的优先级。如果两个指令说不同的事情,并且具有相同的优先级,则会出现异常。

那么让我们试一试。我的高度约束具有1000的优先级,这是必需的。内容贴近高度为250,这是弱的。如果将高度约束优先级降低到249会发生什么?

enter image description here

现在我们可以看到魔法开始发生了。让我们在模拟器中试一下:

enter image description here

太棒了!内容贴近达成。只因为高度优先级249小于内容贴近优先级250。基本上,我是在说"我在这里指定的高度不如我为内容贴近指定的重要"。因此,内容贴近获胜。

底线是,使标签适合文本可能就像指定高度或宽度约束一样简单,并正确设置与该轴的内容贴近优先级约束相关联的优先级。

将宽度等效执行留给读者作为练习!


3
非常感谢您的清晰解释!最终我明白了内容吸附优先级的含义。 - kernix
请问您能否解释一下什么是“内容压缩阻力优先级”?谢谢! - kernix
2
很好的答案,Max!有没有办法限制使用这种方式工作的行数? - rafaeljuzo

7
在iOS7中,注意到sizeToFit不起作用了 - 也许这个解决方案对您有帮助。
[textView sizeToFit];
[textView layoutIfNeeded];

1
@Rambatino 这对我有效。当你需要 setNeedsUpdateConstraints 时,请添加 layoutIfNeeded。但情况可能略有不同。 - Gon
当我使用弹出窗口时,这对我来说效果最好。不过,我需要先执行layoutIfNeeded。 - Jason

4

确保标签的preferredMaxLayoutWidth与标签宽度保持同步的另一种选择:

#import "MyLabel.h"

@implementation MyLabel

-(void)setBounds:(CGRect)bounds
{
    [super setBounds:bounds];

    // This appears to be needed for iOS 6 which doesn't seem to keep
    // label preferredMaxLayoutWidth in sync with its width, which 
    // means the label won't grow vertically to encompass its text if 
    // the label's width constraint changes.
    self.preferredMaxLayoutWidth = self.bounds.size.width;
}

@end

4
我觉得我应该贡献自己的力量,因为花费了一段时间才找到正确的解决方案:
  • 目标是让自动布局(Auto Layout)在不调用sizeToFit()的情况下工作,我们可以通过指定正确的约束来实现:
  • 在您的UILabel上指定顶部、底部和前导/尾随空间约束
  • 将行数属性设置为0
  • 增加内容吸附优先级至1000
  • 降低内容压缩阻力优先级至500
  • 在底部容器约束上,将优先级降低至500
基本上,您告诉UILabel即使它有固定的高度约束,也可以打破这个约束使自己变小以适应内容(例如仅有一行),但它不能打破约束使其变大。

3

我通过编程方式添加了一个UILabel,在我的情况下这已经足够了:

label.translatesAutoresizingMaskIntoConstraints = false
label.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
label.numberOfLines = 0

3
在我的情况下,我正在创建一个包含UILabel(长度未知)的UIView子类。在iOS7中,代码很简单:设置约束,不用担心内容吸附或压缩阻力,一切都按预期工作。
但是,在iOS6中,UILabel始终被剪切为单行。以上所有答案都对我无效。内容吸附和压缩阻力设置被忽略。唯一可以防止剪切的解决方案是在标签上包括preferredMaxLayoutWidth。但是我不知道将首选宽度设置为多少,因为其父视图的大小未知(实际上,它将由内容定义)。
最后,我找到了解决方案here。因为我正在使用自定义视图,所以可以添加以下方法来设置首选布局宽度,此后计算约束,然后重新计算它们:
- (void)layoutSubviews
{
    // Autolayout hack required for iOS6
    [super layoutSubviews];
    self.bodyLabel.preferredMaxLayoutWidth = self.bodyLabel.frame.size.width;
    [super layoutSubviews];
}

这正是我所遇到的情况……有时候知道你不是孤单一人,感觉真好。 - daveMac
Adam,我在处理你说在ios7上完美运行的问题时遇到了困难。我有一个自定义单元格,其中包含一个UIview和一个UILable。我可以让标签适应内容,但无论我加什么约束条件,视图都不会适应。你是如何让它工作的?我发誓我尝试了所有方法,但它不会采用标签的高度。 - denikov

2
我已经通过将"首选宽度"设置为自动,并将标签的顶部、前导和尾随固定来解决了xCode6的问题。 enter image description here

0
我也遇到了这个问题,我的UIView子类包含一个UILabel作为其内部元素。即使使用自动布局并尝试所有推荐的解决方案,标签仍然无法将其高度收紧到文本。在我的情况下,我只想让标签成为一行。
无论如何,对我有效的方法是为UILabel添加所需的高度约束,并在调用intrinsicContentSize时手动将其设置为正确的高度。如果您没有将UILabel包含在另一个UIView中,则可以尝试对UILabel进行子类化,并通过首先设置高度约束,然后返回 [super instrinsicContentSize]; 而不是[ self.containerview intrinsiceContentSize]; 像我下面特定于我的UIView子类一样提供类似的实现。
- (CGSize)intrinsicContentSize
{
    CGRect expectedTextBounds = [self.titleLabel textRectForBounds:self.titleLabel.bounds limitedToNumberOfLines:1];
    self.titleLabelHeightConstraint.constant = expectedTextBounds.size.height;
    return [self.containerView intrinsicContentSize];

}

现在在iOS 7和iOS 8上完美运行。


值得注意的是,如果您已正确设置视图(即您的约束正确,并且您在视图的生命周期的正确阶段设置了所有内容),则您不应该覆盖intrinsicContentSize。运行时应该能够根据您正确设置的约束来计算此内容。 - John Rogers
理论上来说应该是正确的,但我们无法访问所有苹果的私有API和实现,它们并不是绝对可靠的,代码中也可能存在漏洞。 - n8tr
如果您正确实现了自动布局,那么 [super intrinsicContentSize] 应该会处理它。这就是我的意思。 - John Rogers

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