iOS AutoLayout多线UILabel

时间:2013-07-05 14:36:30

标签: ios uilabel autolayout

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

iOS: Multi-line UILabel in Auto Layout

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

正如 mwhuss 指出的那样, setPreferredMaxLayoutWidth 可以让标签跨越多行。但这不是主要问题。问题是我在何时何地获得此 width 值作为参数发送到 setPreferredMaxLayoutWidth

我设法制作了看似合法的东西,所以如果我错了,请纠正我,如果你知道更好的方法,请告诉我。

在UIView的

-(CGSize) intrinsicContentSize

根据self.frame.width我的UILabels setPreferredMaxLayoutWidth

UIViewController的

-(void) viewDidLayoutSubviews

是我知道的第一个回调方法,主视图的子视图是用它们在屏幕上居住的确切帧来指定的。然后,从该方法内部操作我的子视图,使其内在大小无效,以便根据指定的宽度将UILabel分成多行。

9 个答案:

答案 0 :(得分:57)

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

  

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

     

由于我们通常不提前知道这个值,因此我们需要采取两步法来实现这一目标。首先我们让Auto Layout完成它的工作,然后我们在布局过程中使用结果框来更新首选的最大宽度和触发器布局。

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

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

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

答案 1 :(得分:42)

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

在几乎每一个案例中我都使用了Autolayout下的标签,一旦我的布局完成其余部分,首选的最大布局宽度就是标签的实际宽度。

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

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

答案 2 :(得分:32)

<强>更新

我的原始答案看起来很有帮助,所以我在下面没有提到它,但是,在我自己的项目中,我找到了一个更可靠的解决方案,可以解决iOS 7和iOS 8中的错误。 https://github.com/nicksnyder/ios-cell-layout

原始回答

这是一个适用于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()
    }
}

答案 3 :(得分:9)

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

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

这是我们使用的代码。只需将Interface类的Interface类设置为 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

答案 4 :(得分: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];

答案 5 :(得分:2)

由于我不允许添加评论,我不得不将其添加为答案。 如果我在layoutIfNeeded中拨打updateViewConstraints之前获取相关标签的preferredMaxLayoutWidth,则jrturton的版本仅对我有用。 如果没有layoutIfNeeded来电,preferredMaxLayoutWidth 0中的updateViewConstraints总是setBounds:。然而,在preferredMaxLayoutWidth中签入时,它始终具有所需的值。在设置了正确的setPreferredMaxLayoutWidth:时,我无法知道。我在UILabel子类上覆盖setBounds:,但它从未被调用过。

总结,我:

  • ... sublcassed UILabel
  • ...并覆盖preferredMaxLayoutWidth至,如果尚未设置,请将CGRectGetWidth(bounds)设置为[super updateViewConstraints]
  • ...在以下
  • 之前致电layoutIfNeeded
  • ...在将preferredMaxLayoutWidth用于标签尺寸计算之前致电preferredMaxLayoutWidth

编辑:此解决方法似乎只在某些情况下有效或需要。我刚遇到一个问题(iOS 7/8),标签的高度未正确计算,因为0在布局过程执行一次后返回UILabel。经过一些试验和错误(并找到了这个Blog entry)之后,我又转而使用{{1}}并设置了顶部,底部,左侧和右侧的自动布局约束。无论出于何种原因,在更新文本后都正确设置了标签的高度。

答案 6 :(得分: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;
}

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

答案 7 :(得分:0)

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

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

-(void) updateViewConstraints出现问题。

答案 8 :(得分:0)

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

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