Nitpicky details: Updating UITableViewCell 

Published 2018-02-07 on flowinho.io

category | code swift ios code-review programming

During a code-review of a newly implemented listing screen our team encountered an implementation of a UITableViewCell subclass which featured IBOutlet updating using the didSet-closure.

It roughly looked like this:

public class myExampleCell: UITableViewCell {
    @IBOutlet weak private var firstLabel
    @IBOutlet weak private var secondLabel

    public var entity:Entity {
      didSet {
        firstLabel.text = entity.stringOne
        secondLabel.text = entity.stringTwo
      }
    }
}

The corresponding UITableViewDataSource looked something like this:

extension ExampleViewController: UITableViewDataSource {

  func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    ...
    cell.entity = data[indexPath.row]
    ...
  }

}

This implementation immediately triggered a discussion with the co-worker that implemented this, because while this is perfectly fine functionality-wise i have an issue with using the didSet{ } closure to update IBOutlets.

Why?

There are two reasons for this:

  • The above implementation stores a reference to the ViewModel entity inside the cell. This behavior was not necessary in our particular use-case and results in the code storing data inside UIViews that are declared as ReusableViews.
  • The side-effects of assigning the property is unclear. In this particular case, the code written above could:
    • Store the ViewModel entity inside the cell, without any follow-up functionality.
    • Store the ViewModel entity inside the cell, with a particular effect, that is not obvious to the class that assigns the property.

This implementation of UITableViewCell just doesn’t expose a proper and understandable way of how to actually update it’s IBOutlets. We discussed what a better way to tackle this issue would be, and agreed to communicate clearly how the IBOutlets can and should be updated: by using a specific function.1

We changed the implementation to look like this:

public class myExampleCell: UITableViewCell {
    @IBOutlet weak private var firstLabel
    @IBOutlet weak private var secondLabel

    public func updateOutlets(with entity:Entity){
      self.firstLabel.text = entity.stringOne
      self.secondLabel.text = entity.stringTwo
    }
}
extension ExampleViewController: UITableViewDataSource {

  func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    ...
    cell.updateOutlets(with: data[indexPath.row])
    ...
  }

}

This way it’s clearly defined and publicly declared how the Outlets should be updated.

  • Because the function is publicly exposed, while the IBOutlets are not, any programmer utilizing this code recognizes the fact that this is the proper way to update this cells IBOutlets.
  • Because their accessibility level is private, they are not exposed to the utilizing class which in contrary also means that the programmer implementing the cell knows that for the entry point for UI updates is this function only.

As an additional beneficial effect the unnecessary2 storing of the ViewModel reference was removed as well.

Swift is an incredible language with a lot of amazing features, but never forget: with great power comes great responsibility, and with (great) stuff like the didSet{} closure available, it’s pretty easy to forget about a very important aspect of our everyday work: the programmer that has to use this code.

Stuff like this is why i like code-reviews.

  1. Because we utilize POP using a function was the natural next step. POP: Protocol oriented programming, https://www.infoq.com/news/2015/06/protocol-oriented-swift 

  2. Storing View-Model entities in UITableViewCell subclasses can be a valid approach, it was just not needed in our particular use-case.