Faster UITableViews with iOS 7

October 15, 2013

Like past iOS releases, there are often small APIs which get overlooked amongst the new frameworks and tentpole features. In iOS 7, one of these easily overlooked APIs is row height estimation for UITableView. Even if you noticed this new API you might not have realized how powerful it can be.

UITableView is a staple of most apps. It’s most powerfully used in conjunction with NSFetchedResultsController to keep the view in sync with objects in the data store. UITableView’s two data source and delegate protocols were engineered for displaying dynamic content. Often times you will dynamically size each UITableViewCell based on the height of your content. Maybe you’re displaying an image and the cell’s height is based off that image. Dynamic cell heights often are required for text too. Your tableView:heightForRowAtIndexPath might look something like the following code snippet, assuming that you’re caching the height calculation (a good idea):

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    Message *message = [self.fetchedResultsController objectAtIndexPath:indexPath];
    return message.textHeight;
}

So the height of the cell is directly tied to some properties of the model object. The problem here is that in order to reload the table view, all of the faults in the fetched managed objects will be fired. If you’re not familiar with Core Data faulting, it’s basically a method of lazily loading managed objects. If you’re interested you can find out more in the Apple documentation.

Now in iOS 7 with the estimation of row heights, you can load an entire table view without firing all the faults on your managed objects. Simply provide an estimated row height, and UITableView will ask for the absolute row height when you scroll to it. Either use the property on UITableView:

self.tableView.estimatedRowHeight = 300;

Or alternatively you can do some simple calculations for each row height with the UITableViewDelegate method:

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
   return 300;
}

Make sure you keep any calculations very simple, otherwise you’ll loose the performance gains of estimating row heights and probably loose performance since you’re effectively calculating the row height twice. Also, the documentation warns that this estimated row height should never be negative. Along with height estimation for table view rows, Apple has provided the same APIs for section headers and footers.

Let me know how this works for you and if you have any questions in the comments section.