iOS UITableViewCell / UICollectionViewCell registering and resuing — Using Swift protocol &…
Old way of doing register cell
If you are an iOS developer, you would come across UITableView and UICollectionView every day in your life. You would need to register your Cell or UITableViewHeaderFooterView or UICollectionReusableView reuse the cell using an ugly identifier string declared somewhere using static properties. So your code would be something like this
///UITableView
myTableView.register(MyCustomTableViewCell.self, forCellReuseIdentifier: "MyCustomTableViewCell")
//HeaderFooterView
myTableView.register(UINib(nibName: String(describing: MyCustomTableViewHeaderFooterView.self), bundle: nil), forHeaderFooterViewReuseIdentifier: String(describing: MyCustomTableViewHeaderFooterView.self))
//And you have used it like :
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header :MyCustomTableViewHeaderFooterView = tableView.dequeueReusableHeaderFooterView(withIdentifier: String(describing: MyCustomTableViewHeaderFooterView.self)) as! MyCustomTableViewHeaderFooterView
return header
}
The viewcontroller will become messier when there are more cells need to be registered and reuses. So a better way to manage it would be making use of Swift’s generics and protocols which will help us to reuse and register the cells, header footer view and supplementary view very easily.
Using the power of Generics and protocols
First we create a Protocol Named ReusableView
protocol ReusableView: class {
static var defaultReuseIdentifier: String { get }
}
protocol NibLoadableView: class {
static var nibName: String { get }
}
The Protocol has a get only property named defaultReuseIdentifier
which returns the description of the Cell/HeaderFooterView/ReusableView Class which would be unique for each class.
extension ReusableView where Self: UIView {
static var defaultReuseIdentifier: String {
return String(describing: self)
}
}
extension NibLoadableView where Self: UIView {
static var nibName: String {
return String(describing: self)
}
}
Extend UITableView
and UICollectionView
and add the following functions which accept a generic parameter T
for registering both Class and Nib Cells. This function will pass the class type as a generic parameter and registers to the table view or the collection view by using the defaultReuseIdentifier
.
//CollectionView
extension UICollectionView {
func register<T: UICollectionViewCell>(_: T.Type) where T: ReusableView {
register(T.self, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
}
func register<T: UICollectionViewCell>(_: T.Type) where T: ReusableView, T: NibLoadableView {
let bundle = Bundle(for: T.self)
let nib = UINib(nibName: T.nibName, bundle: bundle)
register(nib, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
}
}
//TableView
extension UITableView {
//Registering Cell
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)
}
}
Similarly, we can also register UITableViewHeaderFooterView
and UICollectionReusableView
for the TableView and CollectionView. Add the below function in the UITableView
and UICollectionView
extensions.
// Registering Supplementary View
func register<T: UICollectionReusableView>(_: T.Type, supplementaryViewOfKind: String) where T: ReusableView {
register(T.self, forSupplementaryViewOfKind: supplementaryViewOfKind, withReuseIdentifier: T.defaultReuseIdentifier)
}
func register<T: UICollectionReusableView>(_: T.Type, supplementaryViewOfKind: String) where T: ReusableView, T: NibLoadableView {
let bundle = Bundle(for: T.self)
let nib = UINib(nibName: T.nibName, bundle: bundle)
register(nib, forSupplementaryViewOfKind: supplementaryViewOfKind, withReuseIdentifier: T.defaultReuseIdentifier)
}
//Registering HeaderFooterView
func register<T: UITableViewHeaderFooterView>(_: T.Type) where T: ReusableView {
register(T.self, forHeaderFooterViewReuseIdentifier: T.defaultReuseIdentifier)
}
func register<T: UITableViewHeaderFooterView>(_: T.Type) where T: ReusableView, T: NibLoadableView {
let bundle = Bundle(for: T.self)
let nib = UINib(nibName: T.nibName, bundle: bundle)
register(nib, forHeaderFooterViewReuseIdentifier: T.defaultReuseIdentifier)
}
Now we add the methods for dequeuing the cells / HeaderFooterView / Supplementary Reusable View in the same UITableView
and UICollectionView
extensions which accept a Generic Class T, which should confirm to either ReusableView or NibLodableView (Check Usage Section).
//Dequeue methods for UICollectionView
func dequeueReusableCell<T: UICollectionViewCell>(for indexPath: IndexPath) -> T where T: ReusableView {
guard let cell = dequeueReusableCell(withReuseIdentifier: T.defaultReuseIdentifier, for: indexPath) as? T else {
fatalError("Could not dequeue cell with identifier: \(T.defaultReuseIdentifier)")
}
return cell
}
func dequeueReusableSupplementaryView<T: UICollectionReusableView>(ofKind: String, indexPath: IndexPath) -> T where T: ReusableView {
guard let supplementaryView = dequeueReusableSupplementaryView(ofKind: ofKind, withReuseIdentifier: T.defaultReuseIdentifier, for: indexPath) as? T else {
fatalError("Could not dequeue supplementary view with identifier: \(T.defaultReuseIdentifier)")
}
return supplementaryView
}
//Dequeue methods for UITableView
func dequeueReusableCell<T: UITableViewCell>(for indexPath: IndexPath) -> T where T: ReusableView {
guard let cell = dequeueReusableCell(withIdentifier: T.defaultReuseIdentifier, for: indexPath) as? T else {
fatalError("Could not dequeue cell with identifier: \(T.defaultReuseIdentifier)")
}
return cell
}
func dequeueReusableHeaderFooterView<T: UITableViewHeaderFooterView>(_ : T.Type) -> T where T: ReusableView {
guard let headerFooter = dequeueReusableHeaderFooterView(withIdentifier: T.defaultReuseIdentifier) as? T else {
fatalError("Could not dequeue Header/Footer with identifier: \(T.defaultReuseIdentifier)")
}
return headerFooter
}
Usage
Confirm the Custom cells to ReusableView
and NibLodableView
and the Cells/Supplementary Views/HeaderFooterView are ready to use.
/// UITableViewCell
class MyCustomTableViewCell: UITableViewCell, ReusableView, NibLoadableView {
}
// Or More suggested way
extension MyCustomTableViewCell: ReusableView, NibLoadableView { }
/// UITableViewHeaderFooterView
class MyCustomHeaderFooterView: UITableViewHeaderFooterView, ReusableView, NibLoadableView {
}
//Or
extension MyCustomHeaderFooterView: ReusableView, NibLoadableView { }
Once the extensions and protocol confirmations are in place, you can start using them in your Viewcontroller like below.
Autosuggestion for register methods
/// Registering for UITableView
myTableView.register(MyCustomTableViewCell.self)
myTableView.register(MyCustomHeaderFooterView.self)
/// Registering for UICollectionView
myCollectionView.register(MyCustomCollectionViewCell.self)
myCollectionView.register(MyCollectionViewReusableView.self)
/// Dequeueing in the cellForRowAt / viewForFooter etc.
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: MyCustomTableViewCell = tableView.dequeueReusableCell(for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerView = tableView.dequeueReusableHeaderFooterView(MyCustomHeaderFooterView.self)
return footerView
}
}
Complete ReusableView.swift Class to include in the project :
//
// ReusableView.swift
// Ranjithkumar Matheswaran
//
// Created by Ranjithkumar Matheswaran on 31/10/18.
// Copyright © 2018 AgentDesks LLC. All rights reserved.
//
import Foundation
import UIKit
protocol ReusableView: class {
static var defaultReuseIdentifier: String { get }
}
protocol NibLoadableView: class {
static var nibName: String { get }
}
extension ReusableView where Self: UIView {
static var defaultReuseIdentifier: String {
return String(describing: self)
}
}
extension NibLoadableView where Self: UIView {
static var nibName: String {
return String(describing: self)
}
}
//Confirming Collection View and TableView for Registering and Dequeing
extension UICollectionView {
func register<T: UICollectionViewCell>(_: T.Type) where T: ReusableView {
register(T.self, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
}
func register<T: UICollectionViewCell>(_: T.Type) where T: ReusableView, T: NibLoadableView {
let bundle = Bundle(for: T.self)
let nib = UINib(nibName: T.nibName, bundle: bundle)
register(nib, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
}
//Registering Supplementary View
func register<T: UICollectionReusableView>(_: T.Type, supplementaryViewOfKind: String) where T: ReusableView {
register(T.self, forSupplementaryViewOfKind: supplementaryViewOfKind, withReuseIdentifier: T.defaultReuseIdentifier)
}
func register<T: UICollectionReusableView>(_: T.Type, supplementaryViewOfKind: String) where T: ReusableView, T: NibLoadableView {
let bundle = Bundle(for: T.self)
let nib = UINib(nibName: T.nibName, bundle: bundle)
register(nib, forSupplementaryViewOfKind: supplementaryViewOfKind, withReuseIdentifier: T.defaultReuseIdentifier)
}
//Dequeing
func dequeueReusableCell<T: UICollectionViewCell>(for indexPath: IndexPath) -> T where T: ReusableView {
guard let cell = dequeueReusableCell(withReuseIdentifier: T.defaultReuseIdentifier, for: indexPath) as? T else {
fatalError("Could not dequeue cell with identifier: \(T.defaultReuseIdentifier)")
}
return cell
}
func dequeueReusableSupplementaryView<T: UICollectionReusableView>(ofKind: String, indexPath: IndexPath) -> T where T: ReusableView {
guard let supplementaryView = dequeueReusableSupplementaryView(ofKind: ofKind, withReuseIdentifier: T.defaultReuseIdentifier, for: indexPath) as? T else {
fatalError("Could not dequeue supplementary view with identifier: \(T.defaultReuseIdentifier)")
}
return supplementaryView
}
}
extension UITableView {
//Registering Cell
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)
}
//Registering HeaderFooterView
func register<T: UITableViewHeaderFooterView>(_: T.Type) where T: ReusableView {
register(T.self, forHeaderFooterViewReuseIdentifier: T.defaultReuseIdentifier)
}
func register<T: UITableViewHeaderFooterView>(_: T.Type) where T: ReusableView, T: NibLoadableView {
let bundle = Bundle(for: T.self)
let nib = UINib(nibName: T.nibName, bundle: bundle)
register(nib, forHeaderFooterViewReuseIdentifier: T.defaultReuseIdentifier)
}
//Dequeing
func dequeueReusableCell<T: UITableViewCell>(for indexPath: IndexPath) -> T where T: ReusableView {
guard let cell = dequeueReusableCell(withIdentifier: T.defaultReuseIdentifier, for: indexPath) as? T else {
fatalError("Could not dequeue cell with identifier: \(T.defaultReuseIdentifier)")
}
return cell
}
func dequeueReusableHeaderFooterView<T: UITableViewHeaderFooterView>(_ : T.Type) -> T where T: ReusableView {
guard let headerFooter = dequeueReusableHeaderFooterView(withIdentifier: T.defaultReuseIdentifier) as? T else {
fatalError("Could not dequeue Header/Footer with identifier: \(T.defaultReuseIdentifier)")
}
return headerFooter
}
}
Hope this would help you to reduce the code cluttering and easy usage of registering and reusing TableView and CollectionView Cells, HeaderFooterViews and ReusableSupplementary Views without need to maintain ugly static string constant in every cell class.
We are sending out a weekly newsletter with great curated Mobile developer articles, blogs, events, tutorials, news, and job posts. Subscribe now to get in your inbox.
☝️ Subscribe now and get it delivered to you every weekend.