Swift Reusable Cells

Reusable cells have always been a pain in Swift. String identifiers and poor type information raise the possibility of runtime exceptions with no compiler safety.

Many solutions to this problem involve tying reusability directly to each individual cell class. This breaks if you have more than one nib for a given cell class or use the same cell class with multiple reuse identifiers in the same table or collection view.

Instead I would like to define a simple model object that describes a reusable cell. It should make no assumptions about how it is stored and should be easy to consume.

ReusableCell

Start by taking a modeled approach to the problem. All cells in a table or collection view have a reuse identifier. Some cells have an associated nib and others do not. This can be expressed with a Swift enum:

public enum ReusableCell<Cell> {
case Class(identifier: String)
case Nib(identifier: String, nibName: String, bundle: NSBundle?)
    public var identifier: String {
switch self {
case .Class(let identifier): return identifier
case .Nib(let identifier, _, _): return identifier
}
}
    public init(identifier: String) {
self = .Class(identifier: identifier)
}
    public init(identifier: String, nibName: String, bundle: NSBundle? = nil) {
self = .Nib(identifier: identifier, nibName: nibName, bundle: bundle)
}
}

Now we have the ability to specify the definition of a reusable cell without involving the cell class itself.

private enum Cell {
// This one uses a nib.
var User: ReusableCell<UserCollectionViewCell> {
return ReusableCell(
identifier: "MainViewController.Cell.User",
nibName: "UserCollectionViewCell")
}
    // This one does not.
var Photo: ReusableCell<PhotoCollectionViewCell> {
return ReusableCell(
identifier: "MainViewController.Cell.Photo")
}
}

A couple of extensions on UITableView and UICollectionView let us work with ReusableCells easily.

extension UITableView {
public final func registerReusableCell<T where T: UITableViewCell>(cell: ReusableCell<T>) {
switch cell {
case .Class(let identifier):
registerClass(T.self, forCellReuseIdentifier: identifier)
case .Nib(let identifier, let nibName, let bundle):
let nib = UINib(nibName: nibName, bundle: bundle)
registerNib(nib, forCellReuseIdentifier: identifier)
}
}
    public final func dequeueReusableCell<T where T: UITableViewCell>(cell: ReusableCell<T>, indexPath: NSIndexPath) -> T {
return dequeueReusableCellWithIdentifier(cell.identifier, forIndexPath: indexPath) as! T
}
}

And now we have type-safe cell dequeueing!

// Register a cell definition.
tableView.registerReusableCell(Cell.User)
// This is implicitly a `UserCollectionViewCell`.
let cell = tableView.dequeueReusableCell(Cell.User)

This makes life so much easier. 😊