128

我有一个使用自动布局在情节提要中定义UITableView的自定义。UITableViewCell该单元格有几个 multiline UILabels

UITableView似乎可以正确计算单元格高度,但对于前几个单元格,高度没有在标签之间正确划分。滚动了一下之后,一切都按预期工作(即使是最初不正确的单元格)。

- (void)viewDidLoad {
    [super viewDidLoad]
    // ...
    self.tableView.rowHeight = UITableViewAutomaticDimension;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
    // ...
    // Set label.text for variable length string.
    return cell;
}

有什么我可能遗漏的东西,导致自动布局在前几次无法完成它的工作吗?

我创建了一个示例项目来演示这种行为。

示例项目:首次加载时来自示例项目的顶部视图。 示例项目:向下和向上滚动后的相同单元格。

4

29 回答 29

151

我不知道这是否有明确的记录,但是[cell layoutIfNeeded]在返回单元格之前添加可以解决您的问题。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
    NSUInteger n1 = firstLabelWordCount[indexPath.row];
    NSUInteger n2 = secondLabelWordCount[indexPath.row];
    [cell setNumberOfWordsForFirstLabel:n1 secondLabel:n2];

    [cell layoutIfNeeded]; // <- added

    return cell;
}
于 2014-09-22T09:58:36.183 回答
41

当其他类似的解决方案没有时,这对我有用:

override func didMoveToSuperview() {
    super.didMoveToSuperview()
    layoutIfNeeded()
}

这似乎是一个实际的错误,因为我非常熟悉 AutoLayout 以及如何使用 UITableViewAutomaticDimension,但是我仍然偶尔会遇到这个问题。我很高兴我终于找到了可以解决问题的方法。

于 2016-12-12T14:23:28.740 回答
22

添加不适用于最初滚动到视野之外的单元格[cell layoutIfNeeded]cellForRowAtIndexPath

也不用[cell setNeedsLayout].

您仍然必须将某些单元格滚动并返回到视图中才能正确调整大小。

这是非常令人沮丧的,因为大多数开发人员的动态类型、自动布局和自动调整单元格都可以正常工作——除了这个烦人的情况。这个错误影响了我所有的“高”表视图控制器。

于 2015-05-12T19:01:06.080 回答
14

我在我的一个项目中也有同样的经历。

为什么会发生?

在 Storyboard 中设计的单元格具有某些设备的宽度。例如 400 像素。例如,您的标签具有相同的宽度。当它从情节提要加载时,它的宽度为 400 像素。

这里有一个问题:

tableView:heightForRowAtIndexPath:在单元格布局之前调用它的子视图。

因此它计算了宽度为 400px 的标签和单元格的高度。但是您在有屏幕的设备上运行,例如 320 像素。而且这个自动计算的高度是不正确的。仅仅因为单元格仅在您手动 设置标签layoutSubviews之后才会发生,这也无济于事。tableView:heightForRowAtIndexPath:preferredMaxLayoutWidthlayoutSubviews

我的解决方案:

1)子类UITableView和覆盖dequeueReusableCellWithIdentifier:forIndexPath:。将单元格宽度设置为等于表格宽度并强制单元格的布局。

- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [super dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
    CGRect cellFrame = cell.frame;
    cellFrame.size.width = self.frame.size.width;
    cell.frame = cellFrame;
    [cell layoutIfNeeded];
    return cell;
}

2)子类UITableViewCellpreferredMaxLayoutWidth为您的标签手动设置layoutSubviews. 您还需要手动布局contentView,因为它不会在单元格框架更改后自动布局(我不知道为什么,但确实如此)

- (void)layoutSubviews {
    [super layoutSubviews];
    [self.contentView layoutIfNeeded];
    self.yourLongTextLabel.preferredMaxLayoutWidth = self.yourLongTextLabel.width;
}
于 2015-09-28T21:54:47.137 回答
10

以上解决方案都不适合我,有效的是这个神奇的秘诀:按以下顺序调用它们:

