iPhone Dev: Simplifying table cell creation

samalone's picture

With the iPhone NDA lifted today, I thought I'd celebrate by sharing a little code snippet that has been very helpful as we develop Life Balance for iPhone.

Anytime you create a table on the iPhone, you have to override an UITableViewDelegate method called tableView:cellForRowAtIndexPath:. And in this function you have to test to see if you can reuse an existing table cell before creating a new one. Apple's template code for subclassing UITableViewController starts you off with:

- (UITableViewCell *)tableView:(UITableView *)tableView
    cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = 
        [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero 
                reuseIdentifier:CellIdentifier]
            autorelease];
    }
    // Configure the cell
    return cell;
}

If your table contains cells of different types, then you also have to be careful to use different identifiers for the different cell types, and match the correct identifiers with the correct types. This is all pretty straightforward as coding goes, but it is a little verbose and error-prone if you find yourself replacing one cell type with another.

However, Objective-C is a wonderfully versatile language where it is actually possible to add methods to existing classes, and it occurred to me that we could use this feature to enhance the built-in UITableViewCell class with a class method to streamline the creation of table cells. First I'll, present the code, and then I'll explain how it works.

The header file is named UITableViewCell+Llamagraphics.h, and it looks like:

#import <UIKit/UIKit.h>

@interface UITableViewCell (Llamagraphics)
+ (id) availableCellForTable: (UITableView *)tableView;
@end

This declares a new class method called availableCellForTable: that takes a pointer to a table view and returns a table cell, either by reusing an existing cell or creating a new one. The implementation of this routine is in the file UITableViewCell+Llamagraphics.mm, and it looks like:

#import "UITableVIewCell+Llamagraphics.h"
#import <objc/runtime.h>
#include <string>

@implementation UITableViewCell (Llamagraphics)

+ (id) availableCellForTable: (UITableView *)tableView {
    const char * className = class_getName(self);
    NSString * identifier = 
        [[NSString alloc] initWithBytesNoCopy: const_cast<char *>(className) 
            length: std::strlen(className) 
            encoding: NSASCIIStringEncoding 
            freeWhenDone: false];
    UITableViewCell * cell =
        [tableView dequeueReusableCellWithIdentifier: identifier];
    if (cell == nil) {
        cell = [[[self alloc] initWithFrame: CGRectZero 
                reuseIdentifier: identifier]
            autorelease];
    }
    [identifier release];
    return cell;
}

@end

Using this header file, Apple's starter template reduces to:

- (UITableViewCell *)tableView:(UITableView *)tableView
    cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [UITableViewCell availableCellForTable: tableView];
    // Configure the cell
    return cell;
}

Now doesn't that seem easier? What's more, the method works with custom subclasses of UITableViewCell, so you can use the same method for all of the cell types in your code.

The main trick that availableCellForTable: uses is that for class methods, the variable self is automatically set to the class object that the method was invoked on. So if you call [UITableViewCell availableCellForTable: tableView], self is set to [UITableViewCell class]. If you call [MyTableViewCell availableCellForTable: tableView], self is set to [MyTableViewCell class].

Armed with this knowledge, availableCellForTable: can retrieve the name of the class, convert the name to an NSString, and use the string as the identifier for the cell. There's no danger of associating the identifier with the wrong class, because the identifier is the class name.

If an existing cell can't be dequeued, the code calls [self alloc] to allocate an object of the specified class, and initWithFrame:reuseIdentifier: to initialize it. Again, because of the way that selectors are dispatched in Objective-C, this call will invoke the subclasses' methods if you have overridden them.

If you're developing native apps for iPhone, I hope this trick will make your coding just a little more enjoyable.

0
Your rating: None
Syndicate content