UITableView dynamic cell heights only correct afte

2019-01-10 04:39发布

I have a UITableView with a custom UITableViewCell defined in a storyboard using auto layout. The cell has several multiline UILabels.

The UITableView appears to properly calculate cell heights, but for the first few cells that height isn't properly divided between the labels. After scrolling a bit, everything works as expected (even the cells that were initially incorrect).

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

Is there anything that I might be missing, that is causing auto layout not to be able to do its job the first few times?

I've created a sample project which demonstrates this behaviour.

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

22条回答
何必那么认真
2楼-- · 2019-01-10 04:44

Just make sure you're not setting the label text in 'willdisplaycell' delegate method of table view. Set the label text in 'cellForRowAtindexPath' delegate method for dynamic height calculation.

You're Welcome :)

查看更多
▲ chillily
3楼-- · 2019-01-10 04:45

The problem is that the initial cells load before we have a valid row height. The workaround is to force a table reload when the view appears.

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self.tableView reloadData];
}
查看更多
趁早两清
4楼-- · 2019-01-10 04:46

I have tried most of the answers to this question and could not get any of them to work. The only functional solution I found was to add the following to my UITableViewController subclass:

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

The UIView.performWithoutAnimation call is required, otherwise you will see the normal table view animation as the view controller loads.

查看更多
时光不老,我们不散
5楼-- · 2019-01-10 04:46

In Swift 3. I had to call self.layoutIfNeeded() each time I update the text of the reusable cell.

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
查看更多
Juvenile、少年°
6楼-- · 2019-01-10 04:48

I don't know this is clearly documented or not, but adding [cell layoutIfNeeded] before returning cell solves your problem.

- (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;
}
查看更多
干净又极端
7楼-- · 2019-01-10 04:49

I had same experience in one of my projects.

Why it happens?

Cell designed in Storyboard with some width for some device. For example 400px. For example your label have same width. When it loads from storyboard it have width 400px.

Here is a problem:

tableView:heightForRowAtIndexPath: called before cell layout it's subviews.

So it calculated height for label and cell with width 400px. But you run on device with screen, for example, 320px. And this automatically calculated height is incorrect. Just because cell's layoutSubviews happens only after tableView:heightForRowAtIndexPath: Even if you set preferredMaxLayoutWidth for your label manually in layoutSubviews it not helps.

My solution:

1) Subclass UITableView and override dequeueReusableCellWithIdentifier:forIndexPath:. Set cell width equal to table width and force cell's layout.

- (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) Subclass UITableViewCell. Set preferredMaxLayoutWidth manually for your labels in layoutSubviews. Also you need manually layout contentView, because it doesn't layout automatically after cell frame change (I don't know why, but it is)

- (void)layoutSubviews {
    [super layoutSubviews];
    [self.contentView layoutIfNeeded];
    self.yourLongTextLabel.preferredMaxLayoutWidth = self.yourLongTextLabel.width;
}
查看更多
登录 后发表回答