Clean Table View Code Using Swift Protocols

Getting my UITableView  code just right has been a long and ongoing quest for me, ever since I did my first implementation of the UITableViewDataSource  protocol. Other than the delegation pattern, the pattern of UITableViewDataSource  with it’s reusable cells, was what baffled me the most, when I started doing iOS programming. I simply couldn’t wrap my head around it.

Now, almost 7 years and a lot of UITableView s later, I think I’ve come up with a solution that will help me and hopefully my fellow iOS developers doing clean and maintainable UITableView  code while reducing the boilerplate to a minimum.

The promise I’m going to make is that, by introducing just three protocols, your 100+ line cellForRowAtIndexPath  implementation will be reduced to only a few lines of code, and help you do the separation of concerns at the right level. In fact when we’re done, you never have to write a UITableViewDataSource  again and adding new cells and sections to your table views will be easier than ever.

The blog post assumes you are using Swift and Storyboards (with Auto Layout and self sizing table view cells), but much of the code can be ported to Objective-C and the dear old nibs.

The code presented in this blog post can be found on GitHub.

Use protocols to help you do the right thing

Protocols in Swift have gained a lot of power over their Objective-C counterparts. But for me the main reasons for using protocols are still the same: communicating a way of doing stuff (enforcing a pattern, if you will), make things easier to test and finally offer an often better alternative to subclassing.

First I will show how introducing two protocols can help you write clean, maintainable and flexible table view code. The two protocols are:

  • TableViewCompatible
  • Configurable

TableViewCompatible

Usually a table view displays some model data sorted in a specific order. The way that is usually implemented, is by having an array of model objects and then, when cellForRowAtIndexPath:  gets called, dequeue a cell with a specific identifier from the table view, find the model object in the array of model objects using the index path parameter, and finally configure the cell with whatever properties from the model object is needed.

The problem with this approach is that now, you have a pretty tight coupling between your views, model data and UITableViewDataSource  implementation (more often than not, and instance of UIViewController ). All the configuration and layout is happening in the data source, often based on hardcoded index paths, and moving cells around or adding different cell types with different layouts will likely make the code more complex and harder to read and maintain.

What we really want is to make the data source indifferent as to which cell is being displayed and how it is laid out. Due to the reusable nature of UITableViewCells  it is not feasible to keep an array of all possible instances of the cells in the data source. So we need a way to separate out the responsibility of handling state and configuration of cells to some sort of view model.

A view model is essentially an object that carries the logic configuration of a view. If we create view models for all the objects we want to display in a table view and let the view models handle the creation and configuration of cells, all our data source has to do is to maintain the data structure containing the view models.

The TableViewCompatible  protocol will help you do just that, so let’s take a look at it:

If you let your model object conform to this protocol, all your data source have to do now, is to find the model object and call cellForTableView:atIndexPath: on it.

Let’s look at how the implementation might look:

The data source implementation will then look like this:

Now, this might not look like a lot, but, besides moving the responsibility of configuring the cell to the view model object, this implementation actually opens up for a lot of other benefits.

Take a look at the data property defined on the data source above. Right now it only supports objects of type MyModel . What if we make it support objects that conform to TableViewCompatible  instead?

Here’s what that will look like:

We now have a table view that supports any UITableViewCell  imaginable, as long as it is backed by a model conforming to the TableViewCompatible  protocol. Neat, right?

What this also means is that you can move cells of different types around in the array and then reload the table view, without changing a single line of code in your cellForRowAtIndexPath implementation.

But what about sections you may ask. We’ll come back to that. First let’s look at moving layout code to the view.

Configurable

As iOS developers we often have to make custom cells with layouts that are more complex than the ones provided by the framework. This means we need to write our own layout code based on things like the kind of model object we want to present and it’s state. In the old days we would put this code in cellForRowAtIndexPath , but since we are trying to help our future self, let’s see if we can do better.

Layout code rightly belong in the view rather than in the model. I hope we can all agree on that. Right?

To help us remember that–after all we are only humans–let’s introduce another protocol: Configurable .

Whenever you create a UITableViewCell  subclass, make it conform to the Configurable  protocol and put your layout code in the configureWithModel:  implementation. In the simple example of displaying the MyModel  object in a UITableViewCell  the implementation could look like this:

When having the layout code happen in the UITableViewCell  subclass you have the right separation of concerns. The MyModel  can be updated to support the Configurable  protocol:

Note: I use the implicitly unwrapped optional when casting to MyModelCell. Usually I don’t recommend doing that but in this case, if it fails, it’s a programming error that needs to crash the application sooner rather than later. It is in the same category as forgetting to connect an IBOutlet.

If you need to change how a cell looks, you do it in the cell class. If you don’t need the cell anymore, you just delete the cell class and let you TableViewCompatible  object return a different cell or simply remove it from the data array; everything else is unchanged.

A note on associated types

Swift has given us a lot of new tools to play with. One of these tools is protocols with associated types (or PATs as some prefers to call them). If you haven’t heard of PATs I can recommend this blog post from Natasha The Robot and this talk by Alexis Gallagher. Also in general about protocol oriented programming in Swift I recommend this WWDC talk from 2015.

Anyway, by adding an associated type declaration on the Configurable  protocol, we can make sure that the implementation only supports a specific type of model object, which will move errors from runtime to compile time.

Half time!

