Reputation: 6276
I'm having some performance issues when using a dynamic value in heightForRowAtIndexPath:
(I know it's this method because if I set a static value the responsiveness increases substantially). My table contains around 3000 cells.
I understand why the performance suffers when using a dynamic value (mainly because the calculations in the method must be performed once for every cell in the table before the data can be shown), but I can't figure out how to make it more efficient.
In many of the similar questions I've come across, the proposed solution was to use NSString
's sizeWithFont
method in heightForRowAtIndexPath:
to speed things up. I currently do that but the table still takes roughly ~1.5sec to load (and reload, which is done somewhat frequently). This is just too long and I need to optimize it.
The code I'm currently using (the essence of it at least) is below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell;
UILabel *label = nil;
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
label = [[UILabel alloc] initWithFrame:CGRectZero];
// set up label..
[[cell contentView] addSubview:label];
}
NSDictionary *dict = alphabetDict; //dictionary of alphabet letters (A-Z). each key contains an NSArray as its object
CGFloat rightMargin = 50.f; //padding for the tableview's index titles
NSString *key = [[[dict allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section];
NSArray *array = [dict objectForKey:key];
NSString *cellText = [array objectAtIndex:indexPath.row];
//TABLE_WIDTH is 268.f, CELL_MARGIN is 14.f
CGSize constraintSize = CGSizeMake(TABLE_WIDTH - (CELL_MARGIN * 2) - rightMargin, 20000.0f);
CGSize labelSize = [cellText sizeWithFont:[UIFont systemFontOfSize:17] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
[label setFrame:CGRectMake(CELL_MARGIN, CELL_MARGIN, TABLE_WIDTH - (CELL_MARGIN * 2) - rightMargin, labelSize.height)];
[label setText:cellText];
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//TODO make this faster
NSDictionary *dict = alphabetDict;
CGFloat rightMargin = 50.f;
NSString *key = [[[dict allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section];
NSArray *array = [dict objectForKey:key];
NSString *cellText = [array objectAtIndex:indexPath.row];
CGSize constraintSize = CGSizeMake(TABLE_WIDTH - (CELL_MARGIN * 2) - rightMargin, 20000.0f);
CGSize labelSize = [cellText sizeWithFont:[UIFont systemFontOfSize:17] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
return labelSize.height + (CELL_MARGIN * 2) + 16.f;
}
Could someone point me in the right direction to streamline this code further? Thanks!
Upvotes: 0
Views: 4284
Reputation: 5960
crypticcoder has a good suggestion. I would suggest a variation of that, which might be more flexible, and support table changes (insert, delete, reorder).
Create a class that is a subclass of UITableViewCell and add a cellHeight property adn ivar cellHeight_ to it. Synthesize it this way:
@synthesize cellHeight=cellHeight_;
Calculate the cell height in a lazy way with this code inside the class:
-(CGFloat) cellHeight{
if (cellHeight_ != 0)
return cellHeight_;
else {
NSDictionary *dict = alphabetDict;
CGFloat rightMargin = 50.f;
NSString *key = [[[dict allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section];
NSArray *array = [dict objectForKey:key];
NSString *cellText = [array objectAtIndex:indexPath.row];
CGSize constraintSize = CGSizeMake(TABLE_WIDTH - (CELL_MARGIN * 2) - rightMargin, 20000.0f);
CGSize labelSize = [cellText sizeWithFont:[UIFont systemFontOfSize:17] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
cellHeight_ = labelSize.height + (CELL_MARGIN * 2) + 16.f;
return cellHeight_;
}
}
Like sch, I don't like the sort in there, but if it's not really the performance problem, I'm leaving it there.
If you want to change the contents of a cell, make sure you reset cellHeight_ to zero so it is recalculated lazily (when it's needed again).
Upvotes: 0
Reputation: 1724
I have faced this similar situation before. My solution is to do all the calculations and store the heights in an NSMutableArray called heightsArray before you do [tableView reloadData]. Then
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
[heightsArray objectAtIndex:indexpath.row];
}
No calculations while scrolling up and down. Plain reading from the array. The initial hold-up will be more because of the calculation being done upfront but it is worth it in terms of performance gains while scrolling. You can also perform the calculation on a background thread thereby saving processing time. Just do all that in a method which you can call on a background thread and prepare your heights array.
Upvotes: 5
Reputation: 27506
The most time consuming part in heightForRowAtIndexPath
is:
[[[dict allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section];
So, you can start by adding a property sortedKeys
and use it like this:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *key = [sortedKeys objectAtIndex:indexPath.section];
CGFloat rightMargin = 50.f;
// ...
}
Upvotes: 1