Registering and dequeuing cells in a collection view

When using custom cells in UICollectionViews, you usually have to go through two things:

  • registering the cell: usually in the viewDidLoad method, you have to let the collection view know how to create a cell of a given type
  • dequeue the cell: usually in the collectionView(_:, cellForItemAt:) method, you ask the collection view to dequeue a cell of a given type so that you can configure it and return it

What’s wrong with that?

  • there is a lot of boilerplate code here
  • uncertainty around cell identifiers
  • when dequeuing, you always have to deal with this optional even though there isn’t much you can do if the cell isn’t there

Making registration easier

To make cell registration easier, each cell has to provide two things:

  • a cell identifier is a string identifying the cell
  • a cell nib is an object of type UINib used by the collection view to instantiate the cell

I created a protocol that will make more sense later on:

protocol UICollectionViewRegisterable {
    static var cellIdentifier: String { get }
    static var cellNib: UINib { get }
}

So the cell just needs to conform to this protocol:

extension HomeCell: UICollectionViewRegisterable {
    static var cellIdentifier: String {
        return "HomeCellID"
    }

    static var cellNib: UINib {
        return UINib(nibName: "HomeCell", bundle: nil)
    }
}

Then we have to register the cell in the collection view, here an extension on UICollectionView will help us get rid of the optional and enforce type:

extension UICollectionView {
    func register(type: UICollectionViewRegisterable.Type) {
        register(type.cellNib, forCellWithReuseIdentifier: type.cellIdentifier)
    }

    func dequeueCell<CellType: UICollectionViewRegisterable>(at indexPath: IndexPath) -> CellType {
        guard let cell = dequeueReusableCell(withReuseIdentifier: CellType.cellIdentifier, for: indexPath) as? CellType else {
            fatalError("UICollectionView should dequeue a cell of type \(CellType.self)")
        }
        return cell
    }
}

Registering the cell becomes really easy:

override func viewDidLoad() {
    super.viewDidLoad()

    // Configure collection view
    collectionView.register(type: HomeCell.self)
}

To dequeue the cell, the extension is using generics, so we can’t let type inference do the work here, and have to specify the type of the cell for the code to compile:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell: HomeCell = collectionView.dequeueCell(at: indexPath)
    // TODO: configure cell
    return cell
}

That’s it! Here is the final code:

import UIKit
protocol UICollectionViewRegisterable {
static var cellIdentifier: String { get }
static var cellNib: UINib { get }
}
extension UICollectionView {
func register(type: UICollectionViewRegisterable.Type) {
register(type.cellNib, forCellWithReuseIdentifier: type.cellIdentifier)
}
func dequeueCell<CellType: UICollectionViewRegisterable>(at indexPath: IndexPath) -> CellType {
guard let cell = dequeueReusableCell(withReuseIdentifier: CellType.cellIdentifier, for: indexPath) as? CellType else {
fatalError("UICollectionView should dequeue a cell of type \(CellType.self)")
}
return cell
}
}

Leave a Reply

Your email address will not be published. Required fields are marked *