【问题标题】:iOS AutoLayout multi-line UILabeliOS AutoLayout 多行UILabel
【发布时间】:2013-07-03 16:53:45
【问题描述】:

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

iOS: Multi-line UILabel in Auto Layout

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

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

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

在 UIView 的

-(CGSize) intrinsicContentSize

我根据self.frame.width为我的UILabels setPreferredMaxLayoutWidth

UIViewController的

-(void) viewDidLayoutSubviews

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

【问题讨论】:

    标签: ios uilabel autolayout


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 谢谢,伙计。完全忘记了“setBounds”。这实际上是我一直在寻找的一个子视图被指定其大小的点。我正在尝试 setFrame 并且没有被调用。但是我对这些东西并不是很了解,所以再次tnx。
    • 在 iOS8 下,如果 UILabel 在 tableview 单元格中,似乎不会调用 setBounds。有什么方法可以计算出preferredMaxLayoutWidth 的良好初始猜测?
    • @RogerNolan 还没有时间在 iOS8 上查看它(!)如果有机会我会更新,或者您可能想问一个新问题
    • @RogerNolan 对于 iOS8 的 tableview 中的 UILabel 有什么解决方案吗?
    • 我也支持 iOS 7,所以我将最大布局宽度设置为“显式”。现在我想以编程方式更改 iOS 8 的最大布局宽度。
    【解决方案2】:

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

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

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

    他们提供的用于视图控制器的代码:

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

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

    【讨论】:

    • @AlexanderLongbeach 我不确定你的意思。标签始终位于子视图内。不可能有不在子视图中的UILabel
    • 我的意思是。这里的所有示例都依赖于您知道标签超级视图的大小,但如果该超级视图也已使用自动布局添加而无需硬编码宽度......那会怎样?这可能是一个愚蠢的问题,但我是自动布局的新手..
    • @AlexanderLongbeach 上面的示例实际上并不依赖于在超级视图中具有硬编码的宽度。事实上,您几乎总是可以使用 label.superview.frame.size.width,甚至不需要指向超级视图的直接指针。
    • 看到声称 iOS 7 会为您处理这个问题:danielgarbien.com/ios/2013/11/16/preferredMaxLayoutWidth.html
    • 更新。那篇文章是不正确的。在 iOS 8 之前仍然需要。
    【解决方案3】:

    根据另一个答案的建议,我尝试覆盖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;
    }
    

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

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

        【解决方案5】:

        我们遇到过这样一种情况,即 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
        

        【讨论】:

        • 完美运行!谢谢
        • 有同样的问题,但不幸的是这对我不起作用。我正在使用 tableView 内的标签
        • 此解决方案非常有效,但需要注意一点。我在 tableView 和 heightForRowAtIndexpath 中使用标签我通常会执行“systemLayoutSizeFittingSize”以获得正确的单元格尺寸。但是,在调用此方法之前,您必须更新单元格宽度,因为“dequeueReusableCellWithIdentifier”将以 xib 文件中定义的大小返回它(我的是 320,实际大小为 227)。因此,在调用 layoutIfNeeded 之前更新 cell frame = tableView.contentSize.width 并结合此解决方案恰好适用于所有设备(4s、5、6、6+)。
        • 唯一对我有用的方法(在 UICollectionViewCell 中)是在计算大小时设置初始帧。
        【解决方案6】:

        使用 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];
        

        【讨论】:

        • 我可以用同样的方法计算标签高度吗?
        【解决方案7】:

        更新

        我最初的答案似乎很有帮助,所以我在下面保持不变,但是,在我自己的项目中,我找到了一个更可靠的解决方案,可以解决 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()
            }
        }
        

        【讨论】:

        • 这对我有用。我调整了这个逻辑,因为我只需要在 label.numberOfLines = 0; 时删除标签的 Xcode 警告
        • 这在 iOS 8 中的 UITableViewCell 中对我有用。
        • 这也是在 UITableViewCell 中对我有用的唯一建议。非常感谢
        • 这在 iOS 7 上对我不起作用!我添加了这个: - (void)layoutSubviews { [super layoutSubviews]; self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds); [超级布局子视图]; }
        • 我认为 swift 版本不会触发self.setNeedsUpdateConstraints() 行。它应该检查bounds.size.width != oldValue.size.width。目前的写法就是将值与自身进行比较。
        【解决方案8】:

        由于我不允许添加评论,因此我有义务将其添加为答案。 jrturton 的版本仅适用于我在获得相关标签的preferredMaxLayoutWidth 之前在updateViewConstraints 中调用layoutIfNeeded。 如果没有对layoutIfNeeded 的调用,preferredMaxLayoutWidthupdateViewConstraints 中始终是0。然而,当签入setBounds: 时,它始终具有所需的值。我没能知道什么时候设置了正确的preferredMaxLayoutWidth。我在 UILabel 子类上覆盖了 setPreferredMaxLayoutWidth:,但它从未被调用过。

        总结一下,我:

        • ...子类化 UILabel
        • ...并将setBounds: 覆盖为,如果尚未设置,请将preferredMaxLayoutWidth 设置为CGRectGetWidth(bounds)
        • ...在以下之前调用[super updateViewConstraints]
        • ...在获取preferredMaxLayoutWidth 用于标签大小计算之前调用layoutIfNeeded

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

        【讨论】:

          【解决方案9】:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-06-17
            • 2021-08-05
            • 2021-08-13
            • 2014-11-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多