Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does heightForRowAtIndexPath: come before cellForRowAtIndexPath:?

Almost every time I write an app for a client I have to implement some kind of 'hack' to get UITableViewCells to dynamically become the right height. Depending on the content of the cells this can vary in difficulty.

I usually end up running through code that formats the cell twice, once in heightForRowAtIndexPath: and then again in cellForRowAtIndexPath:. I then use an array or dictionary to store either the height or the formatted cell object.

I've probably written and rewritten this code 20 times over the past 2 years. Why did Apple implement it in this order? It would be much more straightforward to configure the cells and THEN set the height either in cellForRowAtIndexPath: or shortly thereafter in heightForRowAtIndexPath:.

Is there a good reason for the existing order? Is there a better way to handle it?

like image 859
centreee Avatar asked Feb 04 '14 01:02

centreee


2 Answers

Best guess: UITableView needs to know the total height of all cells so that it can know the percentage of scroll for the scroll bar and other needs.

like image 113
zaph Avatar answered Nov 02 '22 10:11

zaph


Actually, in iOS 7, it doesn't have to work that way. You can now set an estimated height for your rows and calculate each row's real height only when that row is actually needed. (And I assume that this is exactly because people made complaints identical to yours: "Why do I have to do this twice?")

Thus you can postpone the height calculation and then memoize it the first time that row appears (this is for a simple one-section table, but it is easy to adapt it if you have multiple sections):

- (void)viewDidLoad {
    [super viewDidLoad];

    // create empty "sparse array" of heights, for later
    NSMutableArray* heights = [NSMutableArray new];
    for (int i = 0; i < self.modeldata.count; i++)
        [heights addObject: [NSNull null]];
    self.heights = heights;

    self.tableView.estimatedRowHeight = 40; // new iOS 7 feature
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    int ix = indexPath.row;
    if ([NSNull null] == self.heights[ix]) {
        h = // calculate _real_ height here, on demand
        self.heights[ix] = @(h);
    }
    return [self.heights[ix] floatValue];
}

You supplied an estimated height, so all the heights are not asked for beforehand. You are asked for a height only before that row actually appears in the interface, either because it is showing initially or because you or the user scrolled to reveal it.

NOTE Also, note that if you use dequeueReusableCellWithIdentifier:forIndexPath: the cell you get has the correct final height already. That is the whole point of this method (as opposed to the earlier mere dequeueReusableCellWithIdentifier:).

like image 26
matt Avatar answered Nov 02 '22 11:11

matt