Use Composition for TableView Based Components
UITableView is the workhorse class for iOS user interfaces. It just fits so well for the sort of data that mobile apps concern themselves with. However, creating tableviews using UITableViewController can be a shortsighted idea.
It is far easier to get started with a UITableViewController, in storyboards as well as code. They offer pull-to-refresh for free, automatically assign themselves as data source and delegate, offer a template as a starting point, and form a cohesive little unit. All well and good, but only until you need to add a view that does not belong in either the cells or as a header or footer. You can add things programmatically but it compromises the abstraction that UITableViewController tries to build and soon, the whole view controller looses cohesion. This is the ugly picture even before we realize the fact that it is a terrible idea for the view controller to also be the data source and the delegate, in addition to being, you know, the view-controller.
I have been going through Code Complete all over again and I remembered something that really sat with me the first time:
“In one of object-oriented programming’s seminal papers, Barbara Liskov argued that you shouldn’t inherit from a base class unless the derived class truly “is a” more specific version of the base class (Liskov 1988).
When we are building, say, a TodoListViewController, the question to ask is: is this view controller a more specific version of the UITableViewController class? The answer is always no. TodoListViewController is a different abstraction than a UITableViewController. It is not a table view, it is a todo list (that it looks like or uses UITableView is happenstance). It cannot possibly satisfy the is a requirement, and hence, it should not inherit from UITableViewController.
A good solutuon here, in my opinion, is compostion. If, you need to add some functionality on top of UITableView, e.g. enabling editing, the place to do that is in a subclass of UITableView, say, EditableTableView. EditableTableView maintains the sanctity of UITableView’s interface as well as abstraction and hence, is a good subclass. This EditableTableView can then be used across the app through composition.
Now, your TodoListViewController has an editable table view as a property, that it configures and uses the way it sees fit. The controller itself is not a table view controller or a derivative of it. That it has an EditableTableView object is just an implementation detail, a detail that should not be exposed to the outside world, through interface or semantically.
What composition allows us here are some very pragmatic benefits which will manifest themselves along the lifetime of the app:
- Say, you decided to write an iPad version of the app and now, suddenly, you need to move to UICollectionView (that is not the only reason to use them. In my opinion, collections views should be preferred over table views even for table view looking UI). Now, you do not need to rewrite the TodoListViewController component but only swap the tableView object for a collectionView object.
- More importantly, since you used a higher level of abstraction than UITableViewController, nowhere in the rest of the app, outside TodoListViewController, will you use methods from UITableViewController. This builds a great encapsulated component with a limited interface. Now, when moving to UICollectionView, there will be no side-effects across the app or the requirement to migrate all the dependent code to the interface of the collection view.
- Composition with UITableView this way also facilitates dependency injection. You can easily reuse the TodoListViewController as a component and provide it with different looking/working table views depending on the context.
- You can add any custom views, anywhere in the view controller around the table view using InterfaceBuilder or code. This alone makes the idea of composition a better pick.
In the thought process behind designing components, I have come to visit composition first. Inheritance can add complexity and unless you see a strong is-a relationship, it is not the right pick.
For UITableViewControllers, is-a almost never holds.