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

时间:2014-09-22 09:48:56

标签: ios uitableview ios8 autolayout ios-autolayout

我有一个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;
}

是否有任何我可能遗漏的东西,导致自动布局在前几次无法完成其工作?

我创建了sample project来证明这种行为。

Sample project: Top of table view from sample project on first load. Sample project: Same cells after scrolling down and back up.

28 个答案:

答案 0 :(得分:120)

我不知道这是否有明确记录,但在返回单元格之前添加[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;
}

答案 1 :(得分:31)

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

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

这看起来像是一个真正的bug,因为我对AutoLayout非常熟悉以及如何使用UITableViewAutomaticDimension,但是我偶尔会遇到这个问题。我很高兴终于找到了可以解决的问题。

答案 2 :(得分:20)

[cell layoutIfNeeded]中添加cellForRowAtIndexPath对于最初滚动到视野外的单元格不起作用。

也没有将其与[cell setNeedsLayout]作为前缀。

您仍然需要将某些单元格滚出并返回视图,以便它们正确调整大小。

这非常令人沮丧,因为大多数开发人员都有动态类型,自动布局和自我调整单元正常工作 - 除了这个烦人的情况。这个错误影响了我所有的更高的&#34;表视图控制器。

答案 3 :(得分:12)

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

为什么会这样?

在故事板中设计的单元格,某些设备有一些宽度。例如400px。例如,您的标签具有相同的宽度。从故事板加载时,它的宽度为400px。

这是一个问题:

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

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

我的解决方案:

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

答案 4 :(得分:8)

我有一个类似的问题,在第一次加载时,没有计算行高,但在一些滚动或转到另一个屏幕后我回到这个屏幕行计算。在第一次加载时,我的项目从互联网加载,在第二次加载时,我的项目首先从Core Data加载并从互联网上重新加载,我注意到行高度是在从互联网重新加载时计算的。所以我注意到当在segue动画期间调用tableView.reloadData()时(与push和present 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()
    })

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

答案 5 :(得分:5)

在我的情况下,第一次显示单元格时,UILabel的最后一行被截断。它发生得非常随机,正确调整大小的唯一方法是将单元格滚出视图并将其恢复原状。 我尝试了到目前为止显示的所有可能的解决方案(layoutIfNeeded..reloadData),但没有任何对我有用。诀窍是将&#34; Autoshrink&#34; 设置为 Minimuum字体比例(对我而言为0.5)。试一试

答案 6 :(得分:4)

为表格视图自定义单元格中的所有内容添加约束,然后在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
}
}

答案 7 :(得分:4)

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

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

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

答案 8 :(得分:2)

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

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

答案 9 :(得分:2)

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

cell.detailLabel.preferredMaxLayoutWidth = cell.frame.width

在我的代码中。

另请参阅Single line text takes two lines in UILabelhttp://openradar.appspot.com/17799811

答案 10 :(得分:2)

Attatching screenshot for your referance对我来说,这些方法都没有奏效,但我发现标签在Interface Builder中有明确的Preferred Width设置。删除它(取消选中“Explicit”)然后使用UITableViewAutomaticDimension按预期工作。

答案 11 :(得分:2)

cell.layoutIfNeeded()内调用cellForRowAt在ios 10和ios 11上为我工作,但在ios 9上没有。

也可以在ios 9上完成这项工作,我打电话给cell.layoutSubviews()然后就可以了。

答案 12 :(得分: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)

  }

答案 13 :(得分: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
     // }
}

答案 14 :(得分:1)

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

苹果偶尔出现的异常无能的例子,问题持续了数年之久。

似乎确实如此

        cell.layoutIfNeeded()
        return cell

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

这就是苹果的生活。

答案 15 :(得分:1)

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

欢迎你:)

答案 16 :(得分: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
    }

答案 17 :(得分: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

答案 18 :(得分: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))

答案 19 :(得分:0)

对于仍然面临此问题的任何人,请尝试以下魔术路线

  tableview.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)

只需确保在用数据填充表之后调用此函数,这样就可以确保第0节和第0行始终可用,否则它将崩溃,但是您可以先检查其可用性,希望对您有所帮助。

答案 20 :(得分:0)

上面的解决方案都不适合我,有效的方法是这种魔术配方: 依次呼叫他们:

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

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

答案 21 :(得分:0)

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

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

答案 22 :(得分:0)

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

答案 23 :(得分:0)

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

答案 24 :(得分: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() 

答案 25 :(得分: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

答案 26 :(得分:0)

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

[self.tableView reloadData];

我确实尝试了

[cell layoutIfNeeded];

但这没效果。

答案 27 :(得分:0)

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


}

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