iOS AutoLayout多行UILabel

下面的问题是这个问题的继续:

iOS:自动布局中的多行UILabel

主要思想是每个视图都应该声明它是“首选”(内在)大小,以便AutoLayout可以知道如何正确显示它。 UILabel只是一个视图本身无法知道需要显示的大小的情况的例子。 这取决于提供的宽度。

正如mwhuss所指出的那样, setPreferredMaxLayoutWidth做了使标签跨越多行的技巧。 但这不是这里的主要问题。 问题是我何时何地得到这个宽度值,我发送作为参数setPreferredMaxLayoutWidth

我设法做出一些看起来合法的东西,所以如果我错了,请纠正我,如果你知道更好的方法,请告诉我。

在UIView的

-(CGSize) intrinsicContentSize 

我为我的UILabels根据self.frame.width setPreferredMaxLayoutWidth

UIViewController的

 -(void) viewDidLayoutSubviews 

是我知道的第一个callback方法,主视图的子视图是用屏幕上居住的精确帧来指定的。 从那个方法里面,我操作我的子视图,使它们的内在大小失效,以便UILabel根据指定给它们的宽度分成多行。

看起来令人讨厌的是,一个UILabel不会默认宽度为首选的最大布局宽度,如果你有明确的定义宽度的约束。

几乎在每一种情况下,我都使用Autolayout下的标签,一旦我的布局执行完毕,首选的最大布局宽度就是标签的实际宽度。

所以,为了自动发生这种情况,我使用了一个UILabel子类,它覆盖了setBounds: 这里,调用超级实现,那么, 如果不是这种情况 ,则将首选最大布局宽度设置为边界大小宽度。

重点是重要的 – 设置首选的最大布局会导致执行另一个布局传递,因此可能会以无限循环结束。

高级自动布局工具箱的 “多行文本的内在内容大小”部分中有关于objc.io上的这个问题的答案。 这是相关的信息:

对于多行文本,UILabel和NSTextField的内在内容大小不明确。 文本的高度取决于线条的宽度,在解决约束时尚未确定。 为了解决这个问题,两个类都有一个名为preferredMaxLayoutWidth的新属性,它指定了计算内在内容大小的最大行宽。

由于我们通常不会提前知道这个值,所以我们需要采取两步走的方法来解决这个问题。 首先,我们让自动布局执行工作,然后使用布局过程中的结果帧再次更新首选的最大宽度和触发布局。

他们在视图控制器中使用的代码:

 - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width; [self.view layoutIfNeeded]; } 

看看他们的post,有更多的信息,为什么有必要做两次布局。

更新

我原来的答案似乎是有帮助的,所以我没有把它留在下面,但是,在我自己的项目中,我发现了一个更可靠的解决scheme,可以解决iOS 7和iOS 8中的错误。https://github.com/nicksnyder/ios胰岛β细胞布局;

原始答案

这是一个适用于iOS 7和iOS 8的完整解决scheme

目标C

 @implementation AutoLabel - (void)setBounds:(CGRect)bounds { if (bounds.size.width != self.bounds.size.width) { [self setNeedsUpdateConstraints]; } [super setBounds:bounds]; } - (void)updateConstraints { if (self.preferredMaxLayoutWidth != self.bounds.size.width) { self.preferredMaxLayoutWidth = self.bounds.size.width; } [super updateConstraints]; } @end 

迅速

 import Foundation class EPKAutoLabel: UILabel { override var bounds: CGRect { didSet { if (bounds.size.width != oldValue.size.width) { self.setNeedsUpdateConstraints(); } } } override func updateConstraints() { if(self.preferredMaxLayoutWidth != self.bounds.size.width) { self.preferredMaxLayoutWidth = self.bounds.size.width } super.updateConstraints() } } 

我们有一种情况,UIScrollView中的自动布局的UILabel在纵向上布置得很好,但是当旋转到横向时UILabel的高度没有被重新计算。

我们发现@jrturton的答案解决了这个问题,大概是因为现在preferredMaxLayoutWidth被正确设置了。

这是我们使用的代码。 只需将“接口”构build器中的“自定义”类设置为CVFixedWidthMultiLineLabel即可

CVFixedWidthMultiLineLabel.h

 @interface CVFixedWidthMultiLineLabel : UILabel @end 

CVFixedWidthMultiLineLabel.m

 @implementation CVFixedWidthMultiLineLabel // Fix for layout failure for multi-line text from // http://stackoverflow.com/questions/17491376/ios-autolayout-multi-line-uilabel - (void) setBounds:(CGRect)bounds { [super setBounds:bounds]; if (bounds.size.width != self.preferredMaxLayoutWidth) { self.preferredMaxLayoutWidth = self.bounds.size.width; } } @end 

