iOS Autolayout:调整大小父视图中的 UILabels 问题

Posted

技术标签:

【中文标题】iOS Autolayout:调整大小父视图中的 UILabels 问题【英文标题】:iOS Autolayout: Issue with UILabels in a resizing parent view 【发布时间】:2012-10-31 00:50:32 【问题描述】:

我有一个只包含 UILabel 的视图。此标签包含多行文本。父级具有可变宽度,可以使用平移手势调整大小。我的问题是,当我调整 UILabel 的大小时,它不会重新计算其高度以使所有内容仍然可见,它只是将其切断。

我已经设法通过一些 hack 来修复它,但运行起来非常慢:

- (void)layoutSubviews 

    CGSize labelSize = [self.labelDescription sizeThatFits:CGSizeMake(self.frame.size.width, FLT_MAX)];

    if (self.constraintHeight) 
        [self removeConstraint:self.constraintHeight];
    

    self.constraintHeight = [NSLayoutConstraint constraintWithItem:self.labelDescription attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:labelSize.height];

    [self addConstraint:self.constraintHeight];

    [super layoutSubviews];

这不应该通过自动布局自动发生吗?

编辑

我的视图结构是:

UIScrollView
---> UIView
     ---> UILabel

以下是 UIScrollView 的约束:

<NSLayoutConstraint:0x120c4860 H:|-(>=32)-[DescriptionView:0x85c81c0]   (Names: '|':UIScrollView:0x85db650 )>,
<NSLayoutConstraint:0x120c48a0 H:|-(32@900)-[DescriptionView:0x85c81c0] priority:900   (Names: '|':UIScrollView:0x85db650 )>,
<NSLayoutConstraint:0x120c48e0 H:[DescriptionView:0x85c81c0(<=576)]>,
<NSLayoutConstraint:0x120c4920 H:[DescriptionView:0x85c81c0]-(>=32)-|   (Names: '|':UIScrollView:0x85db650 )>,
<NSLayoutConstraint:0x120c4960 H:[DescriptionView:0x85c81c0]-(32@900)-| priority:900   (Names: '|':UIScrollView:0x85db650 )>,
<NSLayoutConstraint:0x8301450 DescriptionView:0x85c81c0.centerX == UIScrollView:0x85db650.centerX>,

以下是对 UIView 的约束:

<NSLayoutConstraint:0x85c4580 V:|-(0)-[UILabel:0x85bc7b0]   (Names: '|':DescriptionView:0x85c81c0 )>,
<NSLayoutConstraint:0x85c45c0 H:|-(0)-[UILabel:0x85bc7b0]   (Names: '|':DescriptionView:0x85c81c0 )>,
<NSLayoutConstraint:0x85c9f80 UILabel:0x85bc7b0.trailing == DescriptionView:0x85c81c0.trailing>,
<NSLayoutConstraint:0x85c9fc0 UILabel:0x85bc7b0.centerY == DescriptionView:0x85c81c0.centerY>

UILabel 本身对它没有任何限制,除了将其固定到其父对象的边缘

【问题讨论】:

你对标签和它所在的容器有什么限制? 对原帖添加约束 那么您在标签上设置的唯一限制是它在水平和垂直方向上都填充了它的容器?当容器为特定大小时,文本是否会被截断,或者无论容器有多大,它总是会截断文本? 是的。就像它进行自动布局计算一样,然后当您使用平移手势来更改容器的大小时,它不会重新计算显示标签全部内容所需的高度。一旦文本换行的更改将其从 UILabel 的底部推开,它就会将其切断,它的高度永远不会改变。 如何在平移手势中调整容器的大小? 【参考方案1】:

好吧,我终于搞定了。解决方案是在viewDidLayoutSubviews 中设置preferredMaxLayoutWidth,但只在第一轮布局之后。您可以通过将异步调度回主线程来简单地安排这一点。所以:

- (void)viewDidLayoutSubviews 
    dispatch_async(dispatch_get_main_queue(), ^
        self.theLabel.preferredMaxLayoutWidth = self.theLabel.bounds.size.width;
    );

这样,在标签的宽度由其与父视图相关的约束正确设置之后,您才可以设置preferredMaxLayoutWidth

这里的工作示例项目:

https://github.com/mattneub/Programming-ios-Book-Examples/blob/master/ch23p673selfSizingLabel4/p565p579selfSizingLabel/ViewController.m

编辑:另一种方法!继承 UILabel 并覆盖 layoutSubviews:

- (void) layoutSubviews 
    [super layoutSubviews];
    self.preferredMaxLayoutWidth = self.bounds.size.width;

结果是一个自调整大小的标签 - 无论其宽度如何变化(假设其宽度由约束/布局更改),它都会自动更改其高度以适应其内容。

【讨论】:

@lichen19853 请注意,仅在 OP 奇怪的情况下才需要此解决方案,其中超级视图通过约束调整大小并更改标签的宽度。如果您直接更改标签的宽度,例如通过在代码中改变它的宽度约束,你可以在那里改变它的preferredMaxLayoutWidth 据我所知,当父视图再次增长时,此解决方案不起作用 - 标签不会扩展以占用任何可用的新空间。 这个解决方案对我有用。虽然我的自定义超级视图没有继承 UILabel,而是覆盖了 layoutSubviews,然后在子视图标签上设置了 preferredMaxLayoutWidth。【参考方案2】:

在向 Apple 提出错误后,我已解决此问题。多行文本需要两次布局的问题,这一切都依赖于属性preferredMaxLayoutWidth

以下是需要添加到包含多行标签的视图控制器的相关代码:

- (void)viewWillLayoutSubviews

    // Clear the preferred max layout width in case the text of the label is a single line taking less width than what would be taken from the constraints of the left and right edges to the label's superview
    [self.label setPreferredMaxLayoutWidth:0.];


- (void)viewDidLayoutSubviews

    // Now that you know what the constraints gave you for the label's width, use that for the preferredMaxLayoutWidth—so you get the correct height for the layout
    [self.label setPreferredMaxLayoutWidth:[self.label bounds].size.width];

    // And then layout again with the label's correct height.
    [self.view layoutSubviews];

【讨论】:

感谢分享这个解决方案 - 完全无法猜测! 但是,我一直在测试这个解决方案,我发现设置标签的 preferredMaxLayoutWidth 仍然太晚了一个布局周期。所以有时它适合文本,有时文本的最后几个单词会从标签中删除(它不够高)。 我使用这背后的想法来解决我的 UITableViewCell 中的自动布局问题,方法是将这两种方法添加到 cellForRowAtIndexPath:[self.label setPreferredMaxLayoutWidth:[self.tableView bounds].size.width - 16];[self.tableView layoutSubviews];【参考方案3】:

在适用于 iOS 7/8 的 Xcode 6.1 中,我可以通过在我的视图上调用的 setter 方法中设置 preferredMaxLayoutWidth 来使其工作,以显示标签的文本。我猜它一开始就设置为0。下面的示例,其中self.teachPieceLabel 是标签。该标签与 Interface Builder 中视图中的其他标签一起与约束相连。

- (void)setTeachPieceText:(NSString *)teachPieceText 
      self.teachPieceLabel.text = teachPieceText;
      [self.teachPieceLabel setPreferredMaxLayoutWidth:[self.teachPieceLabel bounds].size.width];

【讨论】:

以上是关于iOS Autolayout:调整大小父视图中的 UILabels 问题的主要内容,如果未能解决你的问题,请参考以下文章

iOS 8 自动布局 - 动态调整 4 个方形视图的大小

以 Autolayout 视觉形式调整子视图的大小

使用 AutoLayout 调整 UIStackView 的大小

如何使用 Cocoa Autolayout 根据优先级调整两个子视图的大小?

iOS Autolayout 调整标签大小和它们之间的空间

使用Xcode 6中的AutoLayout约束模拟方面适合行为