Protocol Oriented UITableViewCells

Posted by Andrea Bizzotto on June 18, 2016
Read Time: 4 min

This blog post shows how to implement a set of UITableViewCell variants by using Protocol Oriented Programming (POP) as a better alternative to subclassing or composition.

NOTE: This material has been inspired by these two great write-ups on this topic:

Show me the cells!

While building my Cast Player app, I needed to add a couple of settings pages to let the user tweak a few things in the app, as well as include links to a Send Feedback form and some About screens. This is the end result:

Setting screens Cast Player Settings screens

From the screens above, we can identify six different types of cells:

Cell name and image Description
DisclosureTitleTableViewCell A cell with a title and a disclosure chevron for navigation
BytesCountTitleTableViewCell A cell with a title and a human readable byte count
DisclosureBytesCountTitleTableViewCell A variant of the above with a disclosure chevron
ActionTitleTableViewCell An action cell with a title but no disclosure chevron
ValuePickerTableViewCell A cell for selecting amongst a set of values
ValuePickerLabelTableViewCell A variant of the above with a title

As a whole, these cells share the following traits (or behaviours):

  • Cell Highlighting
  • Display a title
  • Display a human readable byte count

How can we go about building a set of classes that well represents these six variants of cells? A good first step is to list all the cell types and required traits in a grid:

Cell name and image Highlight Support Display Title Display Byte Count
DisclosureTitleTableViewCell
BytesCountTitleTableViewCell
DisclosureBytesCountTitleTableViewCell
ActionTitleTableViewCell
ValuePickerTableViewCell
ValuePickerLabelTableViewCell

As we can see from the table above:

  • None of the traits are adopted by all cells.
  • Some of the traits are required for some cells but not for others.

This means that building a UITableViewCell class hierarchy would not work well here. In fact, none of these cell types is even a good candidate to be the base class. What to do? 🤔

Protocols & extensions! Yay!

Luckily, this great post about Mixins and Traits in Swift 2.0 can point us in the right direction. Intuitively, protocols and extensions could really work well for this use case, but how to use them correctly here?

The idea is that we can define the interface for each of our traits with a protocol, and provide a default implementation with an extension. Then, we can create very small subclasses of UITableViewCell and have them just conform to the protocols as needed. A very similar problem is explored in this other great talk named Introduction to Protocol-Oriented MVVM. Let’s see how this works!

Armed with protocols and extensions, we can write the first trait, TitlePresentable:

protocol TitlePresentable {
    
    var titleLabel: UILabel! { get set }

    func setTitle(title: String?)
}

extension TitlePresentable {
    
    func setTitle(title: String?) {
        titleLabel.text = title
    }
}

The second trait, BytesCountPresentable takes a similar form:

protocol BytesCountPresentable {
    
    var bytesCountLabel: UILabel! { get set }

    func setBytesCount(bytesCount: Int64?)
}

extension BytesCountPresentable {
    
    func setBytesCount(bytesCount: Int64?) {
    
        bytesCountLabel.text = bytesCountString(bytesCount)
    }

    private func bytesCountString(bytesCount: Int64?) -> String {
        
        if let bytesCount = bytesCount {
            return bytesCount.bytesFormattedString() // defined elsewhere
        }
        return "N/A"
    }
}

With these two traits alone, the implementation of the BytesCountTitleTableViewCell cell becomes trivial:

class BytesCountTitleTableViewCell: UITableViewCell, TitlePresentable, BytesCountPresentable {
    
    @IBOutlet var titleLabel: UILabel!

    @IBOutlet var bytesCountLabel: UILabel!
}

By simply conforming to the TitlePresentable and BytesCountPresentable protocols, the BytesCountTitleTableViewCell class inherits the behaviour added by the corresponding extensions. Other classes can inherit the same behaviour by means of protocol conformance. This is very powerful! 💪💪

Note that the class needs to redeclare the titleLabel and bytesCountLabel properties to correctly implement the protocols, but this is ok as we also need to specify that these are IBOutlet variables so that we can link them from Interface Builder.

What about the cell highlighting trait?

Let’s start from this class:

class HighlightableTableViewCell: UITableViewCell {

    override func setHighlighted(highlighted: Bool, animated: Bool) {
        
        self.contentView.backgroundColor = highlighted ? UIColor(white: 217.0/255.0, alpha: 1.0) : nil
    }
}

Here we choose to implement cell highlighting by overriding the setHighlighted() method of UITableViewCell.

Following the same approach outlined above, we could define a HighlightableView protocol and extension like so:

protocol HighlightableView {
    
    func setHighlighted(highlighted: Bool, animated: Bool)
}

extension HighlightableView {
    
    func setHighlighted(highlighted: Bool, animated: Bool) {

        print("extension highlighted") // not printed
    }
}

Then, we could try to create a UITableViewCell subclass that conforms to HighlightableView and see if cell highlighting works. No such luck unfortunately. 🚫

I believe this is because when the setHighlighted() method is called, the base UITableViewCell method will be called rather than the protocol extension method.

In other words, protocol extensions are meant to add behaviour to existing classes, but cannot be used to replace method overriding.

Cell highlighting, reloaded!

To make cell highlighting work, we can simply keep the HighlightableTableViewCell class defined above and subclass from it where necessary. Example:

class DisclosureTitleTableViewCell: HighlightableTableViewCell, TitlePresentable {
    
    @IBOutlet var titleLabel: UILabel!
}

class DisclosureBytesCountTitleTableViewCell: HighlightableTableViewCell, TitlePresentable, BytesCountPresentable {
    
    @IBOutlet var titleLabel: UILabel!
    
    @IBOutlet var bytesCountLabel: UILabel!
}

This works nicely as we can simply choose to use HighlightableTableViewCell or UITableViewCell as a base class depending on whether we need the cell highlighting trait or not, and mix and match the remaining TitlePresentable and BytesCountPresentable traits as needed.

The resulting class hierarchy is summarised as follows:

In the code, this is represented as:

class HighlightableTableViewCell: UITableViewCell
 
class DisclosureTitleTableViewCell: HighlightableTableViewCell, TitlePresentable
 
class BytesCountTitleTableViewCell: UITableViewCell, TitlePresentable, BytesCountPresentable
 
class DisclosureBytesCountTitleTableViewCell: HighlightableTableViewCell, TitlePresentable, BytesCountPresentable
 
class ActionTitleTableViewCell: HighlightableTableViewCell, TitlePresentable
 
class ValuePickerTableViewCell: UITableViewCell
 
class ValuePickerLabelTableViewCell: ValuePickerTableViewCell, TitlePresentable

NOTE: HighlightableTableViewCell is the only trait that is implemented by subclassing and can serve as base class for three additional cell types. Once the base class is chosen for a given type, additional traits can only be added via protocol extensions.

Conclusion

Using Swift protocols and extensions as a way to add behaviour (traits) can lead to big wins in the design of our classes and helps keeping hierarchies flat. 🚀 Main advantages:

  • Flatter class hierarchy
  • Resulting APIs/classes can be more easily extended
  • Easier to add and remove protocol conformance than adding and removing code
  • Less code duplication

Feedback welcome

The solution presented in this post has worked well for me and my specific use case - I hope that this practical example helps understanding how to use protocol extensions better than I did when I started. If you know of a better way of doing this, let me know in the comments!

Want to see your photos and videos on the big TV? Then Cast Player is the right app for you. Sign up to the mailing list to receive product updates. You can also get beta access with TestFlight.

If you liked this post, you can share it with your followers or follow me on Twitter!

Get an email when I write a new story. No spam. Ever.

* indicates required