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
。 如果没有调用layoutIfNeeded
, preferredMaxLayoutWidth
在updateViewConstraints
中始终为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]; }
这工作,但它是可见的用户界面,并导致“可见的闪烁”,即第一个标签是用两行的高度呈现,然后重新呈现与高度只有一行。
这对我来说是不能接受的。
我通过重写updateViewConstraints
find了更好的解决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 。