Last active
May 30, 2019 12:49
-
-
Save ChrisLawther/89eb0a1bc6df5d1db9f2fd8416baa27f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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