An easier way to dequeue cells in iOS

Michael Dube
Over Engineering
Published in
4 min readJun 21, 2018

Using the power of generics to improve cell registration and reuse πŸ€“

The problem we are trying to solve

Whenever we have to deal withUICollectionView or UITableView, we need to register a cell before we use it, for example.

collectionView.register(Cell.self, forCellWithReuseIdentifier: "Foo")

Later, when you need to use it …

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Foo", for: indexPath) as! Cell

This is what Apple gives us out of the box but what could be wrong with that? πŸ€·β€β™‚ . Well, for starters, we are dealing with a stringly-typed API. We have to register and dequeue our cells based on a string identifier. These two can easily get out of sync and can become a pain to maintain. Not to mention, we will only know its broken in production!

When we dequeue our cells, we also have to make a force cast to the cell type. It would be nice if we could avoid this entirely. Lastly, we have to remember to register the cell. What if we could have it all and automatically register the cell the moment we tried to dequeue them πŸ€”

The proposed solution

Our aim is to simplify cell reuse and registration to just one line of code

let cell: Cell = collectionView.dequeueReusableCell(for: indexPath)

This seems like a job for generics πŸ˜„

The solution here is inspired by this gist.

While generics are a fairly advanced topic, they are one of the most powerful features of Swift. For those new to Generics.

Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.

A large portion of the Swift standard library is built with generic code.

Since we would like to work with both UICollectionView and UITableView, we can start off by defining a protocol for a reusable view that has a defaultReuseIdentifier.

public protocol ReusableView: class {
static var defaultReuseIdentifier: String { get }
}

Since cell registration always needs a reuse identifier, let’s use the class name as the default identifier. We seldom need different reuse identifiers for the same view. Let’s use String(describing: self).

extension ReusableView where Self: UIView {
public static var defaultReuseIdentifier: String {
return String(describing: self)
}
}

One thing to be mindful of, is that we can also load our cells from a nib file. So lets go ahead and add a protocol for a nib loadable view.

public protocol NibLoadableView: class {
static var nibName: String { get }
}

extension NibLoadableView where Self: UIView {
static var nibName: String {
return String(describing: self)
}
}

Now that we have our ReusableView & NibLoadableView protocol, we can implement a generic way to register and dequeue a cell:

extension UITableView {
func register<T: UITableViewCell>(_: T.Type) where T: ReusableView {
register(T.self, forCellReuseIdentifier: T.defaultReuseIdentifier)
}
func register<T: UITableViewCell>(_: T.Type) where T: ReusableView, T: NibLoadableView {
let bundle = Bundle(for: T.self)
let nib = UINib(nibName: T.nibName, bundle: bundle)
register(nib, forCellReuseIdentifier: T.defaultReuseIdentifier)
}

This can be used on any UITableView instance. Notice how we have two register functions. One is for ReusableViewand the other one is for NibloadableView . When you register it will always look like table.register(CustomCell.self) and depending on the protocol that the cell conforms to, it will use the relevant one. Try it out πŸ˜„

Now that we have a way to register, lets put together a nice way to dequeue our cells

func dequeueReusableCell<T: UITableViewCell>() -> T where T: ReusableView, T: NibLoadableView {
register(T.self)
return dequeueReusableCell(withIdentifier: T.defaultReuseIdentifier) as! T
}
func dequeueReusableCell<T: UITableViewCell>() -> T where T: ReusableView {
register(T.self)
return dequeueReusableCell(withIdentifier: T.defaultReuseIdentifier) as! T
}

Every time we dequeue we call register first and in our experience the performance cost is negligible.

Mission accomplished πŸš€

Now that we have everything put together, this is how you would use it

//make your subclass conform to the protocol
extension CustomCell: ReusableView {}
//then you would dequeue your cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: CustomCell = collectionView.dequeueReusableCell(for: indexPath)
return cell
}

That’s it! We have removed so much boilerplate code now and our lives are so much easier. We no longer have to worry about registering cells and supplementary views. We never have to think about what string identifiers are in use and best of all, its all typesafe. With just two lines of code you are good to go πŸŽ‰

Download the full code here

I hope this helped you out and you learned something new today. If you enjoyed reading this article be sure to throw your hands together for a couple of claps πŸ‘πŸ‘ . Feel free to stalk me on Instagram and Twitter πŸ˜„.

--

--

Michael Dube
Over Engineering

iOS & macOS app developer. Currently building cool stuff for @Over.