POP III — Simplifying UITableView with different types of cells
--
This is the third part of the Power of Protocols series. We’ve earlier discussed responding to theme changes and removing network layer in your iOS app.
All the table views in your iOS app has a lot of code repetition. You have to write the DataSource and Delegate methods for each one of them. Add to that the problem of using different types of cells in a single table view, boiler plate code increases and some optional chaining/unsafe types might also creep up. Lets discuss an easy way to abstract all these and focus on making your table views awesome.
In general, when you have a table view with multiple sections, the following are some of the methods you need to implement in your table view data source and delegate.
Most of your view controllers which contain your table view, most likely will only have the table view. So a better option in that case is to use a UITableViewController
which will have a default implementation for most of the methods and override only the functions you want to change.
But lets take a step further and provide structure to our dataSource
such that we need to write these functions only once and it will work for most kinds of table views.
class SectionItem {
let id: String
var header: String?
var items: [RowItem]
init(id: String, header: String?, items: [RowItem]) {
self.id = id
self.header = header
self.items = items
}
}
protocol RowItem {
var height: CGFloat { get }
func getCell(at indexPath: IndexPath,
of tableView: UITableView) -> UITableViewCell
func didSelect(at indexPath: IndexPath)
}
Here, the RowItem
protocol is a simple abstraction of the most common functions in the delegate
and dataSource
. To write even less code in creating subsequent table views, you can provide some default implementation for the protocol like below.
extension RowItem {
var height: CGFloat { return UITableView.automaticDimension }
func didSelect(at indexPath: IndexPath) { }
}
You can also use a struct
instead of a class
for SectionItem
. I prefer classes so that I can just pass them around without worrying about memory. This comes with a drawback of requiring to be extra careful about making unintended changes to the object. But I’m cool with it.
The id
property in the SectionItem
will be helpful in cases where we need to make changes to the rows in a section or the header.
Now, let us subclass UITableViewController
and override some dataSource
and delegate
functions using an array of our type SectionItem
as a stored property which acts as the dataSource
.
That’s all you need. Now you can create multiple table views by just subclassing our MITableViewController
and setting value for the sections
array. You can either set the array while the TableViewController
is initialized or update the property at a later time, but do remember to call tableView.reloadData()
in that case.
Let me illustrate to you the usage of both cases with examples, continuing from where we left off in the previous post.
First let us initialize the Posts view controller, with an array of posts fetched from the sample API.
Yup! That’s it. That’s all the code you need for a table view. We’ve confirmed Post
to RowItem
and added a section with all the posts. Just wire the presentation of this PostsViewController
with a button click and you will see the magic.
Now, let us move to presenting albums by fetching it from the server after the view controller is loaded.
Notice the tableView.reloadData()
after appending the section. That will call all the required delegate
and dataSource
methods and the tableView
will be updated properly with album titles. You can just present with AlbumsViewController
and the data will be updated from the API.
Apart from making your code concise, which is only what I have demonstrated, providing a proper structure to your dataSource
using the SectionItem
and representing each row using the RowItem
protocol gives you unparalleled flexibility with your table views.
Do you want multiple sections each with a different type of cell? Check. Do you want different types of cells within a single section? Check.
All these can be achieved by just appending the SectionItem
array in your MITableViewController
subclass. And there’s no need to cast types or unwrap optionals. That’s the type safety which the protocols offer.
To work with these swifty table views, you can just copy this file to your project. Here’s the demo if you want to have a look around.
Happy Coding!