Table View Cells in Interface Builder
There are a number of techniques for loading instances of UITableViewCell
from a nib file to use in your table views; up until recently, I thought that the best way was the Apple recommended way (developer documentation link). It also comes recommended by Jeff LaMarche, who discovered the technique after writing his book, Beginning iPhone Development.
The technique itself isn't a terrible but as the comments on Jeff's article will attest, there is a perception that the technique is hacky, or non-intention revealing. I'm not sure if I'd go as far as calling it a hack, but it certainly lacks enough clarity that somebody not familiar with the technique could find it potentially confusing.
The biggest issue I have with the technique is that it in order to wire up your custom cell with an outlet, your cell nib's file owner needs to be set to the controller that is using the cell. This introduces an unnecessary dependency on the controller and makes it harder to use in other controllers.
An alternative solution, as presented in Jeff's book is to loop through the array of bundle objects returned by loadNibNamed:owner:options
to find your cell.
It's a few more lines of code but it lends itself well to extraction into a custom category on UIViewController
.
In addition, I've added an assertion to ensure the nib file contains a cell and to fail fast if it doesn't (I can't think of any good reason why you would want this method to ever return nil).
Ensuring cell re-use for performant table views
One problem that both techniques have is that they require the reuse identifier to be set in interface builder; if you forget to do this, your cells will not be re-used and your nib file will have to be loaded for every cell in your table view.
In a recent article, Jeff LaMarche once again offers some advice on how to avoid this problem. It's a reasonable solution but requires a little more boilerplate code than I like - it will also not work if you need to use multiple reuse identifiers, something that Jeff points out in his article.
Instead, I offer an alternative, less intrusive, defensive approach to the problem. In the same way that we can use a simple assertion to ensure our nib file contains a cell, we can use an assertion to check that the cell also has a reuse identifier.
Originally, I added the assertion to the same method but realized that there may be times where you legitimately do not want to set a reuse identifier (e.g. for single-use cells), so I simply introduce a second method in the category:
Now, you still need to set the reuse identifier in Interface Builder, but by using this method we will always fail fast if we forget to do so.
There is also no reason why this technique cannot be combined with Jeff's; if you are using a sub-class of UITableViewCell
with your nib-based cell you could easily override the reuseIdentifier
method to return a static string but it's up to you.
So after all of this, the table view's tableView:cellForRowAtIndexPath:
method ends up looking like this:
There is still one issue left with this solution: the reliance on a string identifier is still prone to error. A single typo in the identifier in Interface Builder would stop cell reuse from working but would not be picked up by the compiler or the assertions.
This is certainly one advantage of Jeff's solution, which avoids setting the reuse identifier in the nib file completely, but we can still defend ourselves against this problem by introducing a simple and obvious convention: always use the same string for your cell identifier and your cell's nib name. By adhering to this convention, we can add one final check to the category method:
Conclusion
Both approaches have their pros and cons. For controller-specific single-use cells, bundling the cells with the nib file for that controller and hooking them up to outlets is probably the simplest solution and is the method I would choose. For general loading of reusable cells, this method seems clearer to me.
The UIViewController
category can be downloaded from GitHub.