Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practices for drawing dynamic UITableView row height

Possibly a duplicate but I couldn't find a specific question on SO, so here it is.

I'm curious about dynamically changing heights for all rows, typically, because you don't know the length of an NSString that's used for a label.

I know you must use this delegate method to change the row heights:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

The problem is this delegate method is called BEFORE the cell is created (i.e. called before cellForRowAtIndexPath).

So, what I've thought of is to create a mock cell in viewWillAppear and a method that adds cell heights to an array that maps to the table view's data source (which in my case is also an array).

viewWillAppear implements this one important method to get the height:

[NSString sizeWithFont: constrainedToSize: lineBreakMode:]

Then in heightForRowAtIndexPath I can return the cell height like so:

//cellHeights is an ivar populated in viewWillAppear
return [[cellHeights objectAtIndex:indexPath.row] floatValue];

I was wondering if there was a better way to dynamically change the row height?

I realize this will degrade performance for a large number of rows (greater than 1000, I believe). But in my case, my rows won't ever come close to that number. So the performance hit is negligible.

Thanks in advance!

like image 394
David Nix Avatar asked Sep 27 '11 23:09

David Nix


1 Answers

Great question! In fact, I did something similar in some of my applications.

I can think of a couple of alternatives, but all of these are along the same theme. You could also just to use sizeWithFont: inside of heightForRowAtIndexPath: and do away with the array. In that case, you might take a performance hit for recalculating the size each time, if that operation is expensive.

You could do "lazy loading" of the cellHeights array inside of heightForRowAtIndexPAth: so it might look something like this:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if ([cellHeights objectAtIndex:indexPath.row] == nil) {
        ... calculate height and store it in the array at the correct index...
    }

    return [[cellHeights objectAtIndex:indexPath.row] floatValue];
}

The advantage I am thinking of here is that you will only calculate the heights for cells that are definitely going to be loaded. If you do the calculation in viewWillAppear, I guess you end up doing it for every cell, regardless of whether it is displayed?

Finally, you could put the size in your data model itself. If it is, for example, an array of strings, you could make a class that has two properties: a string and a "representationSize" property. Then you can recalculate the size of the string each time the value of the string is changed. Then, there would just be one array, not two, that maps onto your data source, filled with a data class containing both the string and display size of the string, and the value would be calculated when the string changes, not at all once when the view appears.

Anyway, I would love to hear some comments about these various approaches.

like image 120
Matthew Gillingham Avatar answered Oct 27 '22 00:10

Matthew Gillingham