Panic on American ApparelCar in Snow, Part 5Car in Snow, Part 4Car in Snow, Part 3

fill the void

Posted
28 June 2009 @ 1pm

Tagged
development

7 Comments

Custom UITableViewCell from a XIB in Interface Builder

Looking around the App Store, I see most apps customize their UITableViews in a unique way. Flixster embeds movie posters and ratings, in addition to their titles. Tweetie integrates tweets, icons, usernames, and the date. GasBuddy lists service type, amount spent, gallons, and dollars per gallon in each row. Constructing these customized UITableViewCells is possible in code, but leveraging Interface Builder’s drag-and-drop interface is far more fun. Thanks to Bill Dudney for talking about one approach to this on his blog and to StackOverflow for covering this topic.

Creating a custom UITableViewCell using Interface Builder is straight-forward.

  1. In Xcode, create a new UITableViewCell subclass and add the desired IBOutlets to the header file.
  2. In Interface Builder, create an “Empty” XIB from the Cocoa Touch palette.
  3. Drag a UITableViewCell from the Library into it, configure the class to be your new custom UITableViewCell subclass, and give it the appropriate identifier.
  4. Add the desired elements to the UITableViewCell and connect the subclass’s outlets to them.
  5. To instantiate the cells using the UIViewController approach, set class of the new XIB’s “File’s Owner” to be “UIViewController”, and connect its view outlet to the customized UITableViewCell.

The XIB is set up, but there are two approaches to instantiating the new customized cell from that XIB. When I was at WWDC a couple weeks ago, I confirmed with one of the Interface Builder engineers at the IB Lab that both work just fine. (He did repeatedly ask if I was using UITableView:dequeueReusableCellWithIdentifier:, just to make sure.)

UIViewController

One approach is to create a temporary UIViewController each time you need a new cell. By setting up the XIB the way we did, the temporary UIViewController has the cell as its view attribute. After we grab a pointer to that, we can release the view controller.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"];
    if (cell == nil) {
		// Create a temporary UIViewController to instantiate the custom cell.
        UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:@"BDCustomCell" bundle:nil];
		// Grab a pointer to the custom cell.
        cell = (BDCustomCell *)temporaryController.view;
		// Release the temporary UIViewController.
        [temporaryController release];
    }

    return cell;
}

NSBundle:loadNibNamed:owner:options:

Another approach is to load the NIB file and grab the cell directly, as it’s the only top-level object in the NIB.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"];
    if (cell == nil) {
		// Load the top-level objects from the custom cell XIB.
        NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"BDCustomCell" owner:self options:nil];
		// Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
		cell = [topLevelObjects objectAtIndex:0];
    }

    return cell;
}

7 Comments

Posted by
Brendan
28 June 2009 @ 7pm

The top level object has previously changed between SDK version.

If you change the File Proxy in the custom cell NIB to the tables controller, you can define one property in the table controller for the cell, and link it in the NIB. Hence when you call loadNibNamed, since the owner is self, the property will be set,
giving access to the custom cell.
Have a look at:
http://iphonedevbook.com/forum/viewtopic.php?f=14&t=27

https://devforums.apple.com/thread/3469?start=0&tstart=0


Posted by
bdunagan
29 June 2009 @ 6am

Interesting. Thanks for the pointer!


Posted by
haiku
6 August 2009 @ 8pm

I think that if you set a breakpoint you will find that your cell is not actually being reused in either case. At least they do not seem to be in the tests I’ve done. Most sample code that is doing this on the net is reloading the cell from the nib/xib file every time because the dequeue cell command is failing even though the cell has a reuseIdentifier (which is supposed to trigger it to be reused). They are not getting added to the _reusableTableCells dictionary in the table view for some reason.

If you can confirm that your solution doesn’t have this problem, I’d be interested to hear that.


Posted by
haiku
6 August 2009 @ 9pm

Ah! indicates that there is no re-use until there is one for every visible row in the table! very good.


Posted by
haiku
6 August 2009 @ 9pm

hmm. tried to put a link in that last post: “http://humblecoder.blogspot.com/2009/05/iphone-tutorial-creating-table-cells-in.html” perhaps?


Posted by
bdunagan
7 August 2009 @ 10pm

Great! Glad it worked for you. It’s true that it doesn’t reuse the cells until an existing one goes off the screen and a new one is needed. That confused me at first as well. And thanks for the link; much more detail in that post.


Posted by
Mahboud
22 November 2009 @ 7pm

Or you can instantiate this way (using an IBOutlet):

HighScoresTableCell *cell = (HighScoresTableCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
// refresh the outlet
[[NSBundle mainBundle] loadNibNamed:@”HighScoresTableCell” owner:self options:nil];
cell = highScoresTableCell; // get the object from the outlet
highScoresTableCell = nil; // make sure this can’t be re-used accidentally
}

// Set up the cell…


Leave a Comment