Sanding Down Rough Edges
An Interesting Start for Hopefully Very Useful Code
Like most Swift developers working with UIKit I got annoyed with the fact that every single time I dequeued a cell from a UITableView
working with anything that's no just a UITableViewCell
has to be cast using as?
, and that registering classes for tables isn't particularly smooth either. As a rough idea of how to get some type safety in the absolute lightest way possible, I wrote this:
protocol AutomaticDequeue {
}
extension AutomaticDequeue where Self: AnyObject {
static var reuseIdentifer: String {
return NSStringFromClass(self) as String
}
}
extension UITableView {
func registerCellClass<CellClass: UITableViewCell where CellClass: AutomaticDequeue>(cellClass: CellClass.Type) {
registerClass(cellClass, forCellReuseIdentifier: cellClass.reuseIdentifer)
}
func registerHeaderFooterClass<HeaderFooterClass: UITableViewHeaderFooterView where HeaderFooterClass: AutomaticDequeue>(headerFooterClass: HeaderFooterClass.Type) {
registerClass(headerFooterClass, forHeaderFooterViewReuseIdentifier: headerFooterClass.reuseIdentifer)
}
func cellForIndexPath<CellClass: UITableViewCell where CellClass: AutomaticDequeue>(indexPath: NSIndexPath) -> CellClass {
guard let cell = dequeueReusableCellWithIdentifier(CellClass.reuseIdentifer, forIndexPath: indexPath) as? CellClass else {
fatalError("Could not dequeue cell with identifier \(CellClass.reuseIdentifer)")
}
return cell
}
func headerForIndexPath<HeaderFooterClass: UITableViewHeaderFooterView where HeaderFooterClass: AutomaticDequeue>(indexPath: NSIndexPath) -> HeaderFooterClass {
guard let header = dequeueReusableHeaderFooterViewWithIdentifier(HeaderFooterClass.reuseIdentifer) as? HeaderFooterClass else {
fatalError("Could not dequeue header with identifier \(HeaderFooterClass.reuseIdentifer)")
}
return header
}
func footerForIndexPath<HeaderFooterClass: UITableViewHeaderFooterView where HeaderFooterClass: AutomaticDequeue>(indexPath: NSIndexPath) -> HeaderFooterClass {
guard let footer = dequeueReusableHeaderFooterViewWithIdentifier(HeaderFooterClass.reuseIdentifer) as? HeaderFooterClass else {
fatalError("Could not dequeue footer with identifier \(HeaderFooterClass.reuseIdentifer)")
}
return footer
}
}
this adds a handful of really neat things, first off by adding conformance to the AutomaticDequeue
protocol the class is then used to generate the re-use identifier automatically based on the class name. It also adds simplified class registration, which ends up looking like table.registerCellClass(MainMenuCell)
. After cells are registered, the can be dequeued with calls like let cell: MainMenuCell = tableView.cellForIndexPath(indexPath)
. The compiler can infer the correct class, and therefore the correct re-use identifier from the return type.
This is the kind of solution that makes me think I'm starting to scratch the surface of generating useful solutions with generics, particularly figuring out ways to add useful functionality if all of the correct conditions are in place, and to not get in the way otherwise.