使用自动布局的UITableViewCell中的UILabel高度计算错误

22
我有一个高度固定为100点的UITableView,其中单元格是在xib文件中创建的,使用3个约束将UILabel固定到单元格的contentView的左侧、右侧和顶部边缘。标签的垂直拉伸优先级设置为1000,因为我希望单元格的高度尽可能小。
当xib文件中单元格的宽度设置为320点时,在iPhone上与tableView的宽度相同时,自动布局按预期工作。但是,当我将单元格的宽度设置为小于320点时,就会出现意外结果。(我想在具有不同宽度的tableViews中使用相同的单元格,例如在通用应用程序中)
例如:当我将宽度设置为224点,并给标签设置一个在该宽度下占用2行的文本时,标签的高度将增加以适应这2行,但是当单元格被调整大小以适应该宽度的tableView时,文本仅占用1行,但标签的高度仍保持在2行。
我在GitHub上放置了一个示例项目以演示问题: https://github.com/bluecrowbar/CellLayout 有没有办法使UILabel始终调整大小以紧贴其文本内容?

在标签上设置preferredMaxLayoutWidth似乎可以产生效果,但是我不确定在哪里计算和设置它的值。 - Steven Vandeweghe
6个回答

72

在单元格子类中添加以下内容:

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

我在http://useyourloaf.com/blog/2014/02/14/table-view-cells-with-varying-row-heights.html上找到了这个。

更新1:此答案适用于 iOS 7。自 iOS 8 以来,我发现表格视图单元格中的自动布局非常不可靠,即使是非常简单的布局也一样。经过大量实验,我(基本上)回到了手动布局和手动计算单元格高度。

更新2:我在 iOS 9 上进行了一些测试,似乎UITableViewAutomaticDimension终于像广告中宣传的那样工作了。耶!


1
这是UILabel的一个bug吗? - Bimawa
@StevenVandeweghe,看起来它修复了我的宽度,但破坏了我的行高...有什么想法吗?(即使我手动设置高度约束)。 - Dima Deplov
1
我的原始答案是针对iOS 7的。我发现自动布局在表格视图单元格中非常不可靠,自从iOS 8以来,即使是非常简单的布局也是如此。经过大量实验后,我(大多数情况下)回到了手动布局和手动计算单元格高度的方式。 - Steven Vandeweghe
可以确认UITableViewAutomaticDimension在iOS 9中已经修复。终于! - Sakiboy
1
对我没用,@Bimawa 是的,这似乎是UILabel的问题,因为contentView的宽度似乎很完美,但标签不正确。 - Amit Battan
显示剩余6条评论

17

愚蠢的错误!我在这个问题上浪费了将近一天的时间,最终是通过Steven Vandewghe的解决方案解决了它。

Swift版本:

override func layoutSubviews() {
    super.layoutSubviews()
    self.contentView.layoutIfNeeded()
    self.myLabel.preferredMaxLayoutWidth = self.myLabel.frame.size.width
}

2
一样的情况。我浪费了几个小时,但是最终找到了上面的解决方案。唯一的问题是为什么需要self.contentView.layoutIfNeeded()才能得到正确的大小? - NNikN

3
由于您限制了标签的width,因此intrinsicContentSize会自动适应该width并调整height。这就造成了一个先有鸡还是先有蛋的问题:
  1. 单元格的自动布局结果依赖于标签的intrinsicContentSize
  2. 标签的intrinsicContentSize依赖于标签的width
  3. 标签的width依赖于单元格的自动布局结果
因此,情况是这样的:单元格的布局仅在一次计算中进行,该计算以XIB文件中的静态宽度为基础(取决于(2)),从而导致标签的height错误。
您可以通过迭代来解决此问题。也就是说,在第一次计算设置标签的width之后,重复进行自动布局计算。在您的自定义单元格中使用以下代码即可:
- (void)drawRect:(CGRect)rect
{
    CGSize size = self.myLabel.bounds.size;
    // tell the label to size itself based on the current width
    [self.myLabel sizeToFit];
    if (!CGSizeEqualToSize(size, self.myLabel.bounds.size)) {
        [self setNeedsUpdateConstraints];
        [self updateConstraintsIfNeeded];
    }
    [super drawRect:rect];
}

原始解决方案 不可靠:

- (void)layoutSubviews
{
    [super layoutSubviews];
    // check for need to re-evaluate constraints on next run loop
    // cycle after the layout has been finalized
    dispatch_async(dispatch_get_main_queue(), ^{
        CGSize size = self.myLabel.bounds.size;
        // tell the label to size itself based on the current width
        [self.myLabel sizeToFit];
        if (!CGSizeEqualToSize(size, self.myLabel.bounds.size)) {
            [self setNeedsUpdateConstraints];
            [self updateConstraintsIfNeeded];
        }
    });
}

这种方法的问题是你能看到重新布局。尝试在iPad模拟器中运行应用程序,你就会明白我的意思。 - Steven Vandeweghe
@StevenVandeweghe 嗯。只有在iPad Retina模拟器中才会出现这种情况。其他模拟器都正常工作。可能是模拟器的一个bug。 - Timothy Moose
更新的答案。将代码移动到drawRect中避免了dispatch_async,据我所知,它可以可靠地工作。 - Timothy Moose
1
我之前没有想到过 drawRect:。在 drawRect 中,这也可以工作: [super drawRect:rect]; self.myLabel.preferredMaxLayoutWidth = self.myLabel.frame.size.width;。无论如何,我已经打开了一个 DTS,只是为了确保苹果认为这是最好的解决方案。 - Steven Vandeweghe
@TimothyMoose 谢谢。讲解得非常好。 - Serge Maslyakov

1
我正在使用带有iOS 12的XCode 10,但在首次呈现表格时仍然存在自动布局问题,其中单元格未被赋予正确的高度。Timothy Moose的答案对我没有解决问题,但基于他的解释,我想出了一种适用于我的解决方案。
我子类化UITableViewController并重写viewDidLayoutSubviews消息以检查宽度更改,如果宽度确实更改,则强制进行表格更新。这可以在视图呈现之前解决问题,使其看起来比我的其他努力更美观。
首先,在您的自定义UITableViewController子类中添加一个属性来跟踪先前的宽度:
@property (nonatomic) CGFloat previousWidth;

然后重写 viewDidLayoutSubviews 方法来检查宽度的变化:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    CGFloat width = self.view.frame.size.width;
    if (self.previousWidth != width) {
        self.previousWidth = width;
        [self.tableView beginUpdates];
        [self.tableView endUpdates];
    }
}

这解决了我的表格单元格有时在初始时给出错误高度的问题。

1

我知道这是一个旧问题,但也许这个UILabel子类对某些人也有帮助:

class AutoSizeLabel: 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()
    }
}

注意:这也适用于当您的UILabel在StackView中时无法正确调整大小的情况。

0

我通常会在viewDidLoad()中添加这两行代码

    self.tableView.rowHeight = UITableViewAutomaticDimension
    self.tableView.estimatedRowHeight = 96

这将自动调整单元格大小


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接