Skip to content

Instantly share code, notes, and snippets.

@IanKeen
Last active July 10, 2019 22:02
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save IanKeen/4ecbffea1e9d0c1bf1d6 to your computer and use it in GitHub Desktop.
Save IanKeen/4ecbffea1e9d0c1bf1d6 to your computer and use it in GitHub Desktop.
Small utility methods to simplify dealing with Reusable items i.e. table/collection view cells
//`UITableViewCell` and `UICollectionViewCell` are `Reusable` by defaut
//Use the extension method to dequeue an instance of the appropriate `Reusable`
class MyVC: UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
tableView
.registerReusable(FooCell.self)
.registerReusable(BarCell.self)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: FooCell = tableView.dequeueReusable(indexPath)
return cell
}
}
//Thats it!
protocol Reusable: class {
static var reuseIdentifier: String { get }
}
extension Reusable {
static var reuseIdentifier: String {
return String(self)
}
}
extension UITableViewCell: Reusable { }
extension UITableViewHeaderFooterView: Reusable { }
extension UICollectionViewCell: Reusable { }
extension UITableView {
//MARK: - Cells
func registerReusable(cellClass: Reusable.Type, fromNib: Bool = true) -> UITableView {
if (fromNib) {
let nib = UINib(nibName: cellClass.reuseIdentifier, bundle: nil)
self.registerNib(nib, forCellReuseIdentifier: cellClass.reuseIdentifier)
} else {
self.registerClass(cellClass, forCellReuseIdentifier: cellClass.reuseIdentifier)
}
return self
}
func dequeueReusable<T: UITableViewCell where T: Reusable>(indexPath: NSIndexPath) -> T {
return self.dequeueReusableCellWithIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as! T
}
//MARK: - HeaderFooter
func registerReusableHeaderFooterView(headerFooterViewClass: Reusable.Type, fromNib: Bool = true) -> UITableView {
if (fromNib) {
let nib = UINib(nibName: headerFooterViewClass.reuseIdentifier, bundle: nil)
self.registerNib(nib, forHeaderFooterViewReuseIdentifier: headerFooterViewClass.reuseIdentifier)
} else {
self.registerClass(headerFooterViewClass, forHeaderFooterViewReuseIdentifier: headerFooterViewClass.reuseIdentifier)
}
return self
}
func dequeueReusableHeaderFooterView<T: UITableViewHeaderFooterView where T: Reusable>() -> T? {
return self.dequeueReusableHeaderFooterViewWithIdentifier(T.reuseIdentifier) as? T
}
}
extension UICollectionView {
//MARK: - Cells
func registerReusable(cellClass: Reusable.Type, fromNib: Bool = true) -> UICollectionView {
if (fromNib) {
let nib = UINib(nibName: cellClass.reuseIdentifier, bundle: nil)
self.registerNib(nib, forCellWithReuseIdentifier: cellClass.reuseIdentifier)
} else {
self.registerClass(cellClass, forCellWithReuseIdentifier: cellClass.reuseIdentifier)
}
return self
}
func dequeueReusable<T: UICollectionViewCell where T: Reusable>(indexPath: NSIndexPath) -> T {
return self.dequeueReusableCellWithReuseIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as! T
}
}
@IanKeen
Copy link
Author

IanKeen commented Jan 24, 2016

I did consider explicitly making UITableViewCell and UICollectionViewCell conform to Reusable thus removing the need to do it yourself, but I feel like the user should maybe be explicit about that themselves...

Having said that if people use this then they probably want all their cells to be Reusable anyway?... anyone have any thoughts on this?

@loganwright
Copy link

I think I'm for conforming explicitly since they're already kind of opting into the api by using these methods.

@IanKeen
Copy link
Author

IanKeen commented Jan 25, 2016

Thanks!, Thats where I was leaning... added default conformance.

@aranasaurus
Copy link

I'd say because it's using custom extension function on UICollectionView it'd be fine to make the UI*Cell classes conform to Reusable, since using this is already explicit in that sense.

I would however want there to be a way to not use a nib.

@winkelsdorf
Copy link

Great approach!

But doesn't self.registerReusable(T.self) in deqeueReusable create a bit of overhead of instructions per dequeued cell? Did you inspect this? I am a bit picky about unnecessary instructions with tableCells in regard to their performance..

