Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
iOS Cell Registration & Reusing with Swift Protocol Extensions and Generics

iOS Cell Registration & Reusing with Swift Protocol Extensions and Generics

A common task when developing iOS apps is to register custom cell subclasses for both UITableView and UICollectionView. Well, that is if you don’t use Storyboards, of course.

Both UITableView and UICollectionView offer a similar API to register custom cell classes:

public func registerClass(cellClass: AnyClass?, forCellWithReuseIdentifier identifier: String)
public func registerNib(nib: UINib?, forCellWithReuseIdentifier identifier: String)

A widely accepted solution to handle cell registration and dequeuing is to declare a constant for the reuse identifier:

private let reuseIdentifier = "BookCell"

class BookListViewController: UIViewController, UICollectionViewDataSource {

    @IBOutlet private weak var collectionView: UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let nib = UINib(nibName: "BookCell", bundle: nil)
        self.collectionView.registerNib(nib, forCellWithReuseIdentifier: reuseIdentifier)
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
    
        if let bookCell = cell as? BookCell {
            // TODO: configure cell
        }
    
        return cell
    }
}

Let’s try to generalize this code and make it simpler and safe.

First of all, it would be nice to get away with declaring a constant for every reuse identifier in our app. We can just use the name of the custom cell class as a default reuse identifier. We can create a protocol for Reusable Views and provide a default implementation constrained to UIView subclasses.

protocol ReusableView: class {
    static var defaultReuseIdentifier: String { get }
}

extension ReusableView where Self: UIView {
    static var defaultReuseIdentifier: String {
        return NSStringFromClass(self)
    }
}

extension UICollectionViewCell: ReusableView {
}

By making UICollectionViewCell conform to the ReusableView protocol, we get a unique reuse identifier per cell subclass.

let identifier = BookCell.defaultReuseIdentifier
// identifier = "MyModule.BookCell"

Next, we can get rid of the hard-coded string we are using to load the Nib.

Let’s create a protocol for Nib Loadable Views and provide a default implementation using protocol extensions.

protocol NibLoadableView: class {
    static var nibName: String { get }
}

extension NibLoadableView where Self: UIView {
    static var nibName: String {
        return NSStringFromClass(self).componentsSeparatedByString(".").last!
    }
}

extension BookCell: NibLoadableView {
}

By making our BookCell class conform to the NibLoadableView protocol we now have a safer way to get the Nib name.

let nibName = BookCell.nibName
// nibName = "BookCell"

If you use a different name for the XIB file than the one provided by Xcode, you can always override the default implementation of the nibName property.

With these two protocols in place, we can use Swift Generics and extend UICollectionView to simplify cell registration and dequeuing.

extension UICollectionView {
    
    func register<T: UICollectionViewCell where T: ReusableView>(_: T.Type) {
        registerClass(T.self, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
    }
    
    func register<T: UICollectionViewCell where T: ReusableView, T: NibLoadableView>(_: T.Type) {
        let bundle = NSBundle(forClass: T.self)
        let nib = UINib(nibName: T.nibName, bundle: bundle)
        
        registerNib(nib, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
    }
    
    func dequeueReusableCell<T: UICollectionViewCell where T: ReusableView>(forIndexPath indexPath: NSIndexPath) -> T {
        guard let cell = dequeueReusableCellWithReuseIdentifier(T.defaultReuseIdentifier, forIndexPath: indexPath) as? T else {
            fatalError("Could not dequeue cell with identifier: \(T.defaultReuseIdentifier)")
        }
        
        return cell
    }    
}

Notice that we created two versions of the register method, one for cell subclasses implementing just ReusableView and another one for cell subclasses implementing both ReusableView and NibLoadableView. This nicely decouples the view controller from the specific cell registration method.

Another nice detail is that the dequeueReusableCell method doesn’t need to take any reuse identifier and uses the cell subclass type for the return value.

Now the cell registration and dequeuing code looks much better :).

class BookListViewController: UIViewController, UICollectionViewDataSource {

    @IBOutlet private weak var collectionView: UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.collectionView.register(BookCell.self)
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        
        let cell: BookCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
        
        // TODO: configure cell
    
        return cell
    }
    ...
}

Conclusion

If you are coming from Objective-C it is worth to investigate powerful Swift features like Protocol Extensions and Generics to find alternate and more elegant ways to deal with Cocoa classes.

@shams-ahmed

This comment has been minimized.

Copy link

commented Feb 6, 2016

I tried to adapt this for UITableViewCell but i always got a fatal error in dequeueReusableCell, some reason i get back a UITableViewCell in the tableview even after i register a custom cell

import Foundation
import UIKit

// See: https://medium.com/@gonzalezreal/ios-cell-registration-reusing-with-swift-protocol-extensions-and-generics-c5ac4fb5b75e#.iflfe9b1e

/// Easier way to create cell identifier
public protocol ReusableView: class {
    /// Get identifier from class
    static var defaultReuseIdentifier: String { get }
}

public extension ReusableView where Self: UIView {
    static var defaultReuseIdentifier: String {
        // Set the Identifier from class name
        return NSStringFromClass(self)
    }
}

extension UITableViewCell: ReusableView {

}

/// Extend to easier allow for identifier to be set without much work
public extension UITableView {

    /// Register cell with automatically setting Identifier
    public func register<T: UITableViewCell where T: ReusableView>(_: T.Type) {
        registerClass(T.self, forCellReuseIdentifier: T.defaultReuseIdentifier)
    }

    /// Get cell with the default reuse cell identifier
    public func dequeueReusableCell<T: UITableViewCell where T: ReusableView>(forIndexPath indexPath: NSIndexPath) -> T {
        guard let cell = dequeueReusableCellWithIdentifier(T.defaultReuseIdentifier, forIndexPath: indexPath) as? T else {
            fatalError("Could not dequeue cell: \(T.self) with identifier: \(T.defaultReuseIdentifier)")
        }

        return cell
    }
}

is there anything that may be wrong with code?

@hhanesand

This comment has been minimized.

Copy link

commented Mar 17, 2016

Great ideas!

However, if you declare the variables as static, you can not override them in subclasses.
I managed to declare my protocol extensions on the type and not on the protocol itself.

@gmarm

This comment has been minimized.

Copy link

commented Apr 17, 2016

Wow this is brilliant!

@windylee

This comment has been minimized.

Copy link

commented May 13, 2016

@igabrielsabiescu

This comment has been minimized.

Copy link

commented Jun 3, 2016

Newbie question here: Any chance this can increase compile time (even marginally)?

@Ashu

This comment has been minimized.

Copy link

commented Oct 5, 2016

This markdown version is more reader friendly than Medium. Just saying :)

@andreaantonioni

This comment has been minimized.

@User2004

This comment has been minimized.

Copy link

commented Jan 8, 2019

Click here for how to register custom collection cell and display collection cell data in swift 3 or later.

@mohsinbmwm3

This comment has been minimized.

Copy link

commented May 14, 2019

How to register multiple(different) cells and then dequeue them?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.