Last active
July 10, 2019 22:02
-
-
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
This file contains hidden or 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
//`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! |
This file contains hidden or 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
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 | |
} | |
} |
@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!
Nice Implementation.! (Y) +1
@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
}
}
Awesome! thanks for the updates π
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
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:
Now both are valid:
and
And in viewDidLoad():
New implementation of the final class:
Benefits