An easy way to solve this is by writing a small extension.
This solution will result in a fatalError if a cell is dequeued that has not been registered. This is already the default behaviour for iOS if we call dequeueReusableCell(withIdentifier:for:)
if we have not registered the cell.
To let this work we need a way to create a unique identifier for any type of cell that we will register and dequeue using generics. If you would want to have a way to dequeue the same cell for different identifiers then you will have to fallback to the default system (never had any need for that).
So lets make a class named UITableView+Tools.swift
(or what ever you like to name it).
extension UITableView {
private func reuseIndentifier<T>(for type: T.Type) -> String {
return String(describing: type)
}
public func register<T: UITableViewCell>(cell: T.Type) {
register(T.self, forCellReuseIdentifier: reuseIndentifier(for: cell))
}
public func register<T: UITableViewHeaderFooterView>(headerFooterView: T.Type) {
register(T.self, forHeaderFooterViewReuseIdentifier: reuseIndentifier(for: headerFooterView))
}
public func dequeueReusableCell<T: UITableViewCell>(for type: T.Type, for indexPath: IndexPath) -> T {
guard let cell = dequeueReusableCell(withIdentifier: reuseIndentifier(for: type), for: indexPath) as? T else {
fatalError("Failed to dequeue cell.")
}
return cell
}
public func dequeueReusableHeaderFooterView<T: UITableViewHeaderFooterView>(for type: T.Type) -> T {
guard let view = dequeueReusableHeaderFooterView(withIdentifier: reuseIndentifier(for: type)) as? T else {
fatalError("Failed to dequeue footer view.")
}
return view
}
}
So now all we have to do in our class (i.e. view controller) is register the cell (no identifier needed) and dequeue it(no identifier, no casting, force unwrapping or manual unwrap with a guard)
func viewDidLoad {
...
tableView.register(MyCustomCell.self)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = dequeueReusableCell(forType: MyCustomCell.self, for: indexPath)
cell.viewModel = cellModel(for: indexPath)
return cell
}
And that's it. Hope you like the idea. Any other (better or worse ) ideas are welcome.