Crafting Stateful Table Views

A UITableView in iOS is the most robust solution to displaying a scrollable list of items without compromising on performance or aesthetics. UIKit provides a rock solid API when it comes to working with tables. But, it’s upto us, the consumers of this API that should be mindful of putting this to use in a way that’s been intended to.

Just like any API — there’s no right or wrong way to do something. But let’s agree that certain approaches are better than others.

Table Views are very often used to list data as a result of a network call. This leads to the View Controller dealing with a variety of different states. A Naive approach to this problem would require keeping a reference to the various states in the View Controller as properties:

  • items Array to hold the list of items which is populated by the network call
  • error Error object that is declared when there’s an error, and used to relay it back to the user
  • isLoading A Loading State used to show/hide a spinner And another case to display an empty state when items.count is empty.

Although there’s nothing wrong with this approach, handling states this way is not very ideal since a lot of moving parts are involved, leading to weird edge-cases at times and less readability.

This post attempts to explain how to design table views that have clearly defined states.

Stateful Table View

A functioning UITableView would have the following states during its lifecycle:

  • empty
  • loading
  • populated
  • error

Designing a Table View — with clearly defined states

Step 0: Lifecycle of a Table View

A Table View is simply a component that displays different kinds of information based on its state. It’s often easy to forget this fact since when we get lost into the nitty-gritties of it because of it’s populated state. But, it actually looks something like this:

Step I: Modelling the State

enum State {
    case loading
    case populated([Item])
    case empty
    case error(Error)
}

We could model the states of the tableview using an enum.

In addition to that, using associated values we are able to even describe the relevant values a state could associate itself with.

Step II: Reference to the Table View State

Using a property on the view controller we could hold the reference to the state of the table view:

var state: State = .empty

Why do we need to capture this into a variable, you ask? This is because we can now have a single source of truth for the state of the table view in the view controller.
This is a mutable property because, we’re going to update this at different points during the lifecycle of our table view.

Step III: Updating the State

Now that we have access to the state, we can easily update this state at different points in the lifecycle of our table view. We might want to update the states at the following points:

  • Default State: empty
  • Initial Loading State, When the table view is initialised and data is fetched: loading
  • After the data is fetched, say from a server: populated([Item])  using the associated value here we have easy access to the items that we might need to populate the table with  
  • Incase there’s an error: error(Error)

The Power of Associated Values:

We’ve seen how associated values are coupled with the enum cases, this pattern helps us write really tasty swift code like this:

enum State {
    case loading
    case populated([Item])
    case empty
    case error(Error)
    
    var items: [Item] {
        switch self {     
            case .populated(let items): return items
            default: return []     
        }   
    }
}


We can ask our state object for items once its populated:

let items = state.items


And get access to our items automagically.

Step IV: Updating the UI

Fortunately or unfortunately, since SwiftUI was just announced and its going to be a while before all our code becomes obsolete. This means that we still have to update our UI based on state changes ourselves like it’s 2018.

There’s a neat way to do this, using property observers:

var state: State {
  didSet {
    tableView.reloadData()
  }
}


With that one line of code, our table view suddenly becomes incredibly responsive to state changes.


Fin

In this post we have seen:

  • why there’s a need to clearly define states in a table view
  • how to do it using an enum-driven approach
  • how to use associated values in enum cases to tightly couple model data with the state
  • how to use property observers to respond to state changes

I hope this helps understand how to write cleaner and much more maintainable tableviews.


Here's a quick project I put together that gives a basic idea of how this can be taken forward: Stateful TableView