tableView.reloadData()
tableView.layoutIfNeeded() tableView.beginUpdates() tableView.endUpdates()

我的 tableView 数据是从 Web 服务填充的,在连接的回调中我写了上面的行。

于 2019-04-03T20:45:12.837 回答
10

我有一个类似的问题,在第一次加载时,没有计算行高,但是经过一些滚动或转到另一个屏幕后,我回到这个屏幕行被计算了。在第一次加载时,我的项目是从 Internet 加载的,在第二次加载时,我的项目首先从 Core Data 加载并从 Internet 重新加载,我注意到行高是在从 Internet 重新加载时计算的。所以我注意到,当在 segue 动画期间调用 tableView.reloadData() 时(与 push 和 present segue 相同的问题),没有计算行高。所以我在视图初始化时隐藏了 tableview 并放置了一个活动加载器以防止对用户产生丑陋的影响,我在 300 毫秒后调用 tableView.reloadData 现在问题解决了。我认为这是一个 UIKit 错误,但这种解决方法可以解决问题。

我将这些行(Swift 3.0)放在我的项目加载完成处理程序中

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300), execute: {
        self.tableView.isHidden = false
        self.loader.stopAnimating()
        self.tableView.reloadData()
    })

这解释了为什么对于某些人来说,在 layoutSubviews 中放置一个 reloadData 来解决问题

于 2016-12-26T21:11:00.120 回答
7

我已经尝试了这个问题的大部分答案,但无法让它们中的任何一个起作用。我发现的唯一功能性解决方案是将以下内容添加到我的UITableViewController子类中:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    UIView.performWithoutAnimation {
        tableView.beginUpdates()
        tableView.endUpdates()
    }
}

UIView.performWithoutAnimation调用是必需的,否则您将在视图控制器加载时看到正常的表格视图动画。

于 2017-05-30T19:44:01.573 回答
5

在我的情况下, UILabel 的最后一行在第一次显示单元格时被截断。它发生得非常随机,正确调整大小的唯一方法是将单元格滚动出视图并将其带回。我尝试了迄今为止显示的所有可能的解决方案(layoutIfNeeded..reloadData),但对我没有任何帮助。诀窍是将“自动收缩”设置为最小字体比例(对我来说是 0.5)。试试看

于 2015-05-01T13:36:38.217 回答
5

为表格视图自定义单元格中的所有内容添加约束,然后在 viewdid 加载中估计表格视图行高并将行高设置为自动尺寸:

    override func viewDidLoad() {
    super.viewDidLoad()

    tableView.estimatedRowHeight = 70
    tableView.rowHeight = UITableViewAutomaticDimension
}

要解决该初始加载问题,请在自定义表格视图单元格中应用 layoutIfNeeded 方法:

class CustomTableViewCell: UITableViewCell {

override func awakeFromNib() {
    super.awakeFromNib()
    self.layoutIfNeeded()
    // Initialization code
}
}
于 2016-08-08T16:14:46.040 回答
3

设置preferredMaxLayoutWidth对我有帮助。我添加了

cell.detailLabel.preferredMaxLayoutWidth = cell.frame.width

在我的代码中。

另请参阅UILabel和http://openradar.appspot.com/17799811中的单行文本需要两行

于 2015-06-09T00:32:41.940 回答
3

上述解决方案均无效,但以下建议组合有效。

必须在 viewDidLoad() 中添加以下内容。

DispatchQueue.main.async {

        self.tableView.reloadData()

        self.tableView.setNeedsLayout()
        self.tableView.layoutIfNeeded()

        self.tableView.reloadData()

    }

reloadData、setNeedsLayout 和 layoutIfNeeded 的上述组合有效,但没有其他任何效果。不过,可能特定于项目中的单元格。是的,必须调用 reloadData 两次才能使其工作。

还要在 viewDidLoad 中设置以下内容

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = MyEstimatedHeight

在 tableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath)

