Using UITableViewCell with InterfaceBuilder

The code examples for the iPhone SDK show only how to construct your table cells programatically. If you want to use InterfaceBuilder to make a proper layout which might also be resizable etc. you can use this ViewFactory to handle the creation and reusage of your table cells.

First create a new empty Interface Builder file. Design all the UITableViewCells you want, but they must be root objects so they can be found by the ViewFactory. Each cell MUST have its “identifier” field set. It is in fact its reuseIdentifier required to make a proper recycling of your cell possible.

bild-1

Now create the ViewFactory, the header ViewFactory.h

@interface ViewFactory : NSObject {
    NSMutableDictionary * viewTemplateStore;
}

- (id) initWithNib: (NSString*)aNibName;

- (UITableViewCell*)cellOfKind: (NSString*)theCellKind forTable: (UITableView*)aTableView;

@end

and the implementation ViewFactory.m

#import "ViewFactory.h"

@implementation ViewFactory

- (id) initWithNib: (NSString*)aNibName
{
    if (self == [super init]) {
        viewTemplateStore = [[NSMutableDictionary alloc] init];
        NSArray * templates = [[NSBundle mainBundle] loadNibNamed:aNibName owner:self options:nil];
        for (id template in templates) {
            if ([template isKindOfClass:[UITableViewCell class]]) {
                UITableViewCell * cellTemplate = (UITableViewCell *)template;
                NSString * key = cellTemplate.reuseIdentifier;
                if (key) {
                    [viewTemplateStore setObject:[NSKeyedArchiver
                                                  archivedDataWithRootObject:template]
                                          forKey:key];
                } else {
                    @throw [NSException exceptionWithName:@"Unknown cell"
                                                   reason:@"Cell has no reuseIdentifier"
                                                 userInfo:nil];
                }
            }
        }
    }

    return self;
}

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

- (UITableViewCell*)cellOfKind: (NSString*)theCellKind forTable: (UITableView*)aTableView
{
    UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:theCellKind];

    if (!cell) {
        NSData * cellData = [viewTemplateStore objectForKey:theCellKind];
        if (cellData) {
            cell = [NSKeyedUnarchiver unarchiveObjectWithData:cellData];
        } else {
            NSLog(@"Don't know nothing about cell of kind %@", theCellKind);
        }
    }

    return cell;
}

@end

You have to make the ViewFactory instance available for your TableViews as a Singleton.

Whenever you want to fill you table cell in your UITableViewDataSource put this line in your method

- (UITableViewCell *)tableView: (UITableView *)aTableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
   UITableViewCell * cell = [viewFactory cellOfKind:@"news" forTable:aTableView];
   // Access the views using [cell viewWithTag:X] in the old way
   return cell;
}

where “news” in this example is the value of the reuseIdentifier.

Internally I store each instance of the UITableViewCells as a template in a NSData archived format and make it accessible via its reuseIdentifier. So whenever cellOfKind:forTable: is called it looks into the reuseQueue of the given table if there is already an old table cell which can be reused or if it needs to instantiate a new one using the archived template to create the new table view cell.

This technique is much more flexible if you are using tables and I hope you will have fun using it.

Advertisements

