UITableView动态单元格高度仅在某些滚动后才正确


117

我有一个使用自动布局在情节提要中定义UITableView的自UITableViewCell定义。该单元具有多个多行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;
}

有什么我可能会丢失的东西,导致自动布局无法在最初的几次中完成它的工作吗?

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

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


1
我也面临这个问题,但是以下答案对我不起作用,对此有任何帮助
Shangari C

1
面对同样的问题,为单元格添加layoutIfNeeded也不能正常工作吗?任何建议
马克斯

1
很棒的地方。这在2019年仍然是一个问题:/
Fattie

我在以下链接中找到了帮助我的东西-这是对可变高度表视图单元格的相当全面的讨论:stackoverflow.com/a/18746930/826946
Andy Weinstein

Answers:


139

我不知道是否有明确记录,但是[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;
}

3
我确实想知道,为什么只需要第一次?如果我将呼叫转到-layoutIfNeeded该单元格的,它也一样有效-awakeFromNib。我宁愿只layoutIfNeeded在知道为什么必要的地方打电话。
blackp 2014年

2
为我工作!我很想知道为什么要这样做!
Mustafa 2015年

如果您渲染大小类(与任何X都不同)
Andrei Konstantinov

@AndreyKonstantinov是的,它不适用于尺码课程,我如何使其与尺码课程一起使用?
2015年

它在没有尺寸等级的情况下有效,是否有尺寸等级的解决方案?
Yuvrajsinh

38

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

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

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


1
比接受的答案更好,因为仅当从xib而不是每个单元格显示加载单元格时才调用它。代码行也少得多。
罗伯特·瓦格斯塔夫

3
别忘了致电super.didMoveToSuperview()
cicerocamargo

苹果说@cicerocamargo didMoveToSuperview:“此方法的默认实现不执行任何操作。”
伊万·史密丹

4
@IvanSmetanin不管默认实现不执行任何操作都没有关系,您仍应调用super方法,因为它将来可能会真正执行某些操作。
TNguyen '19

(顺便说一句,您应该在那上面绝对调用super。我从未见过一个项目,整个团队的子类化不会超过一次,因此,当然,您必须确保将它们全部收集起来。独立的问题正是TNguyen说的)。
Fattie

22

添加[cell layoutIfNeeded]cellForRowAtIndexPath不工作对于最初滚出的视图细胞。

也不会以开头[cell setNeedsLayout]

您仍然必须滚动某些单元格,然后再回到视图中以使其正确调整大小。

这非常令人沮丧,因为大多数开发人员都使Dynamic Type,AutoLayout和Self-Sizing Cells正常工作—除了这种烦人的情况。这个错误影响了我所有的“ taller”表视图控制器。


我也是。当我第一次滚动时,单元格的大小是44px。如果滚动以使该单元不可见并返回,则它的大小正确。您找到解决方案了吗?
最多

谢谢@ ashy_32bit这也为我解决了。
ImpurestClub 2015年

这似乎是一个普通的错误,@ Scenario对吗?可怕的东西。
Fattie 2015年

3
解决方法是使用[cell layoutSubviews]代替而不是layoutIfNeeded。请参阅stackoverflow.com/a/33515872/1474113
ypresto

14

我在一个项目中有相同的经验。

为什么会发生?

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

这是一个问题:

tableView:heightForRowAtIndexPath: 在单元格布局之前称为子视图。

因此,它为宽度为400px的标签和单元格计算了高度。但是您在带有屏幕的设备上运行,例如320px。并且此自动计算的高度不正确。仅因为单元格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)子类UITableViewCell。在中preferredMaxLayoutWidth为标签手动设置layoutSubviews。另外,您还需要手动布局contentView,因为它不会在更改单元格后自动布局(我不知道为什么,但是是)

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

1
遇到此问题时,我还需要确保表视图的框架正确,因此在填充表视图之前(在viewDidLoad中),我调用了[self.tableview layoutIfNeeded]。
GK100 '16

我从未停止过思考这与宽度不正确有关。cell.frame.size.width = tableview.frame.width然后cell.layoutIfNeeded()cellForRowAt函数中帮了我
Cam Connor

10

我有一个类似的问题,在第一次加载时,未计算行高,但是经过一些滚动或转到另一个屏幕后,我回到此屏幕,才计算行数。第一次加载时,我的项目是从Internet加载的,第二次加载时,我的项目是首先从Core Data加载的,然后从Internet重新加载的,我注意到行高度是从Internet重新加载时计算出来的。因此,我注意到在segue动画期间调用tableView.reloadData()时(与推入和当前segue存在相同问题),未计算行高。因此,我在视图初始化时隐藏了tableview,并放置了一个活动加载器以防止对用户造成难看的影响,并在300ms之后调用tableView.reloadData,现在问题已解决。我认为这是一个UIKit错误,但是这种解决方法可以解决问题。

