The Key to Smooth Scrolling

I’m currently writing an iOS app that uses a UITableView to display cells that contain images. When I started testing it on my device (iPod touch 4th gen) though, I experienced some nasty problems with the scroll speed. In fact, the framerate of this thing was so low that the app was hardly usable. I started googling on the subject of smooth tableview scrolling and found some references to Loren Brichter’s blogpost about smooth scrolling. The blog doesn’t exist anymore since Twitter acquired Atebits, but I was able to look it up using the WayBackMachine: http://web.archive.org/web/20100822003957/http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-UITableView/. Other people spoke about an example project on the Apple Developer Portal, which essentially does the same as Loren’s code. There were references to these articles, but I could hardly find any documentation or explanation about how to use it and what to do (Custom drawing in my cells? I do – kind of – my own custom drawing and it still isn’t fluent at all). Maybe it’s because I’m too stupid to understand the samples but I didn’t find anything that was useful to me. I did some research on the drawing of TableViewCells and found out that the system forgets them rightaway when you scroll away from them. If you scroll back, it has to load all of the cell again and draw everything from scratch. That’s pretty intensive stuff for the GPU in your iPhone when there are images in your cells. So what can you do about that?

The following is the solution I came up with. If I’m wrong and doing this is really stupid for some reason please tell me in the comments. I just tried to make my tableviews scroll smoother and it looks like this thing works pretty good for me, so I wanted to share it with you.

What I did is pretty simple. In my UITableViewController subclass I created a global NSMutableDictionary variable called cells. The variable is initialized in the awakeFromNib method with cells = [[NSMutableDictionary alloc] init]; In the cellForRowAtIndexPath method I used the following code:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    if ([cells objectForKey:[NSString stringWithFormat:@"%d",indexPath.row]]) {
        return [cells objectForKey:[NSString stringWithFormat:@"%d", indexPath.row]];
    }

    MasterTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[NSString stringWithFormat:@"Cell%d", indexPath.row]];

    if (cell == nil) {

        cell = [[MasterTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:[NSString stringWithFormat:@"Cell%d", indexPath.row]];

    }

    [self configureCell:cell atIndexPath:indexPath];

    [cells setObject:cell forKey:[NSString stringWithFormat:@"%d", indexPath.row]];

    return cell;

}

First, this code checks in the global dictionary if there’s an object with the current indexPath row as its key. If this exists it returns that object. If it doesn’t exist there it gets the cell with reuseIdentifier cell1 or whatever the current row index is. It is important that the reuseIdentifier is different for each row, because when it isn’t your whole tableView will look messed up, with cells being in the wrong place or missing completely. Then it checks if the cell returned by the tableView is nil. If it is, a new cell is allocated. Then the cell is configured by another method that adds images and text. But now the magic happens. Instead of immediately returning the cell it will be added to our dictionary first. That way it won’t get lost, and when the cell has to be initialized again we can just pull it from the dictionary – that’s what happens in the first three lines of this method. This way the cell doesn’t have to be redrawn and your tableView will scroll many times faster!

Well essentially that’s it! The code will work fine now, although there are still some things that need tweaking. For instance, when you add, move or remove a row the row indexes change and the cells won’t be in the right place anymore. You can simply solve this by clearing the dictionary and reloading the tableView when a row is edited. There’s just one pretty important thing left. When your table contains a lot of cells with images that are all kept in your dictionary, the memory usage of your app can increase significantly. Well that’s a problem. With this method we’re sacrificing memory for scrolling smoothness, but of course you don’t want your app to crash because it’s out of memory. I think the most simple solution is to just empty the whole dictionary when your app gets a memory warning:

- (void)didReceiveMemoryWarning {

    [cells removeAllObjects];

}

So that’s it! I hope you’ll enjoy your smooth tableViews and please leave a comment if you think I’m wrong.