So to sum up: by introducing two simple protocols, I force myself to apply a pattern that helps me separate concerns to the right objects. My UITableViewDataSource  code now basically consist of reusable boilerplate code and when creating cells I follow a three step process:

  1. Create a view model that conforms to   TableViewCompatible  (or let an existing model object conform to the protocol).
  2. Create a UITableViewCell  subclass that conforms to Configurable.
  3. Add the model to the data array, at the index it should be shown in the table view.

The data source is now totally indifferent as to what kind of cells it shows and how they are laid out.

Sections

So what about sections? Well, you can basically do what you want with sections, but I have a way that I am becoming increasingly satisfied with, having used it in my past 3-4 projects (spoiler: it’s not really rocket science).

Let me introduce the TableViewSection :

The idea, as you probably already figured out, is to let your data source be backed by an array of TableViewSection objects. So after providing an actual implementation of TableViewSection , say MyTableViewSection :

… the  MyDataSource implementation will look like this:

Noticed the sorting in didSet ? I can’t really recommend doing that. You probably want to handle sorting somewhere else. In fact, beware of  didSet . It is to be used with caution (side effects’n all, you know).

Conclusion

We all want to do the right thing. Not to take the easy way out; cutting corners. But sometimes the real world makes you do things you didn’t mean to. That’s why you have to set yourself  up, so that the right way is actually the easy way too. I find protocols the perfect tool for this.

By introducing the three protocols:

  • TableViewCompatible (let your model or view model conform to this protocol)
  • Configurable (let your UITableViewCell  subclass conform to this protocol)
  • TableViewSection (let your UITableViewDataSource  be backed by an array of these)

And when letting my UITableViewDataSource  be backed by one property:

I’m now forced to do the right thing, and the right thing is actually making it very easy for me to handle layout changes as well as insertion, removal and deletion of cells, because I only have to worry about updating my model (in this case the sections  property) to reflect these updates.

A final word on Xcode snippets

Snippets in Xcode is a great tool that will help you writing boilerplate code really fast. Using the three protocols above, it’s really easy to create snippets that will make it easy and fast to write new cells. I have created two snippets, one for creating a new cell with a cell model and a UITableViewCell  subclass and one for the UITableViewDataSource  code. I have put both in the git repository. Put them in this folder:

Restart Xcode. The completion shortcuts are cell  and datasource  respectively from any Swift top level scope.

If you have comments or suggestions for improvement I would love to hear from you.

In the next post I will show how we can relieve our view controller for even more boilerplate table view code by using protocol extensions with default implementation. I will also show how to separate out the UITableViewDelegate  code as well as handling conformance to the new UITableViewDataSourcePrefetching  protocol.

6 Comments

  1. Keith Russell

    This looks really interesting to me. I half grasp it! I am eager to learn how to use protocol oriented programming but I do not have your 10 years of wisdom. Do you have an example of where you have used this to help me get up the learning curve?

    Thanks,

    Keith

    • Fredrik Nannestad

      Hi Keith

      I have not yet created an example project but I’m considering it. I pretty much use this pattern in all projects that have table view code. I think, to help you get started, create a simple project using storyboards and auto layout and add a UITableView to your view controller. This blog post assumes that you have the basic understanding of how to implement a UITableView and UITableViewDataSource. If that is the case, then add the three protocols to your project and start by having your UITableViewDataSource backed by an array of TableViewSections. Then implement a cell model conforming to TableViewCompatible and then a UITableViewCell subclass that conforms to Configurable. That should get you started.

  2. Keith Russell

    HI Fredrik,

    Thanks so much for taking the time to respond. I have actually set up many UITableViews, usually with custom UITableViewCells. I am still however a novice and I think that maybe (because you are so experienced) you underestimate the gap between someone like me that has a reasonable level of knowledge and yourself. I understand the basic concept behind protocol oriented programming as being an alternative to using inheritance to “compose” you classes/structs. I get how powefull this is but I maybe have not quite got a good picture of how to do this in this particular case. I originally tried to implement your blog in a Swift playground. I have a playground set up to test out tableview designs already. After your response, I created an Xcode project in case my problem was related to the playground. I still struggled. I tried lots of things. Here are some of the questions I struggled with to give you a better insight into my situation….. What do I do in my ViewController? Do I still make my view controller adopt UITableViewDataSource? and then make my viewcontroller.datasource = self? I created a tableview and a custom tableview cell which I made to be a subclass of UITableViewCell as I usually do. How do I actually adopt data as an array. I tried doing tis many ways and got lots of errors that I could not solve. Sorry to be a pain. You shared some really cool stuff that lots of people (see like me) would like to use. Take my comments as suggested ways to improve how you educate folks….. and keep doing what you are doing. I like how you think!

    Cheers,

    Keith

  3. Dave Wasden

    I would suggest one change, and that is to not modify your model objects with view code directly. It would be better if the model objects were clean, but in the view controller/presenter/view model class you extend the model objects with their view protocol, perhaps with an extension. I haven’t tried this yet, but I love what you are doing here with data source, and will let you know what I come up with as I implement your idea.

    Thanks!

    • Fredrik Nannestad

      Hi Dave

      Good point and I agree that we should keep our model objects clean. An extension of the model object implementing the TableViewCompatible protocol is one way of doing it. Another way would be to create a separate view model object. So in the blog post above the model object MyModel only serves as a way to demonstrate how to implement the TableViewCompatible protocol.

      By the way, we have now a GitHub repo with a more evolved version of this project https://github.com/jayway/CollectionAndTableViewCompatible.

  4. ansu

    How to fit this in MVVM design pattern. In MVVM, we should not move UIcomponent in VM.

Leave a Reply