由于我不允许添加评论,所以我有义务将其添加为答案。 jrturton的版本只对我有效,如果我在获取有问题的标签的updateViewConstraints之前,在updateViewConstraints调用layoutIfNeeded 。 如果没有调用layoutIfNeededpreferredMaxLayoutWidthupdateViewConstraints中始终为0 。 但是,在setBounds:检查时,它始终具有所需的值。 当设置正确的preferredMaxLayoutWidth ,我并没有设法知道。 我重写setPreferredMaxLayoutWidth:UILabel子类,但它永远不会被调用。

总结一下,我:

  • … sublcassed UILabel
  • …并覆盖setBounds: to, 如果尚未设置 ,则将preferredMaxLayoutWidth设置为CGRectGetWidth(bounds)
  • …在下面之前调用[super updateViewConstraints]
  • …在获取preferredMaxLayoutWidth以在标签的尺寸计算中使用之前调用layoutIfNeeded

编辑:这种解决方法似乎只工作,或有时需要。 我刚刚有一个问题(iOS 7/8),标签的高度计算不正确,因为preferredMaxLayoutWidth在布局过程执行一次后返回0 。 所以经过一些试验和错误(并发现这个博客条目 ),我切换到使用UILabel再次,只是设置顶部,底部,左侧和右侧的自动布局约束。 无论出于何种原因,在更新文本之后标签的高度被正确设置。

正如另一个答案build议,我试图重写viewDidLayoutSubviews

 - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; _subtitleLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - 40; [self.view layoutIfNeeded]; } 

这工作,但它是可见的用户界面,并导致“可见的闪烁”,即第一个标签是用两行的高度呈现,然后重新呈现与高度只有一行。

这对我来说是不能接受的。

我通过重写updateViewConstraintsfind了更好的解决scheme:

 -(void)updateViewConstraints { [super updateViewConstraints]; // Multiline-Labels and Autolayout do not work well together: // In landscape mode the width is still "portrait" when the label determines the count of lines // Therefore the preferredMaxLayoutWidth must be set _subtitleLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - 40; } 

这对我来说是更好的解决scheme,因为它不会引起“视觉闪烁”。

使用boundingRectWithSize

我解决了我在使用“\ n”作为换行符的遗留UITableViewCell中的两个多行标签的UITableViewCell ,方法是测量所需的宽度,如下所示:

 - (CGFloat)preferredMaxLayoutWidthForLabel:(UILabel *)label { CGFloat preferredMaxLayoutWidth = 0.0f; NSString *text = label.text; UIFont *font = label.font; if (font != nil) { NSMutableParagraphStyle *mutableParagraphStyle = [[NSMutableParagraphStyle alloc] init]; mutableParagraphStyle.lineBreakMode = NSLineBreakByWordWrapping; NSDictionary *attributes = @{NSFontAttributeName: font, NSParagraphStyleAttributeName: [mutableParagraphStyle copy]}; CGRect boundingRect = [text boundingRectWithSize:CGSizeZero options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]; preferredMaxLayoutWidth = ceilf(boundingRect.size.width); NSLog(@"Preferred max layout width for %@ is %0.0f", text, preferredMaxLayoutWidth); } return preferredMaxLayoutWidth; } 

然后调用这个方法就像下面这样简单:

 CGFloat labelPreferredWidth = [self preferredMaxLayoutWidthForLabel:textLabel]; if (labelPreferredWidth > 0.0f) { textLabel.preferredMaxLayoutWidth = labelPreferredWidth; } [textLabel layoutIfNeeded]; 

一个干净的解决scheme是设置rowcount = 0并使用属性作为标签的heightconstraint。 然后在内容被设置后调用

 CGSize sizeThatFitsLabel = [_subtitleLabel sizeThatFits:CGSizeMake(_subtitleLabel.frame.size.width, MAXFLOAT)]; _subtitleLabelHeightConstraint.constant = ceilf(sizeThatFitsLabel.height); 

-(void) updateViewConstraints自iOS 7.1以来, -(void) updateViewConstraints有一个问题。

在iOS 8中,通过在出列和configuration单元格后调用cell.layoutIfNeeded() ,可以修复单元格中的多行标签布局问题。 这个调用在iOS 9中是无害的。

见尼克·斯奈德的答案。 这个解决scheme是从他的代码取自https://github.com/nicksnyder/ios-cell-layout/blob/master/CellLayout/TableViewController.swift