Faster Tables via Row Heights

If you have a table with different per-row heights, it may load slowly because your height method is called for every row in the table before any of the table is first displayed — and it’s very easy to write a slow height method. This post presents a one-method category to fix this slow-down; code links at end of post.

Easy but slow code

Here’s a height method that works, but is going to be slow:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
  return cell.frameHeight;  // Uses UIView+Position; same as frame.size.height.
}

(This post assumes your table is run by a UITableViewController; it also uses the UIView+Position category to get the

frameHeight

property, which is just an abbreviation for frame.size.height.)

It’s slow because your cellForRowAtIndexPath: method has to completely prepare the row’s table cell just to determine the height – very inefficient if the height of that one row doesn’t change over time. We might as well cache the value, right?

Caching the height

The whole point of this category is to make it very easy for you to cache your row heights. Here’s the new code:

#import "UITableViewController+HeightHelper.h"

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  CGFloat height;
  NSString *tableName = self.navigationItem.title;  // Or any string unique to this table.
  if (![self didGetHeight:&height forRowAtIndexPath:indexPath inTableNamed:tableName]) {
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
    height = cell.frameHeight;
  }
  return height;
}

The call to method

didGetHeight:forRowAtIndexPath:inTableNamed:

will fail (return

NO

) at most once for the existence of your app on disk. It tries to load all heights from disk once. If the file (named after the table) doesn’t exist, it determines all row heights in that table and saves them to disk. After loading from disk or determining all row heights, they’re stored in memory so that every subsequent call to your height method returns extremely quickly.

Assumptions

The code assumes that your table always has the same number of sections, and the same number of rows per section.  It also assumes that each row has a constant height, but of course the heights can be different for each row – just constant over time.

Losing the overhead

Sounds pretty good, right? Except for the one-time cost it takes to determine all row heights the first time. That’s bad because the first impression of a user is important. So you can drop even the initial cost by bundling the saved files with your app. To do this, just uncomment the one

NSLog

line in the m file, and from that you can determine where the files are at runtime. They’ll have extension plist (they are in fact property list files). Copy these files somewhere into your project, and add them to the project (probably somewhere under Resources). That’s it! At runtime the library will find those plist files and the cached methods will always be fast and never return

NO

.

Will it help me?

I want to mention that tables get slow for many, many different reasons. I recommend the following simple test to see if this code will actually speed up your app:

  1. Comment out all NSLog statements that get hit while your table is loading (between when your table first exists in code, and is seen by the user). NSLog statements are slow, and need to be excluded from any speed tests.
  2. Test your current table load time. You can use
    clock()

    or just eyeball it, if you only care about huge, obvious improvements.

  3. Temporarily change your height method to return a constant, any constant. Your tables will look like poo, but you’ll get the timing data you need.
  4. Test the new table load time using the same method as before.

If you see a difference, the code of this post will help you. If not, it won’t.

How it works

If you’re a very curious iOS coder like me, you might be wondering how this one method call has the power to determine the heights of all your rows. How does it do that?

This is the beauty of category methods. By adding this method as a category on UITableViewController, the method automatically knows who the caller is (it’s

self

) and something about the type of the caller (it’s a UITableViewController). So the method can call any of the methods of the callee which are guaranteed to exist since it is of that type. Very handy!

The code

As always, free to use, distribute, mangle, etc.

UITableViewController+HeightHelper.h

UITableViewController+HeightHelper.m

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*