Let’s do it better: UITableViewCell

Alex Shirokov
5 min readNov 23, 2018

--

Every iOS Developer creates UITableViewCells, but let’s do it better.
Here are five principles based on my practice.

TL;DR

  1. Do it in its own xib;
  2. Make UI private;
  3. Do not configure with the model;
  4. Let em crush;
  5. Reset before reuse.

Source code of the complete sample application: https://github.com/alekseishirokov/medium-01-better-uitableviewcell

Do it in its own xib

Every time you create a new UITableViewController in the storyboard, the XCode creates an empty cell prototype. That leads the developer to use this template to place UI elements right into this cell view.

But this is a bad habit. Sooner or later your project manager will ask you to use the exact same cell design but for another table.
I’ve got rid of this behaviour when they told me that documents list must be shown not only at the person’s detail view but also at the department’s detail view. My document’s cell prototype contained 10 UILabels, 2 UIImageViews and a bunch of tricky constraints. It was not easy to do, despite of XCode has a copy-paste commands.

Let’s do it better: create UITableViewCell in its own xib file and reuse it in any table you want.

It is easy to do. Press Cmd+N to create a new file. Then select iOSCocoa Touch Class. Then select the UITableViewCell in the Subclass of dropdown, enter the class name, and check the Also create XIB file checkbox.

XCode creates two files: DocumentTableViewCell.swift and DocumentTableViewCell.xib.

Use xib file to place UI elements into the cell view. Use swift file to bound IBOutlets with these UI elements.

DocumentTableViewCell.xib

Then you use this DocumentTableViewCell in the table view controller like this:

Notice, that before you create an instance of the DocumentTableViewCell you need to register this class by tableView.register. This is for table view controller to know where to find necessary cell elements.
If your table uses many cell views at once, you can register several cell views with their own reuse identifiers.

Make UI private

Often in table view controllers I meet a code like this:

I don’t like that UILabels are used directly. Yes, I also tend to create cell views as dumb as possible, but they needs to be at least smart enough not to expose their UI’s to others.

Let’s make them private. It means that the table view controller does not need to know about internals of the table view cell.

My motivation in doing so was raised after the day a designer comes to me and says that besides showing the priority of the document, we need to mark the hight priority ones with the red color and add a red bell image to the document icon:

DocumentTableViewCell.xib

That means I need to refactor every table view controller which uses my DocumentTableViewCell like this:

Nowadays I do it better. I declare UI outlets private to the cell view. I pass the data to UI elements using the configure method only:

In the table view controller, you just pass document’s priority as a parameter to the configure method.

An intuition here that priority is the data, and it is up to the cell view how to present it.

Also there is no need to refactor table view controllers in case the cell design has changed.

Do not configure with the model

It is common, even in tutorials, to fill a cell with the data by passing the model object to the configure method:

It seems like an elegant solution, but it’s not. This couples the view of the cell with the model object. Cell view depends on the structure of the model object. But MVC pattern, which we adhere to, is all about decoupling the view and the model.

I tend to pass the data to the configure method as separate parameters:

It allows me to reuse the cell view in another table no matter what data object I will use there.

The cell view designer does not need to know about the data model.

Also, when declaring the configure method, I can strictly specify, which data is optional and which is required to present the cell view properly.

Look, the ‘number’ is optional.

You might notice, that priority as an Int is still coupled with the model. Also there is a question: what if the value of the priority would be 4? To resolve this uncertainty, let’s introduce a Priority enum. Declare it in a table view cell class, because it refers to a view:

By doing that, you strictly limit the range of priority values. That makes configure function more robust and safe:

But you need to do some extra work in the table view controller. Create a private function priority(from:), which transforms integer value to the enumeration value. After this, the cell view and the model are completely independent.

Let em crush

Sometimes I see the cell creation like this way:

Note, that if the dequeueReusableCell returning value is not of type DocumentTableViewCell than we silently create instance of the DocumentTableViewCell.

Let’s do it better: use the forced unwrapping.

This is not only less to code, but it also crashes with an error if the reuse identifier does not correspond to the expected cell type. This crash is good.
My opinion is if the dequeueReusableCell can’t create a cell of the expected type, that it means something wrong with my application design. And I want to know this as soon as possible at the development stage.

Reset before reuse

If for some reason you do not follow the principle of ‘make them private’, you need to create a reset method in the cell view class, which sets all UI elements to their default states.

Then you call this method in the table view controller after the dequeueReusableCell method but before setting any values to UI elements.

Calling reset method prevents UI elements to be non-initialized or stayed with previous values while cell reusing.

NOTE: But if you follow all the principles mentioned earlier: declare UI elements private, set them only by configure method, and your configure method sets up all of the cell’s UI elements, then you may omit the reset method.

Recap

  1. Do it in its own xib;
  2. Make UI private;
  3. Do not configure with the model;
  4. Let em crush;
  5. Reset before reuse.

--

--