It’s not the first time that we’ve been discussing about how we should structure view specific code and controller logic. This is a post to describe a new pattern we called Model-View-Controller-Presenter to create highly reusable components.
Lets say we have an app that display lists of cars as an example:
There’re 3 screens here, #1 List, #2 Photos, #3 Details. Both List and Photos are UITableViewController that representing list of Cars, tapping on one of the cell will goes to theDetails screen.
We’ve learned to use categories for cell configuration logic, it may end up with something like this:
- Multiple cell subclasses with same or similar IBOutlets.
- Possibly duplicated category implementations.
By taking a deeper look into our code:
We have 16 files (including cell subclass and categories) that basically separated into 4 groups. You might already noticed, the headers and implementation files are extremely similar in each set. We wanted to make this better.
Optimisation #1: Use a Single Base Class for the Cells
To keep it DRY, you may already know is to extract those logic into a single base class, here’s probably what it’d look like:
By prototyping our cells in Interface Builder, we can even remove the style specific subclasses. All our layout is specified in our Storyboard or Cell.xib, so we can simply wire up or unwire relevant UI elements with IBOutlets. Our base category extension will just do the right thing to configure our views.
This was an earlier version of MVCP. We were able to use a single object to propagate data in every other cells. This is a “Presenter” of a specific model type.
Yet this is a problem because we are only serving UITableViewCells. A slight design change in Photoshop can likely break this coheriance. Say now we’ve to adopt some design changes:
Notice that now our Photos layout is now best to be implemented with UICollectionView, and ourDetail screen showed a custom titleView with a bit more information on the displaying car.
If we tried to implement those changes…
Our files where we previously tried to remove are inevitably returned, and take a look at the new header files that we’ve created:
Looks familiar isn’t it?
This is why putting configuration logic inside any view subclasses, no matter category or not, doesn’t solve this fundamental problem. It’s highly coupled with the subclass, if we’re going to port to different view, we have no choice but to duplicate the logic.
Our solution is to extract that logic out, and use it in any views we would want to.
Optimization #2 Introducing Model Presenter
We need a way that can be reused no matter what kind of subclasses it is. Imagine there’s someone who already knows how to represent a model in different types of UI elements, but itself is not a subclass of any UITableViewCell. Lets call it a Model Presenter here.
It’s easy to extract our logic into a this class, we’ll name it CarPresenter here.
This is an object that knows how to represent every aspect of a certain model. From now on, it’s not necessary to declare any IBOutlets on our cells nor custom view subclasses, instead we just need to have a reference to our Model Presenter.
The above figure shows that if we’re using xibs, we can drag a custom objects into our view tree, make it a subclass of CarPresenter, and connect the IBOutlets to that object.
However this turns into a problem if we compile and run…
Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘invalid nib registered for identifier (CarPhotoCell) — nib must contain exactly one top level object which must be a UICollectionReusableView instance.
This is funny limitation that raised by Apple’s API. I originally created a workaround that helps developers to bypass this limitation. It seemed to work, but this technique just doesn’t work well with Storyboard cell prototyping.
Fortunately, the solution is quick and simple. By changing our CarPresenter into a UIViewsubclass, we can now put it directly into the view hierachy. (I like using UIImageView subclass, because also allows us to add a little nice icon to identify itself. I found this to be a nice indicator on what type of model we’re displaying on which cells.)
Our CarPresenter is now highly reusable and I can use it in anywhere I wanted, including the original UITableViewCell, our UICollectionViewCell and also our custom title view in the Detail screen.
As a bonus, we also completely get rid of any category files that would be created per model per subview.
How actually Model-View-Controller-Presenter can be described with this diagram.
Comparing to the original Model-View-Controller diagram:
The main difference between them is that now the Controller will not directly interacting with our views. This is a healthy change that our view controller so it can concentrate on performing navigation related operations, and our Presenter serves as a mediator that can be easily reused to display any view logics related to a certain model type.
I think this is a super effective way to modularise our code. You should find that MVCP allows you to write your view logic once and reuse it everywhere. If you are interested to see a complete working example, download the example code, and feel free to send me any comments.
This post couldn’t really be done without the help of Simon Pang, his discussion on this topic largely helped to explain things a lot more clearer than it would originally be.