Skip to content

Instantly share code, notes, and snippets.

@ChrisLawther
Last active May 30, 2019 12:49
Show Gist options
  • Save ChrisLawther/89eb0a1bc6df5d1db9f2fd8416baa27f to your computer and use it in GitHub Desktop.
Save ChrisLawther/89eb0a1bc6df5d1db9f2fd8416baa27f to your computer and use it in GitHub Desktop.
//
// IdentifiedReusableViews.swift
//
// Created by Chris on 29/05/2019.
// Copyright © 2019 Chris Lawther. All rights reserved.
//
// By conforming your UITableView{Cell,HeaderFooterView} subclasses to
// `Identified{Cell,HeaderFooter}`, you gain:
//
// * a more concise method for registering the class with a `UITableView`, omitting
// the `String` identifier parameter
//
// * a token-based dequeueing which performs a type-safe cast to the corresponding class
//
// * impossibility of using the token-based api to dequeue a cell type which has not been registered
//
// * a typed version of `dequeueReusable{Cell,HeaderFooterView}` which again omits
// the `String` identifier and casts the returned cell to the specified type, used when cells
// are designed as Storyboard cell prototypes.
//
// Given:
//
// class MyTableCell: UITableViewCell, IdentifiedCell { ... }
//
// Registration:
//
// // Programatically generated views
// let myCellToken = tableView.register(MyTableCell.self)
//
// // Views loaded from NIBs
// tableView.registerNib(for: MyTableCell.self)
//
// Dequeueing:
//
// // Class or Nib registered
// let myCell = tableView.dequeueReusableCell(token: myCellToken, for: indexPath)
//
// // Storyboard prototype cells
// let myCell = tableView.dequeueReusableCell(MyTableCell.self, for: indexPath)
//
import UIKit
protocol IdentifiedReusableView {
static var identifier: String { get }
static var nib: UINib { get }
}
extension IdentifiedReusableView {
// Default identifier matching the classname
/// (can be overridden by implementations)
static var identifier: String {
return String(describing: self)
}
/// Default to a nib matching the classname, in the default/main bundle
/// (can be overridden by implementations)
static var nib: UINib {
return UINib(nibName: String(describing: self), bundle: nil)
}
}
/// Empty struct - all the required information is captured by specialisations
/// of the generic type
struct ReuseToken<T> { }
// Allows us to prevent attempts to request a UITableViewCell in a context expecting a
// UITableViewHeaderFooterView and vice-versa.
protocol IdentifiedCell: UITableViewCell, IdentifiedReusableView { }
protocol IdentifiedHeaderFooter: UITableViewHeaderFooterView, IdentifiedReusableView { }
// MARK: - UITableViewCell
extension UITableView {
/// Convenient registration of a celltype, automatically associated with it's identifier
///
/// - Parameter cellType: The cell type to be registered (e.g. `MyCell.self`)
/// - Returns: A token to be used when dequeueing cells
func register<T: IdentifiedCell>(_ cellType: T.Type) -> ReuseToken<T> {
register(cellType, forCellReuseIdentifier: cellType.identifier)
return ReuseToken<T>()
}
/// Convenient registration of the nib providing a celltype, automatically associated with it's identifier
///
/// - Parameter cellType: The cell type to be registered (e.g. `MyCell.self`)
/// - Returns: A token to be used when dequeueing cells
func registerNib<T: IdentifiedCell>(for cellType: T.Type) -> ReuseToken<T> {
register(cellType.nib, forCellReuseIdentifier: cellType.identifier)
return ReuseToken<T>()
}
/// Dequeue a cell of the type captured by the token
///
/// - Parameters:
/// - type: The cell type (e.g. `MyCell.self`)
/// - indexPath: The cell's indexPath
/// - Returns: A dequeued cell, of the required type
func dequeueReusableCell<T: IdentifiedCell>(token: ReuseToken<T>, for indexPath: IndexPath) -> T {
// swiftlint:disable:next force_cast
return dequeueReusableCell(withIdentifier: T.identifier, for: indexPath) as! T
}
/// Dequeue a cell of the specified type by it's identifier, casting it to the required type
///
/// - Parameters:
/// - type: The cell type (e.g. `MyCell.self`)
/// - indexPath: The cell's indexPath
/// - Returns: A dequeued cell, of the required type
func dequeueReusableCell<T: IdentifiedCell>(_: T.Type, for indexPath: IndexPath) -> T {
// swiftlint:disable:next force_cast
return dequeueReusableCell(withIdentifier: T.identifier, for: indexPath) as! T
}
}
// MARK: - UITableViewHeaderFooterView
extension UITableView {
/// Convenient registration of a header/footer type, automatically associated with it's identifier
///
/// - Parameter headerFooterType: The type of header/footer to be registered (e.g. `MyFooter.self`)
func register<T: IdentifiedHeaderFooter>(_ headerFooterType: T.Type) -> ReuseToken<T> {
register(headerFooterType, forHeaderFooterViewReuseIdentifier: headerFooterType.identifier)
return ReuseToken<T>()
}
/// Convenient registration of the NIB providing the header/footer associated with an identifier
///
/// - Parameter headerFooterType: The type of header/footer view to be registered (e.g. `MyFooter.self`)
func registerNib<T: IdentifiedHeaderFooter>(for headerFooterType: T.Type) -> ReuseToken<T> {
register(headerFooterType.nib, forHeaderFooterViewReuseIdentifier: headerFooterType.identifier)
return ReuseToken<T>()
}
/// Dequeue a cell of the type captured by the token
///
/// - Parameters:
/// - type: The cell type (e.g. `MyCell.self`)
/// - indexPath: The cell's indexPath
/// - Returns: A dequeued cell, of the required type
func dequeueReusableHeaderFooterView<T: IdentifiedHeaderFooter>(token: ReuseToken<T>) -> T {
// swiftlint:disable:next force_cast
return dequeueReusableHeaderFooterView(withIdentifier: T.identifier) as! T
}
/// Dequeue a header/footer view, using it's identifier and casting it to the required type
///
/// - Parameters:
/// - type: The header/footer view type (e.g. `MyFooter.self`)
/// - Returns: A dequeued view, of the required type
func dequeueReusableHeaderFooterView<T: IdentifiedHeaderFooter>(_: T.Type) -> T {
// swiftlint:disable:next force_cast
return dequeueReusableHeaderFooterView(withIdentifier: T.identifier) as! T
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment