61

以下问题是这个问题的延续:

iOS:自动布局中的多行 UILabel

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

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

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

在 UIView 的

-(CGSize) intrinsicContentSize

我根据self.frame.width为我的UILabels设置了PreferredMaxLayoutWidth

UIViewController 的

-(void) viewDidLayoutSubviews

是我知道的第一个回调方法,主视图的子视图在哪里指定了它们在屏幕上的确切帧。然后,从该方法内部,我对我的子视图进行操作,使它们的固有大小无效,以便 UILabel 根据指定给它们的宽度分成多行。

4

9 回答 9

63

在objc.io的Advanced Auto Layout Toolbox的“多行文本的内在内容大小”部分中有一个答案。以下是相关信息:

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

由于我们通常事先不知道这个值,因此我们需要采取两步的方法来解决这个问题。首先我们让 Auto Layout 完成它的工作,然后我们在布局过程中使用生成的框架来更新首选的最大宽度并再次触发布局。

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

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

看看他们的帖子,有更多关于为什么需要进行两次布局的信息。

于 2013-11-29T14:19:14.797 回答
44

如果您有明确地为您定义该宽度的约束,那么 UILabel 不会默认为其首选最大布局宽度的宽度似乎很烦人。

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

因此,为了自动实现这一点,我使用了一个 UILabel 子类,它覆盖了setBounds:. 在这里,调用超级实现,然后,如果不是这样,则将首选最大布局宽度设置为边界大小宽度。

重点很重要 - 设置首选最大布局会导致执行另一个布局传递,因此您最终可能会出现无限循环。

于 2013-07-05T16:18:09.063 回答
33

更新

我最初的答案似乎很有帮助,所以我在下面保持不变,但是,在我自己的项目中,我找到了一个更可靠的解决方案,可以解决 iOS 7 和 iOS 8 中的错误。 https://github.com/nicksnyder/ios -单元格布局

原始答案

这是适用于 iOS 7 和 iOS 8 的完整解决方案

目标 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()
    }
}
于 2014-10-03T15:21:49.737 回答
9

我们遇到了这样一种情况,即 UIScrollView 内的自动布局 UILabel 以纵向布局很好,但是当旋转到横向时,UILabel 的高度没有重新计算。

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

这是我们使用的代码。只需将 Interface builder 中的 Custom 类设置为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
于 2014-05-16T13:21:59.780 回答
2

使用 boundingRectWithSize

UITableViewCell通过像这样测量所需的宽度,我解决了我在使用“\n”作为换行符的遗留系统中与两个多行标签的斗争:

- (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];
于 2014-06-17T17:58:05.013 回答
2

由于我不允许添加评论,因此我有义务将其添加为答案。jrturton 的版本只对我有用,如果我layoutIfNeededupdateViewConstraints得到有preferredMaxLayoutWidth问题的标签之前打电话。没有调用总是layoutIfNeeded在。然而,它在签入时始终具有所需的值。我没能知道什么时候设置正确。我覆盖了子类,但它从未被调用过。preferredMaxLayoutWidth0updateViewConstraintssetBounds:preferredMaxLayoutWidthsetPreferredMaxLayoutWidth:UILabel

总结一下,我:

  • ...子类化的 UILabel
  • ...并覆盖setBounds:为,如果尚未设置,则设置preferredMaxLayoutWidthCGRectGetWidth(bounds)
  • ...[super updateViewConstraints]在以下之前调用
  • ...在用于标签尺寸计算layoutIfNeeded之前调用preferredMaxLayoutWidth

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

于 2015-01-21T10:14:49.397 回答
1

正如另一个答案所建议的,我试图覆盖viewDidLayoutSubviews

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

这行得通,但它在 UI 上可见并导致“可见闪烁”,即首先以两行的高度渲染标签,然后以仅一行的高度重新渲染。

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

然后我通过覆盖找到了一个更好的解决方案updateViewConstraints

-(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;
}

这对我来说是更好的解决方案,因为它不会导致“视觉闪烁”。

于 2014-03-08T23:30:46.133 回答
0

一个干净的解决方案是为rowcount = 0标签的高度约束设置和使用属性。然后在设置内容后调用

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

-(void) updateViewConstraints从 iOS 7.1 开始出现问题。

于 2014-05-05T18:42:33.097 回答
0

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

请参阅尼克斯奈德的回答。该解决方案取自他在https://github.com/nicksnyder/ios-cell-layout/blob/master/CellLayout/TableViewController.swift的代码。

于 2015-11-05T17:01:42.867 回答