One UITableViewController to Rule Them All

Table views are the bread and butter of mobile development. Usually a table view will contain one cell view, but sometimes you want to do something like the image below.

This isn’t a random screen: I received this screen when I was just getting started in iOS development. I was told that there were roughly 15 different pieces of data (read: cells) that may or may not be displayed based on the data from the server. I admit that my solution at the time was poor (I used a scroll view for crying out loud), but I’ve since learned better.

A table view is the way to go, but how do you manage all the different cell types? In the view controller? Sounds like a mess! And any future updates involve a massive, convoluted file. To get around the bloated view controller, a friend of mine showed me a strategy using a presenter and enums, and it changed the way I approach table views all together.

The main idea of this strategy is to create an array of Cell objects which abstracts the model into primitive data types. You’ll first need a Cell object that holds a portion of the data with some cell metadata. Cell will hold a title, value, and a CellType (which is an enum):

struct Cell {
var title: String
var value: Any?
var cellType: CellType
}
enum CellType {
case text, toggle, image
}

Notice that value is of type Any so that we can use any object and pass it to the cell (preferably primitives, but it can be anything if needs be). Now that we have the Cell object, we’ll build the array of Cells.

Call it a controller if you want, but this class’s job is to package the data in a way that the view can display it. The presenter will be given an object (in this case, a customer), and will package the data into an array of Cell objects we made above that the view controller can understand:

class CustomerShowPresenter {
var customer: Customer
var cells: [Cell] = []
init(customer: Customer) {
self.customer = customer
}
func loadCells() {
cells = []

cells.append(Cell(title: "Image", value: customer.image, cellType: .image))
cells.append(Cell(title: "Name", value: customer.name, cellType: .text))
cells.append(Cell(title: "Birthdate", value: customer.birthdate, cellType: .date))
}
}

The power of this strategy is that we can reuse it with any object because the array's contents are object agnostic. This is critical. Once the cells are loaded into the array, it’s time to make a reusable UITableViewController.

Much like the cells array above, a reusable UITableViewController doesn’t care about the object that it’s representing, but rather the cell data that it is displaying. The presenter takes the concrete data object and dumbs the data down into abstract cell data that the view controller can display. Inside the UITableViewController, most of the calls get to be cut and dry so I won’t go over a lot of it. The most interesting part of this is the cellForRowAtIndexPath call, so let’s look at that:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//get the cell data from the presenter
let cellData = presenter.cells[indexPath.row]
//switch on the cell type to determine which cell you should show. Each cell can take a Cell object and configure itself.
switch cellData.cellType {
case .text:
let cell = tableView.dequeueReusableCell(withIdentifier: "text", for: indexPath) as! TextTableViewCell
cell.configure(cellData)
return cell
case .date:
let cell = tableView.dequeueReusableCell(withIdentifier: "date", for: indexPath) as! DateTableViewCell
cell.configure(cellData)
return cell
case .image:
let cell = tableView.dequeueReusableCell(withIdentifier: "image", for: indexPath) as! ImageTableViewCell
cell.configure(cellData)
return cell
}

Do you see the Customer object anywhere? The view controller doesn’t need to know what the object is, but instead it cares what type of cell it should display at each index. Ultimately, the presenter could transform any object it into an array of Cell objects and the same ViewController could be used.

With the view controller ready, we can make a new presenter and not use a new view controller. A presenter protocol will help us do this.

For the purposes of this example, the protocol has only two things: an array of Cells and the function loadCell().

protocol CellPresenterProtocol {
var cells: [Cell] { get }
func loadCells()
}

With the protocol ready, we can inject the presenter when we instantiate the reusable view controller.

//Just showing how instantiating each page follows a very simple pattern
func showCustomerViewController(customer: Customer) {
let showPresenter = CustomerShowPresenter(customer: customer)
pushShowTableViewController(presenter: showPresenter)
}
func showOrderViewController(order: Order) {
let showPresenter = OrderShowPresenter(order: order)
pushShowTableViewController(presenter: showPresenter)
}
func showProductViewController(product: Product) {
let showPresenter = ProductShowPresenter(product: Product)
pushShowTableViewController(presenter: showPresenter)
}
private func pushShowTableViewController(presenter: CellPresenterProtocol) {
let showViewController = ShowTableViewController(presenter: presenter)
navigationController?.pushViewController(showViewController, animated: true)
}

Passing only an array of cell data to your view controller reduces the tableview delegate and data source complexity, especially when cells are of all different types. Setting up tables to handle cell data instead of data in this way has many advantages:

  1. Your view logic can be reused. Since the view controller doesn’t know about the object it’s showing but only about an abstract “Cell” object, the view controller can be easily reused, meaning you have a lot less code to build and manage.
  2. What the cells contain and how they’re ordered is more easily managed. If you want to change where a value is, moving it in the array is far easier than trying to manage it in the view controller.
  3. Presenters for show pages follow the same format, making them much more familiar and readable to the other devs on your team (or you in three months when you add a new feature to the code).

I hope this helps you build better and reusable table view logic.

Here’s a link to the demo project.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store