Write Clean Reusable Code To Dequeue Table View Cells With Swift Generics

I’ve always been a fan of reducing code size and making it the most concise possible. The swift standard library amazes me with the collection of API and tools it provides to help us – developers – write better code.

Generics is one of those beautiful concepts that exist in many languages, and swift is no exception. Generic code enables you to write code that do the same task that nongeneric code does. The main pros of generic code reside in its shortness and reusability.

In practical, generics have many use cases. In this post I will focus on one of them – How to leverage generics to dequeue UITableView cells.

Let’s start from a simple example – You have one screen displaying some cells in a table view. The table view has 30 cells to display, the first ten cells are red, the second ten cells are green and the last ones are blue.

The tableView(_:cellForRowAt:) data source method implementation will look something like this:

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        switch indexPath.row {
        case 0...9:
            guard let cell = tableView.dequeueReusableCell(withIdentifier: ViewController.redTableViewCellIdentifier, for: indexPath) as? RedTableViewCell else {
                fatalError("Could not dequeue cell with identifie: \(ViewController.redTableViewCellIdentifier)")
            }
            return cell
        case 10...19:
            guard let cell = tableView.dequeueReusableCell(withIdentifier: ViewController.greenTableViewCellIdentifier, for: indexPath) as? GreenTableViewCell else {
                fatalError("Could not dequeue cell with identifie: \(ViewController.greenTableViewCellIdentifier)")
            }
            return cell
        case 20...29:
            guard let cell = tableView.dequeueReusableCell(withIdentifier: ViewController.blueTableViewCellIdentifier, for: indexPath) as? BlueTableViewCell else {
                fatalError("Could not dequeue cell with identifie: \(ViewController.blueTableViewCellIdentifier)")
            }
            return cell
        default:
            return UITableViewCell()
        }
    }

Nothing is technically wrong with this code, except that it makes me read the exact same code three times.

Well, it is not really the exact same code (you may wonder) as the cells are backed by different classes. That’s correct, but still it is the same pattern that keeps repeating and the fact the cells are instances of different classes doesn’t make the code above unshortenable.

Actually, we can extract the dequeue of the cells using the guard statement to another method. As all classes (RedTableViewCell, GreenTableViewCell, BlueTableViewCell) are of type UITableViewCell, we can make this method generic with a placeholder of type UITableViewCell which will make it reusable whenever we use a table view.

Consider the following code:

extension UITableView {
    func dequeueReusableCell<T: UITableViewCell>(indexPath: IndexPath) -> T {
        guard let cell = dequeueReusableCell(withIdentifier: T.identifier, for: indexPath) as? T else {
            fatalError("Could not dequeue cell with identifie: \(T.identifier)")
        }
        return cell
    }
}

The code above is very similar to the duplicated code we used before inside the tableView(_:cellForRowAt:) method. The benefit of this code is that it is written once and can be reused for all UITableView type and all UITableViewCell type.

As you can see, the placeholder type inside the angle brackets is UITableViewCell. That what tells the method it should expect to work with any type as long as it is of type UITableViewCell – Generics power 😉

As a result, our tableView(_:cellForRowAt:) method implementation can be shortened to the following:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        switch indexPath.row {
        case 0...9:
            let cell: RedTableViewCell = tableView.dequeueReusableCell(indexPath: indexPath)
            return cell
        case 10...19:
            let cell: GreenTableViewCell = tableView.dequeueReusableCell(indexPath: indexPath)
            return cell
        case 20...29:
            let cell: BlueTableViewCell = tableView.dequeueReusableCell(indexPath: indexPath)
            return cell
        default:
            return UITableViewCell()
        }
    }

The outcome is quite satisfying – less lines of code, and most importantly, no redundant code.

Earlier you saw me calling T.identifier. The identifier property is not available by default on UITableViewCell objects – we should add it to make each cell provides an identifier needed to dequeue the cell.

To do so, an easy way is to extend UITableViewCell and add a static computed property to provide a value as an identifier for the cell – something like the following:

extension UITableViewCell {
    static var identifier: String {
        String(describing: self)
    }
}

That’s all for now – you can clone the code as a running sample project from the repository here.

You can follow me on Twitter. I don’t tweet that often though but it is good to stay in touch 🙂

Thanks!

Malek
iOS developer with over than 11 years of extensive experience working on several projects with different sized startups and corporates.