我将这些代码行(Swift 3.0)放入了项目加载完成处理程序

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

这说明了为什么对于某些人来说,将reloadData放入layoutSubviews中可以解决此问题


这也是唯一为我工作的WA。除非我不使用isHidden,而是在添加数据源时将表的alpha设置为0,然后在重新加载后将其设置为1。
PJ_Finnegan '17

6

以上解决方案均不适用于我,有效的方法是此魔术配方:按以下顺序调用它们:

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

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


1
对于Swift 5,即使在收藏夹视图中也可以使用,上帝保佑您,兄弟。
Govani Dhruv Vijaykumar,

对我来说,这返回了正确的单元格高度和布局,但其中没有数据
Darrow Hartman

在这些行之后尝试setNeedsDisplay()
JAHelia

5

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


这对我有用,而没有应用上面列出的任何其他解决方案。很棒的发现。
mckeejm 2015年

2
但这会自动收缩文本...为什么要在表格视图中使用不同大小的文本?看起来糟透了。
lucius degeer

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

重要的是estimatedRowHeight,将值设置为> 0而不是UITableViewAutomaticDimension(-1),否则自动行高将不起作用。
PJ_Finnegan '17

5

我已经尝试了该问题的大部分答案,但无法解决任何问题。我发现的唯一功能解决方案是在我的UITableViewController子类中添加以下内容:

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

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


工作,绝对比两次调用reloadData更好
Jimmy George Thomas

2
黑魔法。这样做viewWillAppear对我不起作用,但这样做对我viewDidAppear有用。
马丁

当在viewDidAppear中实现时,此解决方案“对我有用”。这绝对不是最佳的用户体验,因为随着正确调整大小,表格内容跳来跳去。
richardpiazza

令人惊讶的是我尝试了很多例子,但失败了,你是恶魔
沙克尔·艾哈迈德

与@martin相同
AJ Hernandez


3

在ios 10和ios 11上进行cell.layoutIfNeeded()内部调用cellForRowAt对我有用,但在ios 9上不起作用。

为了在ios 9上也能做到这一点,我打电话给我cell.layoutSubviews(),它成功了。


2

截图以供参考对我来说,这些方法都不起作用,但是我发现该标签Preferred Width在Interface Builder中具有显式设置。将其删除(取消选中“显式”),然后UITableViewAutomaticDimension按预期使用。


2

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

编辑:取消选中大小类会导致情节提要上出现很多问题,因此我尝试了另一种解决方案。我在视图控制器viewDidLoadviewWillAppear方法中填充了表视图。这解决了我的问题。


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)

  }

1

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

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
     // }
}

我也有一个异步块,但这是必需的吗?别无选择了吗?
Nazar Medeiros

1

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

别客气 :)



1

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

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self.tableView reloadData];
}

这是远比我更好的解决方案。它没有校正100%的像元,但高达80%的像元实际尺寸已被接受。非常感谢
TheTravloper

1

仅适用于iOS 12+,自2019年起...

苹果公司偶尔出现的异常无能的例子,这是一个持续多年的问题,这是一个持续不断的例子。

看来确实是这样

        cell.layoutIfNeeded()
        return cell

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

苹果公司就是这样。


0

就我而言,单元格高度的问题发生在加载初始表视图之后,并且发生了用户操作(点击单元格中的按钮会改变单元格高度)。除非执行以下操作,否则我无法使单元更改其高度:

[self.tableView reloadData];

我确实尝试过

[cell layoutIfNeeded];

但这没用。


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

0

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

必须在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() 

我的类似作品reloadData/beginUpdates/endUpdates/reloadData也在起作用;reloadData 必须再次调用。不需要将其包装在中async
射线

0

我遇到了这个问题,并通过移动我的看法/标签初始化代码从固定它tableView(willDisplay cell:)TO tableView(cellForRowAt:)


不是初始化单元格的推荐方法。willDisplay将会有比更好的性能cellForRowAt。仅使用最新的来实例化正确的单元格。
马丁

2年后,我正在阅读我写下的旧评论。这是一个坏建议。即使willDisplay性能更好,也建议在cellForRowAt自动布局时初始化单元格UI 。实际上,布局是由UIKit cellForRowAt等之后将在 willDisplay 之前计算的。因此,如果您的单元格高度取决于其内容,请在中初始化标签内容(或其他内容)cellForRowAt
马丁

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


}

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


实际上,只要设置了tableView.estimatedRowHeight和tableView.rowHeight = UITableViewAutomaticDimension属性,就不需要Ramesh。另外,请确保自定义单元格对各种小部件和contentView具有适当的约束。
BonanzaDriver 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.