I agree with what @aranasaurus mentioned. There should be a way to distinguish between code based and nib based Cells. Speaking of registerNib and registerClass in registerReusable.

Here is a slightly enhanced implementation, which

  • implements UITableViewHeaderFooterViews too
  • has an optional parameter to allow both loading with (fromNib: true) and without nib (default)
import UIKit

protocol Reusable: class {
    static var reuseIdentifier: String { get }
}

extension Reusable {
    static var reuseIdentifier: String {
        return String(self)
    }
}

extension UITableViewCell: Reusable { }
extension UITableViewHeaderFooterView: Reusable { }
extension UICollectionViewCell: Reusable { }

extension UITableView {

    // Cells
    func registerReusable(cellClass: Reusable.Type, fromNib: Bool = false) -> UITableView {
        if fromNib {
            let nib = UINib(nibName: cellClass.reuseIdentifier, bundle: nil)
            self.registerNib(nib, forCellReuseIdentifier: cellClass.reuseIdentifier)
        } else {
            self.registerClass(cellClass, forCellReuseIdentifier: cellClass.reuseIdentifier)
        }

        return self
    }

    func dequeueReusable<T: UITableViewCell where T: Reusable>(indexPath: NSIndexPath, fromNib: Bool = false) -> T {
        self.registerReusable(T.self, fromNib: fromNib)
        return self.dequeueReusableCellWithIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as! T
    }

    // HeaderFooterViews
    func registerReusableHeaderFooterClass(headerFooterViewClass: Reusable.Type, fromNib: Bool = false) -> UITableView {
        if fromNib {
            let nib = UINib(nibName: headerFooterViewClass.reuseIdentifier, bundle: nil)
            self.registerNib(nib, forHeaderFooterViewReuseIdentifier: headerFooterViewClass.reuseIdentifier)
        } else {
            self.registerClass(headerFooterViewClass, forHeaderFooterViewReuseIdentifier: headerFooterViewClass.reuseIdentifier)
        }

        return self
    }

    func dequeueReusableHeaderFooterView<T: UITableViewHeaderFooterView where T: Reusable>(fromNib fromNib: Bool = false) -> T? {
        self.registerReusableHeaderFooterClass(T.self, fromNib: fromNib)
        return self.dequeueReusableHeaderFooterViewWithIdentifier(T.reuseIdentifier) as? T
    }
}

extension UICollectionView {
    func registerReusable(cellClass: Reusable.Type, fromNib: Bool = false) -> UICollectionView {
        if fromNib {
            let nib = UINib(nibName: cellClass.reuseIdentifier, bundle: nil)
            self.registerNib(nib, forCellWithReuseIdentifier: cellClass.reuseIdentifier)
        } else {
            self.registerClass(cellClass, forCellWithReuseIdentifier: cellClass.reuseIdentifier)
        }

        return self
    }

    func dequeueReusable<T: UICollectionViewCell where T: Reusable>(indexPath: NSIndexPath, fromNib: Bool = false) -> T {
        self.registerReusable(T.self, fromNib: fromNib)
        return self.dequeueReusableCellWithReuseIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as! T
    }
}

Usage from tableView pretty much as before:

    override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let sectionHeaderView: DynamicGroupedTableSectionHeaderFooterView? = tableView.dequeueReusableHeaderFooterView(fromNib: false)
        if let sectionHeaderView = sectionHeaderView {
            sectionHeaderView.updateLabel() // Update label font and color for Dynamic Type / UIAccessibility
        }

        return sectionHeaderView
    }

fromNib can be omitted as fromNib defaults to false. Loading from Nib is optional.

    tableView.dequeueReusableHeaderFooterView() // does load from class only, not from nib

Note that dequeueReusableHeaderFooterView returns an optional. Just like SDK does with dequeueReusableHeaderFooterViewWithIdentifier. Thus the if let in viewForHeaderInSection.

hth!

Cheers,
Frederik

@winkelsdorf
Copy link

Checked it, it really creates a lot of calls to registerNib/Class due to the way it's implemented. May become a performance bottleneck at some point. I would suggest splitting register / dequeue again, that's why it's done in UIKit in the first place: Register to be called in viewDidLoad() and dequeue in the tableView methods.

I also noticed, it's easy to overlook that your implementation requires the variable/constant to be typed.