cell.setNeedsLayout()
cell.layoutIfNeeded() 
于 2017-04-22T15:17:55.720 回答
3

在 ios 10 和 ios 11 上调用cell.layoutIfNeeded()insidecellForRowAt对我有用,但在 ios 9 上没有。

为了在 ios 9 上完成这项工作,我打电话cell.layoutSubviews()并成功了。

于 2018-03-28T17:02:23.623 回答
2

就我而言,单元格中的堆栈视图导致了问题。这显然是一个错误。一旦我删除它,问题就解决了。

于 2017-05-26T10:53:11.703 回答
2

我尝试了此页面中的所有解决方案,但取消选中使用大小类然后再次检查它解决了我的问题。

编辑:取消选中大小类会导致故事板上出现很多问题,所以我尝试了另一种解决方案。viewDidLoad我在我的视图控制器和viewWillAppear方法中填充了我的表格视图。这解决了我的问题。

于 2015-09-25T14:11:36.723 回答
2

附上截图供您参考对我来说,这些方法都不起作用,但我发现标签Preferred Width在 Interface Builder 中有一个明确的集合。删除它(取消选中“显式”),然后UITableViewAutomaticDimension按预期使用。

于 2017-02-24T18:28:43.337 回答
1

就我而言,我正在其他周期进行更新。所以 tableViewCell 的高度是在设置 labelText 之后更新的。我删除了异步块。

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
     let cell = tableView.dequeueReusableCell(withIdentifier:Identifier, for: indexPath) 
     // Check your cycle if update cycle is same or not
     // DispatchQueue.main.async {
        cell.label.text = nil
     // }
}
于 2017-04-04T05:52:14.663 回答
1

仅适用于 iOS 12+,2019 年以后...

一个持续不断的例子,表明苹果偶尔会出现奇怪的无能,问题会持续数年。

情况似乎确实如此

        cell.layoutIfNeeded()
        return cell

会修复它。(你当然会失去一些性能。)

这就是苹果的生活。

于 2019-10-10T20:01:54.887 回答
1

问题是初始单元格在我们有一个有效的行高之前加载。解决方法是在视图出现时强制重新加载表。

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self.tableView reloadData];
}
于 2017-11-20T13:44:49.350 回答
1

我有调整标签大小的问题,所以我只需要
在设置文本后做 chatTextLabel.text = chatMessage.message chatTextLabel?.updateConstraints()

// 完整代码

func setContent() {
    chatTextLabel.text = chatMessage.message
    chatTextLabel?.updateConstraints()

    let labelTextWidth = (chatTextLabel?.intrinsicContentSize().width) ?? 0
    let labelTextHeight = chatTextLabel?.intrinsicContentSize().height

    guard labelTextWidth < originWidth && labelTextHeight <= singleLineRowheight else {
      trailingConstraint?.constant = trailingConstant
      return
    }
    trailingConstraint?.constant = trailingConstant + (originWidth - labelTextWidth)

  }
于 2016-05-20T13:54:41.043 回答
1

只要确保您没有在表格视图的“willdisplaycell”委托方法中设置标签文本。在 'cellForRowAtindexPath' 委托方法中设置标签文本以进行动态高度计算。

别客气 :)

于 2017-05-25T06:12:15.153 回答
0

在 Swift 3 中。每次更新可重用单元格的文本时,我都必须调用 self.layoutIfNeeded() 。

import UIKit
import SnapKit

class CommentTableViewCell: UITableViewCell {

    static let reuseIdentifier = "CommentTableViewCell"

    var comment: Comment! {
        didSet {
            textLbl.attributedText = comment.attributedTextToDisplay()
            self.layoutIfNeeded() //This is a fix to make propper automatic dimentions (height).
        }
    }

    internal var textLbl = UILabel()

    override func layoutSubviews() {
        super.layoutSubviews()

        if textLbl.superview == nil {
            textLbl.numberOfLines = 0
            textLbl.lineBreakMode = .byWordWrapping
            self.contentView.addSubview(textLbl)
            textLbl.snp.makeConstraints({ (make) in
                make.left.equalTo(contentView.snp.left).inset(10)
                make.right.equalTo(contentView.snp.right).inset(10)
                make.top.equalTo(contentView.snp.top).inset(10)
                make.bottom.equalTo(contentView.snp.bottom).inset(10)
            })
        }
    }
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let comment = comments[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: CommentTableViewCell.reuseIdentifier, for: indexPath) as! CommentTableViewCell
        cell.selectionStyle = .none
        cell.comment = comment
        return cell
    }

commentsTableView.rowHeight = UITableViewAutomaticDimension
    commentsTableView.estimatedRowHeight = 140
于 2017-01-13T06:51:59.920 回答
0

我遇到了这个问题,并通过将我的视图/标签初始化代码 FROM tableView(willDisplay cell:)TO来修复它tableView(cellForRowAt:)

于 2017-06-12T03:18:06.170 回答
0

我找到了一个很好的解决方法。由于在单元格可见之前无法计算高度,因此您需要做的就是在计算单元格大小之前滚动到单元格。

tableView.scrollToRow(at: indexPath, at: .bottom, animated: false)
let cell = tableView.cellForRow(at: indexPath) ?? UITableViewCell()
let size = cell.systemLayoutSizeFitting(CGSize(width: frame.size.width, height: UIView.layoutFittingCompressedSize.height))
于 2021-01-22T01:38:12.800 回答
0

在我的情况下,单元格高度的问题发生在加载初始表格视图之后,并且发生了用户操作(点击单元格中具有更改单元格高度效果的按钮)。除非我这样做,否则我无法让单元格改变其高度:

[self.tableView reloadData];

我确实尝试过

[cell layoutIfNeeded];

但这没有用。

于 2016-11-04T20:23:05.887 回答
0

快速而肮脏的方式。像这样的双重 reloadData:

tableView.reloadData()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: { [weak self] in
   self?.tableView.reloadData()
})
于 2021-09-06T16:04:17.483 回答
0

覆盖 prepareForReuse() 并在你的单元格类中将 lable 设置为 nil

override func prepareForReuse() {
        super.prepareForReuse()
        self.detailLabel.text = nil
        self.titleLabel.text = nil
    }
于 2021-08-24T11:50:33.560 回答
0

iOS 11+

默认情况下,表格视图使用估计的高度。这意味着 contentSize 与最初的估计值一样。如果您需要使用 contentSize,您需要通过将 3 个估计高度属性设置为零来禁用估计高度:tableView.estimatedRowHeight = 0 tableView.estimatedSectionHeaderHeight = 0 tableView.estimatedSectionFooterHeight = 0

    public func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
        return CGFloat.leastNormalMagnitude
    }

    public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return CGFloat.leastNormalMagnitude
    }

    public func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
        return CGFloat.leastNormalMagnitude
    }
于 2021-03-03T16:58:07.907 回答
0

另一种解决方案

@IBOutlet private weak var tableView: UITableView! {
    didSet {
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = UITableView.automaticDimension
    }
}

extension YourViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        UITableView.automaticDimension
    }
}

然后你不需要 layoutSabviews

于 2021-04-08T09:28:50.320 回答
-1
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{


//  call the method dynamiclabelHeightForText
}

使用上述方法动态返回行的高度。并将相同的动态高度分配给您正在使用的标签。

-(int)dynamiclabelHeightForText:(NSString *)text :(int)width :(UIFont *)font
{

    CGSize maximumLabelSize = CGSizeMake(width,2500);

    CGSize expectedLabelSize = [text sizeWithFont:font
                                constrainedToSize:maximumLabelSize
                                    lineBreakMode:NSLineBreakByWordWrapping];


    return expectedLabelSize.height;


}

此代码可帮助您找到标签中显示的文本的动态高度。

于 2014-09-22T12:43:06.460 回答