Use Swift to make easy and safe dequeue operation in UITableViewCells
It would be nice to get away with declaring a constant for every reuse identifier of cells in our app: with this common approach is very easy to make mistakes (and clearly it does not fits in a Swift world).
NOTE
I’ve used this approach to simplify my everyday work both with
UITableView and
UICollectionView ; from this work I’ve created Flow, a new approach to manage UITableView.
▶︎ Check it now! it will save you tons of code!
We can just use the name of the custom cell class as a default reuse identifier.
First of all we can create a
ReusableViewProtocol which is responsible to provide the identifier of cell.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public protocol DequeuableProtocol: class { /// Return the nib name in which the dequeuable resource is located /// You must implement it if your cell is located in a separate xib file /// (not for storyboard). /// In this case you should call `table.register(CellClass.self)` before /// using it in your code. /// Default implementation returns the name of the class itself. static var dequeueNibName: String { get } /// This is the identifier used to queue/dequeue the cell. /// You don't need to override it; default implementation return the name /// of the cell class itself as identifier. static var dequeueIdentifier: String { get } } |
By making
UITableViewCell conform to the
DequeuableProtocol protocol, we get a unique reuse identifier per cell subclass.
Generally you don’t need to provide implementation of
dequeueIdentifier ; default implementation return the same name of the class (also with module name prefix).
Another property is
dequeueNibName : if your cell is defined inside a bundle (and not as prototype cell inside storyboard) you can register it to the table and use it (even this method has its own default implementation).
1 2 3 4 5 6 7 8 |
extension DequeuableProtocol where Self: UIView { public static var dequeueIdentifier: String { return NSStringFromClass(self) } public static var dequeueNibName: String { return NSStringFromClass(self).components(separatedBy: ".").last! } } |
The final part of this trick is to provide registration and dequeue function inside the table (keep in mind: you can apply the same technique even with UICollectionView!)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public extension UITableView { /// Register a cell from external xib into a table instance. /// /// - Parameter _: cell class public func register<T: UITableViewCell>(_: T.Type) where T: DequeuableProtocol { let bundle = Bundle(for: T.self) let nib = UINib(nibName: T.dequeueNibName, bundle: bundle) self.register(nib, forCellReuseIdentifier: T.dequeueIdentifier) } /// Dequeue a cell instance strongly typed. /// /// - Parameter indexPath: index path /// - Returns: instance public func dequeueReusableCell<T: UITableViewCell>(forIndexPath indexPath: NSIndexPath) -> T where T: DequeuableProtocol { guard let cell = dequeueReusableCell(withIdentifier: T.dequeueIdentifier, for: indexPath as IndexPath) as? T else { fatalError("Cannot dequeue: \(T.self) with identifier: \(T.dequeueIdentifier)") } return cell } } |
Now if your cell is defined inside a storyboard you can simply call:
1 |
let instance: MyCell = table.dequeueReusableCell(for: indexPath) |
While if your cell is inside an external xib:
1 2 3 4 5 |
// in your viewDidLoad or before using the table itself self.table.register(MyCell.self) // then, when you need the instance call let instance: MyCell = table.dequeueReusableCell(for: indexPath) |
With this approach you can get your strongly typed instance of the cell easily.
The whole code is available here.
But it’s not enough… the best part is yet to come!