iPhone - When to Calculate Heightforrowatindexpath for a Tableview When Each Cell Height Is Dynamic

iOS dynamical height of UITableViewCell and heightForRowAtIndexPath

If you are using iOS 8 and above, you do not need to calculate height dynamically. Auto layout will do all for you. But if you are using lower than IOS 8, you need to calculate cell height.

For IOS 8:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}

And add below code in your controller:

tableView.estimatedRowHeight = 400.0
tableView.rowHeight = UITableViewAutomaticDimension

Where estimatedRowHeight should be max height which can be for your cell.

Thanks

How to calculate tableView row height and pass value to heightForRowAtIndexPath

If your cell view is really very complex and every component's height are depending on data source. You can try to create the view in heightForRowIndexPath method and then cache the created view to a dictionary in your view controller and use it directly in cellForRowAtIndexPath. In this way you only need to create the view once when user scrolling the table. If the datasource is not changing very frequently, you can reuse the cached view in heightForRowIndexPath as well.

And if the tableview has a lot of rows, you should return an approximate value for height in estimatedHeightForRowAtIndexPath to speed up the loading process of the view controller. Otherwise during loading tableview, it will try to calculate all row's height which may requires a lot of time.

But I really don't think your cell would be so complex. If only some UITextLabels that depends on datasource for the height, you can simply only calculate the height for the label, then add it to other components' height which is fixed.

Dynamic UITableView Cell Height Based on Contents

The best way that I've found for dynamic height is to calculate the height beforehand and store it in a collection of some sort (probably an array.) Assuming the cell contains mostly text, you can use -[NSString sizeWithFont:constrainedToSize:lineBreakMode:] to calculate the height, and then return the corresponding value in heightForRowAtIndexPath:

If the content is constantly changing, you could implement a method that updated the array of heights when new data was provided.

UITableView flexible/dynamic heightForRowAtIndexPath

Yes, I agree it will already allocate the cell, but the heightForRowAtIndexPath delegate method is only called for the cells that will be visible so the cell will be allocated anyway.

This is incorrect. The table view needs to call heightForRowAtIndexPath (if it's implemented) for all rows that are in the table view, not just the ones currently being displayed. The reason is that it needs to figure out its total height to display the correct scroll indicators.

UITableView dynamic cell heights only correct after some scrolling

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

how do I calculate heightForRowAtIndexPath when the cell is not even constructed yet?

When a UITableView is created and whenever you send it a reloadData message, the datasource is sent one heightForRowAtIndexPath message for each cell. So if your table has 30 cells, that message gets sent 30 times.

Say only six of those 30 cells are visible on screen. In that case, when created and when you send it a reloadData message, the UITableView will send one cellForRowAtIndexPath message per visible row, i.e. that message gets sent six times.

Why do Apple implement it like this? Part of the reason is that it's almost always cheaper to calculate the height of a row than it is to build and populate a whole cell. And given that in many tables the height of every cell will be identical, it is often vastly cheaper. And part of the reason is because iOS needs to know the size of the whole table: this allows it to create the scroll bars and set it up on a scroll view etc.

If your row heights vary in size because they hold varying amounts of text, you can use one of the sizeWithFont: methods on the relevant string to do the calculations. This is quicker than building a view and then measuring the result. Note, that if you change the height of a cell, you will need to either reload the whole table (with reloadData - this will ask the delegate for every height, but only ask for visible cells) OR selectively reload the rows where the size has changed.

Additional material
If I understand the follow up question in the comment, the following may help:

If you are implementing an editing mode, then it's not uncommon to need to change the height of your table rows. For example, you may have text in your table rows and when they cells become narrower - to make space for the delete circles on the right - you may want some of the cells to become taller to accommodate the text. The basic approach here is to:

  • Make sure the tableView:heightForRowAtIndexPath: method knows whether your are in editing mode or not. (It can ask the tableView using isEditing.) And then get the method to return the right height, depending on whether you are in editing mode or not.

  • In your setEditing:animated: method in the UITableViewController (or a UIViewController, whichever you are using - there are some differences depending what you use, so it's worth checking the documentation carefully) send a reloadData message to the tableView after you have changed its state. This will force the tableView to grab the heights of every row and it will refetch the cells for the visible rows. The tableView handles making cells narrower when you enter editing mode, but if you want to do more work on the layout, do it in tableView:cellForRowAtIndex:. As noted above, the general strategy is to find a means of calculating the height that is quick. With text sizeWithFont: (and its variants) can do it. If you have images etc., then you can grab their dimensions and do some sums.

  • In addition to those steps you may also want to scroll the tableView a bit after switching modes. If the heights of your rows are different, then you will end up in the wrong position in the table after switching mode. An approach I have taken here is to use performSelector:withObject:afterDelay after I've reloaded the table to call a method that does the scroll adjusting. You need to use the delay, to allow time for the tableView to collect the new heights and the new table cells. (There may be a smarter way of doing this.) I do some sums to make the scroll adjustment based on the difference between the origin.y of the tableView:cellForRowAtIndexPath: of the cell first visible row on screen before and after the reload. So, for e.g., to get the position before the pre-load, something a bit like this.

    CGPoint offset = [[self tableView] contentOffset];
    NSIndexPath* indexPath = [[self tableView] indexPathForRowAtPoint:CGPointMake(0,offset.y)];
    CGFloat preCellOffset = [[[self tableView] cellForRowAtIndexPath:indexPath] origin].y;


Related Topics



Leave a reply



Submit