各セルの計算を tableView:heightForRowAtIndexPath: に移動する際の問題は、 reloadData が呼び出されるたびにすべてのセルが再計算されることです。少なくとも数百行ある可能性がある私のアプリケーションでは、遅すぎます。デフォルトの行の高さを使用し、計算時に行の高さをキャッシュする代替ソリューションを次に示します。高さが変更されるか、最初に計算されると、新しい高さをテーブル ビューに通知するためにテーブルのリロードがスケジュールされます。これは、行の高さが変化したときに行が 2 回表示されることを意味しますが、比較するとわずかです。
@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;
}