46 thoughts on “Using UITableViewCell with InterfaceBuilder

  1. Excellent article describing how to use interface builder to create multiple custom UITableViewCells in a single UITableView. Your formatting is exceptionally suitable for this type of article, and the screenshots/images make a huge difference. Thank you!

    Can you share your singleton instantiation of this class? How are you using ViewFactory.initWithNib to get things set up?

    -Kevin

  2. Thanks pegolon. I’m having problems accessing the IBOutlet references for items in the cell. They’re wired up properly, but the value of the IBOutlets are null at runtime. Any thoughts?

    -Kevin

  3. Pegolon,

    I’m still having difficulty as described in the last comment. When the cell is unpacked, the IBOutlets are null. This means I can’t see updates to the data in cell elements. Can you tell me what was necessary to wire up the outlets?

    -Kevin

    • You cannot use IBOutlets with UITableViewCells. You have to access the views using viewWithTag: like in the examples from the iPhone SDK.

  4. Hi, Thanks a lot for this information. It worked perfectly. I’m trying to use a UIWebView on my custom cell. I’m able to display the information on the webview but as soon as I clicked on the cell webview disappears but all the other information stays. Do you know why this might be happening.

    NSURL *url = [NSURL URLWithString:urlAddress];
    NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
    UIWebView *webImageDisplay = (UIWebView *)[cell viewWithTag:2];
    [webImageDisplay loadRequest:requestObj];

    It might be something with a setting on my UITableView. Thanks for your help.

  5. Hi Nathaniel,
    I think using WebView in a table cell is not a good idea: it makes your table almost unusable. Maybe you should use an off screen web view and copy its content to an UIImage which you will display in your table cell.
    Cheers,
    Markus

  6. Thanks I was able to made it work, but encounter some weird web view loading error when scrolling the table. I will try to implement your suggestion.

  7. mohrt: that’s quiet a vague error report! Did you analyze your code with Instruments? What kind of leak did you find? I have no problem at all with it..

  8. First, thanks for the valuable post, pegolon. I haven’t tested your code but I believe mohrt is right that it leaks memory. Basically, we are responsible for releasing top-level objects we load from a Nib file and the intermittent archiving of these objects doesn’t change that. So the cell we return in cellOfKind:forTable: never gets released. We should autorelease it in cellOfKind:forTable: or cellForRowAtIndexPath:, just as Apple does in their table view template.

  9. What do you mean by root object?
    For some reason this line gives me an objc_exception_throw:

    NSData* temp = [NSKeyedArchiver archivedDataWithRootObject:template];

    the key is the same as the identifier i made in interface builder.
    template is loaded fine and is a UITableViewCell* but NSKeydArchiver doesnt like it…

    any idea what i might have done wrong?

  10. Root object means it has to be in the first level of the IB file (the same level as the file owner).

    Maybe you have included UIImageView with an image? UIImage is does not comply to NSCoder so you cannot set images already in Interface Builder.

    Hopes this helps. Otherwise a more specific stacktrace and error message would be helpful to solve your problem.

  11. One more question. I dragged a few labels into the cell how do I get the labels so I can set their text?

    Thanks!

  12. Eeverything is working as it should but I am getting a leak that I cannot figure out.

    Instruments is directing me to this line “[viewTemplateStore setObject:[NSKeyedArchiver”
    The leaked objects are CGColor and UICachedDeviceWhiteColor

    Any suggestions?

    • Mitch, I also discovered that error in Instruments but I thought that it was a leak in the SDK classes, since I found several forums on the net mentioning it. Maybe someone else has a solution, since it makes no real sense to me…
      Markus

  13. Why doesn’t this work if you skip the NSKeyedArchiver step? I don’t see why you can’t simply store the object that you have thawed into your factory’s store. I tried doing this, and it seems to only draw my last cell, whereas yours works as expected.

    The big difference is that my way allows me to set everything via IBOutlets, rather than having to go by (far more irritating) tags. It must be the initialization code, and what is being called when you call initWithCoder vs loadNibNamed. Does anyone have any ideas about how we can use the above code, yet still wire up the IBOutlets?

    • You will only get one instance for all cell objects you request from the factory if you don’t use the way with the archiver. If you want to use outlets you have to call loadNibNamed: each time you want to create a new cell in cellOfKind: but I guess that would be not so performant.

  14. Btw, this doesn’t work if you include images in your NIB file because UIImage does not conform to NSCoding. I tried to put a UIButton on a table cell and set the button’s image to a file from my Resources folder in IB. While archiving to/unarchiving from the NIB works (it seems to just store the filename and not the actual image), [viewTemplateStore setObject:forKey:] fails with an exception because UIImage doesn’t have an encodeWithCoder: method. I wrote a simple category to make this work with UIImage:

    @interface UIImage (NSCoding)
    – (id)initWithCoder:(NSCoder *)decoder;
    – (void)encodeWithCoder:(NSCoder *)encoder;
    @end

    @implementation UIImage (NSCoding)
    – (id)initWithCoder:(NSCoder *)decoder {
    NSData *pngData = [decoder decodeObjectForKey:@”PNGRepresentation”];
    [self autorelease];
    self = [[UIImage alloc] initWithData:pngData];
    return self;
    }
    – (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:UIImagePNGRepresentation(self) forKey:@”PNGRepresentation”];
    }
    @end

    This is no comprehensive NSCoding support for UIImage since it doesn’t include all properties, but it works for the view factory.

  15. Is there any resolution to the leaking memory issue? I’m sure I’m not the only one with this problem now.

    Thanks,

    Brendan

  16. First of all, thanks for the excellent post, and thanks for the useful comments too. I’ve just put some buttons on a UITableViewCell using this approach, but I can’t seem to figure out how to respond to the clicked events, has anyone done something similar?

    Thank you,
    EGA.

  17. I found this somewhere ….

    UICachedDeviceWhiteColor leak looks like a bug in the apple code when cell is loaded from the nib file.

    Solution:
    in

    -(UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
    {
    NSAutorelease * pool = [[NSAutorelease alloc] init];

    //gets your cell from the nib file

    [cell retain];
    [pool release];
    return cell;
    }

    I’ve tried it and no more memory leaks due to UICachedDeviceWhite color in Instruments
    But the Dealloc method of my custom cell is never called, confusing

    I don’t know if it’s a good idea?

  18. Pingback: Question about UITableViewCell and grouped tables - iPhone Dev SDK Forum

  19. Pingback: Aral Balkan - Links for 2009-04-21

  20. “-(UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
    {
    NSAutorelease * pool = [[NSAutorelease alloc] init];

    //gets your cell from the nib file

    [cell retain];
    [pool release];
    return cell;

    if you add a [cell autorelease] after the [pool release] your cell will release properly… only problem is you still get the NSCachedDeviceWhiteColor… I think you just circumvented the white color bug by introducing a different memory leak.

    Jacob

  21. I have a problem with this code. How do I access labels in my UITableView cell? IBOutles don’t work right? How do I use viewWithTag? How do I set these tags in Interface Builder? When I watch the tags of my views they are all 0. (which is default isn’t it?)

  22. I’m still not finding where to set tags in Interface Builder. The Identity Inspector makes no mention of the word “Tag” in any of the properties. Clue, please? 😦

    • … and the reason is I was looking in the wrong Inspector! For those playing along at home, pick “Attributes Inspector” (Command-1), _then_ scroll down to View and you should see Tag just beneath the Background Color property.

  23. Thanks Markus!

    I finally got to use it. What I don’t like is the fact that you have to use the tags, so I subclassed UITableViewCell, changed the class in IB. I did the subclassing in a wrapping way, so you can access the components of the table cell through properties so it feels like using outlets. I will definitly write a blog post about it on the weekend when I have time for it 😉

  24. I’m curious if anyone has seen UITableView scrolling performance degradation using this method vs. self drawing? I have never had good tableview performance using the IB/tablecell approach. Is there a better way of using cellForRowAtIndexPath: when using custom cells from IB? Would love to do layout in IB but I never get acceptable performance compared to doing cell layout myself in code.

  25. Pingback: Using UITableViewCell with InterfaceBuilder « thomas bittel

  26. Sorry pegolon but I still have some problem to get elements in my personalized cell.

    From what I understood I have to utilize viewWithTag on my cell element….I think it should look like this:

    [(UILabel *)[cell viewWithTag:7] setText:@”casetta”];

    but I’m accessing the wrong elemnt…someone can help me?

    thx a lot

  27. I’m new to iPhone development. This technique is going to save me LOTS of time. It has already. Thanks for this GREAT post!!!!!

  28. I love your solution, it works quite perfect.

    Is there any way to have an IBAction assigned?
    I want to put a Textfield and a Slider in a cell.

  29. Pingback: Loading custom UITableViewCells from Nib files without a controller in MonoTouch » Dimitris Tavlikos Software

  30. Pingback: ViewFactory iphone | taking a bite into Apple

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s