Thursday, April 22, 2010

Table View Cells Redux

Quite a while ago, I posted about Apple's recommended way of doing custom table view cells in Interface Builder. The code from that post has been available for 9 months and today, for the first time, somebody pointed out to me that the attached project didn't reuse dequeued cells because I forgot to type the cell's identifier in Interface Builder.

Which is the weakness with Apple's recommended approach. It's a real Achille's heel. It's really, really easy to forget that step, and the code works perfectly fine if you do forget, you're just eating memory and getting poorer performance than you should. Unless you profile your apps or test with very large data sets, you could very well ship your app like this and not even realize it. You never want your customers to discover these things before you.

Now, I didn't even know I had made this mistake until today, but I've known it was a potential problem for quite some time, which is why, in my contract work, I've started using a modified version of the technique. My earlier mistake now gives me a good excuse to post that modification.

Mostly, it's the same technique as I discussed before, only I start out by defining a constant for the identifier. I actually create a header file in my Xcode projects ConstantsAndMacros.h in my project, which I add to my pre-compiled header file. This means that any constants and any macros I put in ConstantsAndMacros.h will be available to all my source code files in my project without having to manually import them. For a simple project, that file might look something like this:

#define TABLE_CELL_IDENTIFIER @"Table Cell Identifier"
#define NSStubLog() NSLog(@"%s", __PRETTY_FUNCTION__)¹

Once I have that file, I add it to the .pch file in the Other Sources folder:

#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "ConstantsAndMacros.h"

Now that constant is available project wide². Then, in my UITableViewCell subclass, I override the reuseIdentifier method and add a class method with the same name, like this:

+ (NSString *)reuseIdentifier

- (NSString *)reuseIdentifier
return [[self class] reuseIdentifier];

By doing this, the system will ignore any identifier I set in Interface Builder, or any value I set in code using the setBundleIdentifier: mutator method. For instances of this particular class, it will always use the same identifier. By creating the class method, I have access to that identifier even if I don't yet have an instance of the class.

For all the other steps in using custom table view cells loaded from a nib, the process is the same as the previous tutorial and it works great. Here's an example tableView:cellForRowAtIndexPath: method using this technique:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
TestCell *cell = (TestCell *)[tableView dequeueReusableCellWithIdentifier:[TestCell reuseIdentifier]];
if (cell == nil)
NSLog(@"Loading new cell");
[[NSBundle mainBundle] loadNibNamed:@"TestCell" owner:self options:nil];
cell = loadCell;
self.loadCell = nil;

cell.cellLabel.text = [NSString stringWithFormat:@"Row %d", [indexPath row]];
return cell;

Notice the NSLog() statement? I can open my table view cell in Interface Builder and set any identifier I want, or set no identifier at all, and it will still re-use table view cells. Run the app, and no matter what you do in Interface Builder, you'll only see a handful of rows loaded from the nib file. After the initial loads, it will just keep reusing the same cell instances over and over. This is far less fragile than having to make sure the identifier in IB and the one in your code match.

There might be times when you don't want to set it up like this - when you need to have multiple identifiers for the same table view class, but those situations will be exceedingly rare. In most practical situations where you are subclassing UITableViewCell, you will want a single identifier for all instances of that class. What you normally won't want is the fragility of having to make sure the value in your code matches the one in IB exactly, especially given that there are no obvious signs that you've forgotten to do it.

1 This macro just logs the name of the method of function that it's placed in when that method is called. I use it whenever I stub out an IBAction method to make sure my connections are all made correctly, hence the name, but it's useful for debugging as well.

2 If you will only use a table view cell in a single controller, you probably don't want it here. In general, I try to create table view cells to be generic enough to be used in more than one controller. Sometimes that's not possible or practical, and in those cases, #define your identifier in the table view controller header instead.

You can find a sample implementation project right here/


Elfred Pagan said...

One thing I tend to do is have reuseIdentifier defined as:
+ (NSString *)reuseIdentifier
return NSStringForClass([self class]);

in my parent cell class. That way every cell gets its own identifier and I don't need to implement it for all my cell types.

szotyi said...

I couldn't find the documentation for NSStringForClass. Can you give me a hint.

The Slick One said...

Fantastic option. I wish Apple would remove the identifier from IB, and then provide an initWithNib... method that also accepted identifier as a parameter. Then you could do essentially the same thing whether you are using a custom class or a built-in style. Ideally it would remove the need for the cell @property in the parent view controller, too.


AlBlue said...

After setting up the NSStubLog in your standard header, did you then mean to actually use it for your "loading new cell" message, instead of NSLog()?

I like the idea of using the class name myself but you'd have to set it up so that it always returned the same instance for speed.

Adrian Hosey said...

I also like to use a Constants.h file, but it causes a lot of spurious "defined but not used" warnings because not every .m file uses everything defined in my Constants.h. Is there a way to suppress/avoid those warnings but not suppress other "defined but not used" warnings that might be important?

Jeff LaMarche said...


That's actually a great idea.


Check here in foundation reference:



Yeah, the more I work with it, the more I think having the identifier in the nib is useless. I think the identifier should just default to the class name.


Nope, it's just a standard macro that gets included in all my projects because it's part of my customized templates. I left it in because I thought some people might like the idea.


You must have your GCC settings configured differently than mine, or be using a different compiler, because I can't recall ever having seen that warning before.

Stack Overflow has this on the matter:

Jeff LaMarche said...

Note: the long urls in the above comment can be copied by triple-clicking them, which will highlight the entire row.

Mike Weller said...

All I do is keep my reuse constant in the code, and after loading from the nib:
assert([cell.reuseIdentifier isEqualToString:CellIdentifier]);

Mike Weller said...

Also, something else I've discovered recently:

Using this method of loading a cell, you can only use the cell with one class. You can't set the owner XIB class to be an interface or anything.

A more flexible way to do this is to take the array returned from loadNibNamed: and the object at index 0 is going to be your cell:

NSArray *objects = [[NSBundle mainBundle] loadNibNamed:@"OTReportsCell" owner:self options:nil];
cell = [objects objectAtIndex:0];

You can use this code in any class, no need for outlets and a fixed cell owner class. I got this from an apple employee on the developer forums btw.



Elfred Pagan said...

Mike, you can always have a inheritance tree of view controllers, where you have say MikesTableViewController, that has the outlet and all your specific behavior are in children of that class. I also tend to have a method in that class that does the actual loading.

The Slick One said...

@Mike Weller

Incorrect, the cells can be used with any class. The specific class of File's Owner is ignored. As long as the view controller has an appropriately named @property, and implements any necessary IBActions, the custom cell can be reused across multiple classes.

The risk with the index approach you mention is, I believe, the same issue Jeff and Dave bumped into with an early version of their book: Apple changed the order of the array.

JeansPilot said...

Just a bit of shameless self-advertising :) A couple of months ago I wrote a simple command-line tool that reads XIB files and outputs Objective-C code (for the iPhone) which can be used by those (still) willing to use code for whatever reason...
Buy Jeans

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels

healthpharmacyrx1 said...

kamagra Rezeptfrei Bestellen
cialis super active plus Bestellen
achat nolvadex
acheter levitra en ligne
Kaufen proscar Online
clomid Rezeptfrei Bestellen
viagra soft Rezeptfrei
xenical Bestellen
nolvadex acheter
achat cialis en ligne
viagra soft Online Rezeptfrei
priligy Bestellen Rezeptfrei
Rezeptfrei cialis
clomid Kaufen Apotheke
Ohne Rezept amoxil Apotheke
Buy levitra
Buy amoxil online
accutane Bestellen