Posts Tagged‘abtableviewcell’

Fast Scrolling UITableView: Updates for iOS 4.2.

I’ll be honest and say that most of the programs I’ve built have never really been that resource intensive so optimising them for performance really hadn’t been much of a priority. Sure there were the occasional thing that I’d catch and try to improve, like when an early copy of Geon had a dropped shadow around the map that inexplicably made it run like a dog, but for the most part I’d just code them up and leave it at that. Coding for the iPhone and other resource poor systems however does not afford me such luxuries and performance tuning the app has taken up a considerable amount of my development time, but the pay offs have been quite great.

After getting my first shot at the Lobaco app up and running I noticed there was considerable slow down when scrolling through the main list of items. Since I’m a big fan of the official Twitter app I knew that it was possible to have quite smooth scrolling even when you had multiple images and gobs of text on the screen. As it turns out I wasn’t alone with this performance problem with UITableViews (the class used for that main list display) and the developers behind it posted up some code to demonstrate how they achieved such fast scrolling.

If you follow that link you’ll notice that that particular blog post is now over 2 years old, back when the iPhone 3G was still the top offering from Apple. Whilst the code given in that blog post still functions I ran into a couple of issues implementing it in the latest SDK (4.2). The first issue you’ll hit when trying to use this code is the initWithFrame function, which is used to create your cell, is now deprecated. Whilst it should still function I could not get my code to work until I made the following change in ABTableViewCell.m:

// Copyright (c) 2008 Loren Brichter
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
//  ABTableViewCell.m
//
//  Created by Loren Brichter
//  Copyright 2008 Loren Brichter. All rights reserved.
//

#import "ABTableViewCell.h"

@interface ABTableViewCellView : UIView
@end

@implementation ABTableViewCellView

- (void)drawRect:(CGRect)r
{
	[(ABTableViewCell *)[self superview] drawContentView:r];
}

@end

@implementation ABTableViewCell

/*- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier
{
    if(self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier])
	{
		contentView = [[ABTableViewCellView alloc] initWithFrame:CGRectZero];
		contentView.opaque = YES;
		[self addSubview:contentView];
		[contentView release];
    }
    return self;
}*/

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
	if(self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])
	{
		contentView = [[ABTableViewCellView alloc] initWithFrame:CGRectZero];
		contentView.opaque = YES;
		contentView.backgroundColor = [UIColor whiteColor];
		[self addSubview:contentView];
		[contentView release];
    }
    return self;
}

- (void)dealloc
{
	[super dealloc];
}

- (void)setFrame:(CGRect)f
{
	[super setFrame:f];
	CGRect b = [self bounds];
	b.size.height -= 1; // leave room for the seperator line
	[contentView setFrame:b];
}

- (void)setNeedsDisplay
{
	[super setNeedsDisplay];
	[contentView setNeedsDisplay];
}

- (void)drawContentView:(CGRect)r
{
	// subclasses should implement this
}

@end

The main change is to replace the old initWithFrame with the new initWithStyle. This also requires changing the super call to the UITableViewCell class we’re subclassing, but apart from that everything else remains the same. Once I had that problem out of the way my custom cells were now drawing properly and appeared to be scrolling much more smoothly than they were before. However I was noticing another strange issue with my cells, they seemed to be displaying data at random from my data array. Try as I might to find the solution to this problem I couldn’t, until went back to the fundamentals of the UITableView.

You see creating cells with a UITableView is a pretty expensive process, just as it is for any system when creating new objects. This is even more pronounced with the resource limitations of the iPhone and so the iOS SDK employs a simple trick to work around this. Instead of creating and deleting a new cell every time one is needed it will instead reuse a cell that’s no longer in use, I.E. one that’s scrolled off screen. Since the cell will usually have new data in it at this point when it comes back on screen it should redraw itself to reflect this. However it seems that the ABTableViewCell class wasn’t doing this and the only way I could get it to update the data was by clicking on the cell, which caused a refresh.

If you’re not using this class then you’ll probably never encounter this issue and I believe this is because of the way ABTableViewCell does it’s drawing. You see in order to get the performance improvement you’re basically bypassing the regular way of drawing the cell and doing it yourself. This has enormous performance benefits since you’re not doing any unnecessary drawing, but it appears that the UITableViewCell class doesn’t call the drawContentView function as part of its normal drawing routine anymore. Thankfully this can be solved with a one liner in your UITableView controller class by letting the cell know it needs to redraw itself with setNeedsDisplay:

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

    static NSString *CellIdentifier = @"Cell";
	int nodeCount = [displayItems count];

    LobacoTableCell *cell = (LobacoTableCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        //cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
		cell = [[[LobacoTableCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
    }

    // Configure the cell...

	if (nodeCount > 0)
	{
		Post *post = [displayItems objectAtIndex:indexPath.row];
		cell.post = post;
		if (!post.profileImage)
		{
			if (self.tableView.dragging == NO && self.tableView.decelerating == NO)
			{
				[self startImageDownload:post forIndexPath:indexPath];
			}

			cell.image = [UIImage imageNamed:@"Placeholder.png"];

		}
		else
		{
			cell.image = post.profileImage;
		}
	}
	[cell setNeedsDisplay];
    return cell;
}

I do this after I’ve done all the reconfiguration of the cell so that it’s drawn with all the correct information. The image code in this part will also trigger a redraw of the cell when it’s finished downloading the image (in this case the user’s profile picture) ensuring that it’s displayed immediately rather than when the drops out and comes back into view again. With all these fixes in place my new custom UITableViewCell works perfectly and the scrolling performance is glassy smooth.

All of the above issues I encountered after I upgraded my Xcode installation to iOS 4.2 and despite my intense Googling I couldn’t find any real solutions to these problems. If you’re a budding iPhone developer like me struggling to figure out why some things just aren’t working the way they should I hope this post gives you a little insight into what was going wrong and ultimately how to fix it. It’s these kinds of curious problems that frustrate the hell out of me when I’m in them but they’re always quite satisfying once you’ve managed to knock them over.