Skip to content

Instantly share code, notes, and snippets.

@Moximillian
Last active November 26, 2018 21:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Moximillian/f666a6ffd481b01be04c797950bed3b8 to your computer and use it in GitHub Desktop.
Save Moximillian/f666a6ffd481b01be04c797950bed3b8 to your computer and use it in GitHub Desktop.
Journey into PAT-land
/**
* A journey in trying to provide unified interface to UITableView and UICollection
*/
protocol UnifiedCell { /* specific features omitted, as they are not relevant for this journey */ }
extension UITableViewCell: UnifiedCell {}
extension UICollectionViewCell: UnifiedCell {}
// the idea of the deque() below is that a unified code can be built
// that uses single codepath to call collection.deque() on both UITableViews and UICollections
//
// see https://github.com/Moximillian/ProtoKit/blob/4.1.0/Sources/UnifiedCollection.swift
// for context where this would be used
/**
* Attempt 1: simple protocols ------------------------------------
*/
protocol UnifiedCollection {
func deque(for indexPath: IndexPath) -> UnifiedCell
}
extension UITableView: UnifiedCollection {
func deque(for indexPath: IndexPath) -> UnifiedCell {
return dequeueReusableCell(withIdentifier: String(describing: UnifiedCell.self), for: indexPath)
}
}
// well, doesn't work at all. UnifiedCell doesn't keep the underlying type
//
// So, you can't use UnifiedCell directly, but you could use associate type... oh, doh! PAT wall.
//
// So Crusty says to use generics, let's use generics.
/**
* Attempt 2: Generics ------------------------------------
*/
protocol GenericCollection {
func deque<T: UnifiedCell>(for indexPath: IndexPath) -> T
}
extension UITableView: GenericCollection {
func deque<T: UnifiedCell>(for indexPath: IndexPath) -> T {
guard let cell = dequeueReusableCell(withIdentifier: String(describing: T.self), for: indexPath) as? T else {
fatalError("Cell Type \(T.self) is not supported")
}
return cell
}
}
// works fine, except compiler won't complain about trying to instantiate UICollectionViewCell on a UITableView
// it will only bug out at runtime :( That's not Swifty my friend!
/**
* Attempt 2b: Generics / Parametrized extension ------------------------------------
*/
extension<T> UITableView: GenericCollection where T: UITableViewCell & UnifiedCell {
func deque(for indexPath: IndexPath) -> T {
guard let cell = dequeueReusableCell(withIdentifier: String(describing: T.self), for: indexPath) as? T else {
fatalError("Cell Type \(T.self) is not supported")
}
return cell
}
}
// Maybe it would work, maybe not. Don't know, because Swift doesn't support this (yet).
// Also, would this keep deque function signature intact (in extension), so that protocol conformance
// to GenericCollection is still valid?
/**
* Attempt 3: protocols with associated type ------------------------------------
*/
protocol UnifiedCollection {
associatedtype Cell: UnifiedCell
func deque(for indexPath: IndexPath) -> Cell
}
extension UnifiedCollection where Self: UITableView, Cell: UITableViewCell {
func deque(for indexPath: IndexPath) -> Cell {
guard let cell = dequeueReusableCell(withIdentifier: String(describing: Cell.self), for: indexPath) as? Cell else {
fatalError("Cell Type \(Cell.self) is not supported")
}
return cell
}
}
class MyCell: UITableViewCell {}
class MyTable: UITableView, UnifiedCollection {
typealias Cell = MyCell
}
// this works, but you'd have to use subclassed table always, and can't make UITableView conform directly,
// unless you use only single type for cell (need to define typealias)
// is there any better way? Well, one can give up trying to follow UIKit API
// and do deque() on cell instead of table.
/**
* Attempt 4 (give up on generics/existentials): classes with Self ------------------------------------
*/
extension UITableViewCell {
public static func deque(in table: UITableView, for indexPath: IndexPath) -> Self {
guard let cell = table.dequeueReusableCell(withIdentifier: String(describing: self), for: indexPath) as? Self else {
fatalError("Could not dequeue tableview cell with identifier: " + String(describing: self))
}
}
}
// ooops, Self works as return type, but Self cannot be casted to :(
// but protocol Self can be used in casts. More elbow grease needed...
/**
* Final solution 5: multiprotocol with associated type and Self ------------------------------------
*/
protocol UnifiedCollection {}
extension UITableView: UnifiedCollection {}
// have to introduce a parent protocol so that the associated type does not to blow up
protocol HasCollection {
associatedtype Collection: UnifiedCollection
}
extension UITableViewCell: HasCollection {
typealias Collection = UITableView
}
// main protocol, uses associated type from parent
protocol UnifiedCell: HasCollection {
static func deque(in collection: Collection, for indexPath: IndexPath) -> Self
}
extension UnifiedCell where Self: UITableViewCell {
static func deque(in collection: Collection, for indexPath: IndexPath) -> Self {
guard let cell = collection.dequeueReusableCell(withIdentifier: String(describing: self), for: indexPath) as? Self else {
fatalError("Could not dequeue tableview cell with identifier: " + String(describing: self))
}
return cell
}
}
// can only use final classes, but that's ok.
final class MyCell: UITableViewCell, UnifiedCell {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment