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 callerror
Error object that is declared when there’s an error, and used to relay it back to the userisLoading
A Loading State used to show/hide a spinner And another case to display an empty state whenitems.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
1enum State {2 case loading3 case populated([Item])4 case empty5 case error(Error)6}
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:
1var 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:
1enum State {2 case loading3 case populated([Item])4 case empty5 case error(Error)67 var items: [Item] {8 switch self {9 case .populated(let items): return items10 default: return []11 }12 }13}
We can ask our state object for items once its populated:
1let 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:
1var state: State {2 didSet {3 tableView.reloadData()4 }5}
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