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 methodsAutosuggestion 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.

No Comments Yet