iOS AutoLayout多行UILab

以下问题是该问题的某种延续:

iOS:自动版式中的多行UILabel

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

正如mwhuss指出的那样,setPreferredMaxLayoutWidth可以使标签跨多行。 但这不是这里的主要问题。 问题是我在哪里以及何时获得此宽度值,并将其作为参数发送给setPreferredMaxLayoutWidth。

我设法制作出看起来合法的东西,所以如果我以任何方式错了,请改正我,如果您知道更好的方法,请告诉我。

在UIView的

-(CGSize) intrinsicContentSize

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

UIViewController的

-(void) viewDidLayoutSubviews

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

ancajic asked 2019-10-09T07:43:48Z
9个解决方案
57 votes

在objc.io上,“高级自动版式工具箱”的“多行文本的内在内容大小”部分有一个答案。 以下是相关信息:

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

由于我们通常不预先知道此值,因此需要采取两步方法来实现此目的。 首先,我们让自动版式完成其工作,然后在版式传递中使用生成的帧来更新首选的最大宽度并再次触发版式。

他们提供给视图控制器内部使用的代码:

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

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

nevan king answered 2019-10-09T07:45:11Z
42 votes

令人讨厌的是,如果您有明确为您定义该宽度的约束,则UILabel不会默认使用其首选最大布局宽度的宽度。

在几乎所有情况下,我都会在“自动布局”下使用标签,一旦完成其余布局,首选的最大布局宽度就是标签的实际宽度。

因此,为使此过程自动发生,我使用了UILabel子类,该子类覆盖了setBounds:。在这里,调用super实现,然后(如果还不是这样的话)将首选的最大布局宽度设置为边界大小宽度。

重点很重要-设置首选的最大布局会导致执行另一遍布局,因此您可能会陷入无限循环。

jrturton answered 2019-10-09T07:44:20Z
32 votes

更新

我的原始答案似乎很有帮助,因此我在下面未做任何改动,但是,在我自己的项目中,我发现了一种更可靠的解决方案,可以解决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()
    }
}
Nick Snyder answered 2019-10-09T07:45:50Z
9 votes

我们曾遇到过这样的情况,即UIScrollView内的自动布局UILabel可以很好地纵向放置,但是当旋转以横向放置UILabel时,高度不会重新计算。

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

这是我们使用的代码。 只需将“接口”构建器中的“定制”类设置为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
Ben Clayton answered 2019-10-09T07:46:40Z
2 votes

使用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];
Cameron Lowell Palmer answered 2019-10-09T07:47:17Z
2 votes

由于不允许添加评论,因此必须将其添加为答案。仅当我在获取有问题的标签UILabel之前在0中致电preferredMaxLayoutWidth时,jrturton的版本才对我有用。如果没有致电layoutIfNeeded,则preferredMaxLayoutWidth中的preferredMaxLayoutWidth始终是updateViewConstraints中的0。但是,在setBounds:中进行检查时,它始终具有所需的值。 我在UILabel子类上覆盖了setPreferredMaxLayoutWidth:,但从未调用过它。

总结一下,我:

  • ...被征服的UILabel
  • ...并覆盖preferredMaxLayoutWidth,如果尚未设置,则将0设置为UILabel
  • ...在进行以下操作之前请致电preferredMaxLayoutWidth
  • ...在获得0用于标签尺寸计算之前致电preferredMaxLayoutWidth

编辑:这种变通办法似乎只工作,或有时需要。 我只是遇到一个问题(iOS 7/8),其中标签的高度没有正确计算,因为执行布局过程一次后preferredMaxLayoutWidth返回了0。 因此,经过反复试验(并找到此Blog条目),我再次切换为使用UILabel,并仅设置了上,下,左和右自动布局约束。 并且无论出于何种原因,更新文本后,标签的高度都已正确设置。

dergab answered 2019-10-09T07:48:26Z
1 votes

如另一个答案所建议,我尝试覆盖updateViewConstraints

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

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

jbandi answered 2019-10-09T07:49:21Z
0 votes

干净的解决方案是设置-(void) updateViewConstraints并为标签的heightconstraint使用一个属性。 然后在设置内容后调用

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

自iOS 7.1起-(void) updateViewConstraints出现问题。

netshark1000 answered 2019-10-09T07:49:55Z
0 votes

在iOS 8中,您可以通过在出队和配置单元后调用cell.layoutIfNeeded()来解决单元中的多行标签布局问题。 该呼叫在iOS 9中无害。

请参阅Nick Snyder的答案。 此解决方案来自他的代码,网址为[https://github.com/nicksnyder/ios-cell-layout/blob/master/CellLayout/TableViewController.swift]。

phatmann answered 2019-10-09T07:50:29Z
translate from https://stackoverflow.com:/questions/17491376/ios-autolayout-multi-line-uilabel