Iphone - when to calculate heightForRowAtIndexPath for a tableview when each cell height is dynamic?

前端 未结 10 1286
迷失自我
迷失自我 2020-11-30 19:50

I have seen this question asked many times but astoundingly, I have not seen a consistent answer, so I will give it a try myself:

If you have a tableview containing

10条回答
  •  醉梦人生
    2020-11-30 20:23

    The problem with moving the calculation of each cell to tableView:heightForRowAtIndexPath: is that all the cells are then recalculated every time reloadData is called. Way too slow, at least for my application where there may be 100's of rows. Here's an alternative solution that uses a default row height, and caches the row heights when they are calculated. When a height changes, or is first calculated, a table reload is scheduled to inform the table view of the new heights. This does mean that rows are displayed twice when their heights change, but that's minor in comparison:

    @interface MyTableViewController : UITableViewController {
        NSMutableDictionary *heightForRowCache;
        BOOL reloadRequested;
        NSInteger maxElementBottom;
        NSInteger minElementTop;
    }
    

    tableView:heightForRowAtIndexPath:

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        // If we've calculated the height for this cell before, get it from the height cache.  If
        // not, return a default height.  The actual size will be calculated by cellForRowAtIndexPath
        // when it is called.  Do not set too low a default or UITableViewController will request
        // too many cells (with cellForRowAtIndexPath).  Too high a value will cause reloadData to
        // be called more times than needed (as more rows become visible).  The best value is an
        // average of real cell sizes.
        NSNumber *height = [heightForRowCache objectForKey:[NSNumber numberWithInt:indexPath.row]];
        if (height != nil) {
            return height.floatValue;
        }
    
        return 200.0;
    }
    

    tableView:cellForRowAtIndexPath:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        // Get a reusable cell
        UITableViewCell *currentCell = [tableView dequeueReusableCellWithIdentifier:_filter.templateName];
        if (currentCell == nil) {
            currentCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_filter.templateName];
        }
    
        // Configure the cell
        // +++ unlisted method sets maxElementBottom & minElementTop +++
        [self configureCellElementLayout:currentCell withIndexPath:indexPath];
    
        // Calculate the new cell height
        NSNumber *newHeight = [NSNumber numberWithInt:maxElementBottom - minElementTop];
    
        // When the height of a cell changes (or is calculated for the first time) add a
        // reloadData request to the event queue.  This will cause heightForRowAtIndexPath
        // to be called again and inform the table of the new heights (after this refresh
        // cycle is complete since it's already been called for the current one).  (Calling
        // reloadData directly can work, but causes a reload for each new height)
        NSNumber *key = [NSNumber numberWithInt:indexPath.row];
        NSNumber *oldHeight = [heightForRowCache objectForKey:key];
        if (oldHeight == nil || newHeight.intValue != oldHeight.intValue) {
            if (!reloadRequested) {
                [self.tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0];
                reloadRequested = TRUE;
            }
        }
    
        // Save the new height in the cache
        [heightForRowCache setObject:newHeight forKey:key];
    
        NSLog(@"cellForRow: %@ height=%@ >> %@", indexPath, oldHeight, newHeight);
    
        return currentCell;
    }
    

提交回复
热议问题