let sectionHeaderView: DynamicGroupedTableSectionHeaderFooterView? = tableView.dequeueReusableHeaderFooterViewWithClass()

I would prefer to omit the Type in the constant definition, instead give dequeReusable a Type as parameter.

This should allow both, either referencing by variable type or (optional) parameter:

    public func dequeueReusableHeaderFooterViewWithClass<T: UITableViewHeaderFooterView where T: Reusable>(headerFooterViewClass: T.Type = T.self) -> T? {
        return self.dequeueReusableHeaderFooterViewWithIdentifier(T.reuseIdentifier) as? T
    }

Now both are valid:

    let sectionHeaderView: DynamicGroupedTableSectionHeaderFooterView? = tableView.dequeueReusableHeaderFooterViewWithClass()

and

    let sectionHeaderView = tableView.dequeueReusableHeaderFooterViewWithClass(DynamicGroupedTableSectionHeaderFooterView.self)

And in viewDidLoad():

        tableView.registerReusableHeaderFooterViewClass(DynamicPlainTableSectionHeaderFooterView.self, fromNib: true)
        tableView.registerReusableHeaderFooterViewClass(DynamicGroupedTableSectionHeaderFooterView.self)

New implementation of the final class:

import UIKit

public protocol Reusable: class {
    static var reuseIdentifier: String { get }
}

public extension Reusable {
    static var reuseIdentifier: String {
        return String(self)
    }
}

extension UITableViewCell: Reusable { }
extension UITableViewHeaderFooterView: Reusable { }
extension UICollectionViewCell: Reusable { }

public extension UITableView {

    // Cells
    public func registerReusable(cellClass: Reusable.Type, fromNib: Bool = false) -> UITableView {
        if fromNib {
            let nib = UINib(nibName: cellClass.reuseIdentifier, bundle: nil)
            self.registerNib(nib, forCellReuseIdentifier: cellClass.reuseIdentifier)
        } else {
            self.registerClass(cellClass, forCellReuseIdentifier: cellClass.reuseIdentifier)
        }

        return self
    }

    public func dequeueReusableCellWithClass<T: UITableViewCell where T: Reusable>(cellClass: T.Type, indexPath: NSIndexPath) -> T {
        return self.dequeueReusableCellWithIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as! T
    }

    // HeaderFooterViews
    public func registerReusableHeaderFooterViewClass(headerFooterViewClass: Reusable.Type, fromNib: Bool = false) -> UITableView {
        if fromNib {
            let nib = UINib(nibName: headerFooterViewClass.reuseIdentifier, bundle: nil)
            self.registerNib(nib, forHeaderFooterViewReuseIdentifier: headerFooterViewClass.reuseIdentifier)
        } else {
            self.registerClass(headerFooterViewClass, forHeaderFooterViewReuseIdentifier: headerFooterViewClass.reuseIdentifier)
        }

        return self
    }

    public func dequeueReusableHeaderFooterViewWithClass<T: UITableViewHeaderFooterView where T: Reusable>(headerFooterViewClass: T.Type = T.self) -> T? {
        return self.dequeueReusableHeaderFooterViewWithIdentifier(T.reuseIdentifier) as? T
    }
}

extension UICollectionView {
    public func registerReusable(cellClass: Reusable.Type, fromNib: Bool = false) -> UICollectionView {
        if fromNib {
            let nib = UINib(nibName: cellClass.reuseIdentifier, bundle: nil)
            self.registerNib(nib, forCellWithReuseIdentifier: cellClass.reuseIdentifier)
        } else {
            self.registerClass(cellClass, forCellWithReuseIdentifier: cellClass.reuseIdentifier)
        }

        return self
    }

    public func dequeueReusableCellWithClass<T: UICollectionViewCell where T: Reusable>(cellClass: T.Type, indexPath: NSIndexPath) -> T {
        return self.dequeueReusableCellWithReuseIdentifier(T.reuseIdentifier, forIndexPath: indexPath) as! T
    }
}

Benefits

  • Still referencing via Class, not Identifier.
  • Optional support for automatically loading from nib (default: use a class which just uses code)
  • dequeue methods are now named like their corresponding reuseIdentifier methods (dequeueReusableCellWithClass etc).
  • dequeueReusable can be used by explicitly setting the Type of it's variable or giving it the class using a parameter

@IanKeen
Copy link
Author

IanKeen commented Apr 5, 2016

@winkelsdorf : Wow - I hadn't looked back here in a while. 👍

  • Thanks for adding the header/footer implementations and the class based handling ( I personally only use nibs which is why I omitted it originally 😄 )
  • You're right, I hadn't looked into the overhead of the automatic and constant registering of cells. I've used this pretty extensively however and (as yet) have not noticed any performance hits. I do agree that its probably misleading though to automatically register things when the function name makes no mention of this.
  • I've left the functions names as is - I think it could lead to confusion having something like dequeueClass(fromNib: true) I'd rather keep it simple
  • I generally try and avoid adding things like T.Type to the func - the 'noise' of having to include it can't be avoided or inferred in this instance - my preference is to have it as part of the declaration rather than the function (I just feel thats where it belongs)

I've updated the snippet with these things in mind, thanks for the feedback!

@hitendradeveloper
Copy link

Nice Implementation.! (Y) +1

@winkelsdorf
Copy link

winkelsdorf commented Oct 5, 2016

@IanKeen Sorry missed your notification. Guess the notification system's not working with Gists :(

You're very welcome. Yes, both approaches - with and T.Type are valid and have their benefits. Depends on your general code style. I just wanted to add and share my thoughts and improvements - glad you incorporated some of them :)

Here's the recent update for Swift 3, if anybody needs it:

import UIKit

public protocol Reusable: class {
    static var reuseIdentifier: String { get }
}

public extension Reusable {
    static var reuseIdentifier: String {
        return String(describing: self)
    }
}

extension UITableViewCell: Reusable { }
extension UITableViewHeaderFooterView: Reusable { }
extension UICollectionViewCell: Reusable { }

// MARK: - UITableViewCell
public extension UITableView {

    @discardableResult
    public func registerReusableClass(withClass cellClass: Reusable.Type) -> UITableView {
        register(cellClass, forCellReuseIdentifier: cellClass.reuseIdentifier)

        return self
    }

    @discardableResult
    public func registerReusableNib(withClass cellClass: Reusable.Type) -> UITableView {
        let nib = UINib(nibName: cellClass.reuseIdentifier, bundle: nil)
        register(nib, forCellReuseIdentifier: cellClass.reuseIdentifier)

        return self
    }

    public func dequeueReusableCell<T: UITableViewCell>(withClass cellClass: T.Type, for indexPath: IndexPath) -> T where T: Reusable {
        // swiftlint:disable:next force_cast
        return dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as! T
    }

}

// MARK: - UITableViewHeaderFooterView
public extension UITableView {

    @discardableResult
    public func registerReusableHeaderFooterView(withClass headerFooterViewClass: Reusable.Type, fromNib: Bool = false) -> UITableView {
        if fromNib {
            let nib = UINib(nibName: headerFooterViewClass.reuseIdentifier, bundle: nil)
            register(nib, forHeaderFooterViewReuseIdentifier: headerFooterViewClass.reuseIdentifier)
        } else {
            register(headerFooterViewClass, forHeaderFooterViewReuseIdentifier: headerFooterViewClass.reuseIdentifier)
        }

        return self
    }

    public func dequeueReusableHeaderFooterView<T: UITableViewHeaderFooterView>(withClass headerFooterViewClass: T.Type = T.self) -> T? where T: Reusable {
        return dequeueReusableHeaderFooterView(withIdentifier: T.reuseIdentifier) as? T
    }

}

// MARK: - UICollectionView
extension UICollectionView {

    @discardableResult
    public func registerReusable(withClass cellClass: Reusable.Type, fromNib: Bool = false) -> UICollectionView {
        if fromNib {
            let nib = UINib(nibName: cellClass.reuseIdentifier, bundle: nil)
            register(nib, forCellWithReuseIdentifier: cellClass.reuseIdentifier)
        } else {
            register(cellClass, forCellWithReuseIdentifier: cellClass.reuseIdentifier)
        }

        return self
    }

    public func dequeueReusableCell<T: UICollectionViewCell>(withClass cellClass: T.Type, for indexPath: IndexPath) -> T where T: Reusable {
        // swiftlint:disable:next force_cast
        return dequeueReusableCell(withReuseIdentifier: T.reuseIdentifier, for: indexPath) as! T
    }

}

@IanKeen
Copy link
Author

IanKeen commented Oct 22, 2016

Awesome! thanks for the updates 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment