Like many iPhone apps, the app I’m currently working on uses several table views. Most of these display actual lists of data, and some are used as a convenient layout mechanism for input fields, navigation, and other UI elements (similar to iPhone preference screens).
UITableView and its associated classes like
UITableViewDelegate are very powerful, but they also require a fair amount of boilerplate code split across several methods. The specific matter that I am tackling in this post is the creation of cells, which happens inside the
[UITableViewDataSource tableView:cellForRowAtIndexPath:] method. When dealing with only a single type of cell, it typically looks like this:
As you can see, there’s a lot of boilerplate code here. This works well enough with one type of cell, but if you’re dealing with multiple types of cells (particularly in a grouped table view), this approach quickly gets out of hand. You end up with a long method with large, ugly switch statements. But if you look at this method closely, you’ll notice that there are only a few cell-specific areas:
The cell identifier (
MyCellin my example). This is used to look up and reuse existing cells (e.g. when scrolling through a large table of items) and avoids the costly creation of new cells every time. It’s a standard cell creation pattern for the iPhone and makes a lot of sense, but it also means that the cell specific code is spread across several places.
The initialization code. This is where a cell of a given type is initialized for the first time. If you can get away with the standard cell styles (which cover a few different layouts of labels and images), you usually don’t need to do much here, besides setting your colors, fonts, and perhaps selection style. Otherwise, this is where you want to create and add your subviews, and assign a tag to them so you can populate them later.
The customization code. Given a cell with the correct properties and subviews (which may have been reused or created during this call), this is where you populate it with the correct data. This typically involves looking up some sort of data based on the indexPath, and setting it either on the cell itself (using the
imageViewproperties) or on its subview. The latter requires looking up the subviews using the tags you’ve assigned earlier.
With this in mind, I decided to factor out all the cell specific code, resulting in a generic method in my base view that can be used to create all the cells of my app. Here’s what that method looks like:
Structurally, this method is very similar to the previous one. It first tries to reuse an existing cell, creating and initializing a new one if it doesn’t find one. It then customizes the cell. You’ll notice that I’m using some
performSelector calls here, coupled with a naming convention. For example, given an identifier of
"Foobar", I will look for
customizeCellForFoobar:indexPath to initialize and customize this cell respectively. A simple example might look like this:
Note that both methods are optional. In some cases (particularly in table views that are used for preferences or similar types of UI elements), there’s only a single cell of any given type, so I perform the complete initialization in
initCellForFoobar and omit the
customizeCellForFoobar method. In other cases, I may not require any special initialization and only have a
Obviously, the example above is trivial, and both methods can get significantly longer when dealing with custom subviews. In that case I am using the same tag based approach I mentioned above: Assign a tag to each subview inside
initCellForFoobar, then look up the subview using the tag in
customizeCellForFoobar. But the important thing is that the code is well factored, and the cell specific code is not mixed with boilerplate code.
Last not least, an example of the actual
UITableViewDataSource method to create a new cell:
The example above is what I would typically use for a grouped table view, where each section contains a specific type of cell. But obviously this approach supports any type of table view, grouped or non-grouped. You just need to plug in your own logic to determine the type of cell, and leave everything else to the
createCellForIdentifier:tableView:indexPath:style:selectable method we created earlier.
There are probably other approaches for simplifying working with table views, but this approach has worked very well for me. It really cuts down significantly on the amount of boilerplate code and allows me to focus on the actual application specific code.
Any questions, suggestions for further improving this, or perhaps alternative solutions that you’ve used? Leave a comment!