Advertisement
Guest User

Untitled

a guest
Sep 9th, 2017
240
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.37 KB | None | 0 0
  1. # iOS Cell Registration & Reusing with Swift Protocol Extensions and Generics
  2. A common task when developing iOS apps is to register custom cell subclasses for both `UITableView` and `UICollectionView`. Well, that is if you don’t use Storyboards, of course.
  3.  
  4. Both `UITableView` and `UICollectionView` offer a similar API to register custom cell classes:
  5.  
  6. ```swift
  7. public func register(_ cellClass: AnyClass?, forCellReuseIdentifier identifier: String)
  8. public func register(_ nib: UINib?, forCellReuseIdentifier identifier: String)
  9. ```
  10.  
  11. A widely accepted solution to handle cell registration and dequeuing is to declare a constant for the reuse identifier:
  12.  
  13. ```swift
  14. private let reuseIdentifier = "BookCell"
  15.  
  16. class BookListViewController: UIViewController, UICollectionViewDataSource {
  17.  
  18. @IBOutlet private weak var collectionView: UICollectionView!
  19.  
  20. override func viewDidLoad() {
  21. super.viewDidLoad()
  22.  
  23. let nib = UINib(nibName: "BookCell", bundle: nil)
  24. self.collectionView.register(nib, forCellWithReuseIdentifier: reuseIdentifier)
  25. }
  26.  
  27. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  28. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
  29.  
  30. if let bookCell = cell as? BookCell {
  31. // TODO: configure cell
  32. }
  33.  
  34. return cell
  35. }
  36. }
  37. ```
  38.  
  39. Let’s try to generalize this code and make it simpler and safe.
  40.  
  41. First of all, it would be nice to get away with declaring a constant for every reuse identifier in our app. We can just use the name of the custom cell class as a **default reuse identifier**.
  42. We can create a **protocol for Reusable Views** and provide a default implementation constrained to `UIView` subclasses.
  43.  
  44. ```swift
  45. protocol ReusableView: class {
  46. static var defaultReuseIdentifier: String { get }
  47. }
  48.  
  49. extension ReusableView where Self: UIView {
  50. static var defaultReuseIdentifier: String {
  51. return NSStringFromClass(self)
  52. }
  53. }
  54.  
  55. extension UICollectionViewCell: ReusableView {}
  56. ```
  57.  
  58. By making `UICollectionViewCell` conform to the `ReusableView` protocol, we get a unique reuse identifier per cell subclass.
  59.  
  60. ```swift
  61. let identifier = BookCell.defaultReuseIdentifier
  62. // identifier = "MyModule.BookCell"
  63. ```
  64.  
  65. Next, we can get rid of the hard-coded string we are using to load the Nib.
  66.  
  67. Let’s create a protocol for **Nib Loadable Views** and provide a default implementation using protocol extensions.
  68.  
  69. ```swift
  70. protocol NibLoadableView: class {
  71. static var nibName: String { get }
  72. }
  73.  
  74. extension NibLoadableView where Self: UIView {
  75. static var nibName: String {
  76. return NSStringFromClass(self).components(separatedBy: ".").last!
  77. }
  78. }
  79.  
  80. extension BookCell: NibLoadableView {}
  81. ```
  82.  
  83. By making our `BookCell` class conform to the `NibLoadableView` protocol we now have a safer way to get the Nib name.
  84.  
  85. ```swift
  86. let nibName = BookCell.nibName
  87. // nibName = "BookCell"
  88. ```
  89.  
  90. If you use a different name for the XIB file than the one provided by Xcode, you can always override the default implementation of the nibName property.
  91.  
  92. With these two protocols in place, we can use **Swift Generics** and extend `UICollectionView` to simplify cell registration and dequeuing.
  93.  
  94. ```swift
  95. extension UICollectionView {
  96.  
  97. func register<T: UICollectionViewCell>(_: T.Type) where T: ReusableView { {
  98. registerClass(T.self, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
  99. }
  100.  
  101. func register<T: UICollectionViewCell>(_: T.Type) where T: ReusableView, T: NibLoadableView {
  102. let bundle = Bundle(for: T.self)
  103. let nib = UINib(T.nibName, bundle: bundle)
  104.  
  105. registerNib(nib, forCellWithReuseIdentifier: T.defaultReuseIdentifier)
  106. }
  107.  
  108. func dequeueReusableCell<T: UICollectionViewCell>(forIndexPath indexPath: NSIndexPath) -> T where T: ReusableView {
  109. guard let cell = dequeueReusableCell(T.defaultReuseIdentifier, for: indexPath) as? T else {
  110. fatalError("Could not dequeue cell with identifier: \(T.defaultReuseIdentifier)")
  111. }
  112.  
  113. return cell
  114. }
  115. }
  116. ```
  117.  
  118. Notice that we created two versions of the register method, one for cell subclasses implementing just `ReusableView` and another one for cell subclasses implementing both `ReusableView` and `NibLoadableView`. This nicely decouples the view controller from the specific cell registration method.
  119.  
  120. Another nice detail is that the `dequeueReusableCell` method doesn’t need to take any reuse identifier and uses the cell subclass type for the return value.
  121.  
  122. Now the cell registration and dequeuing code looks much better :).
  123.  
  124. ```swift
  125. class BookListViewController: UIViewController, UICollectionViewDataSource {
  126.  
  127. @IBOutlet private weak var collectionView: UICollectionView!
  128.  
  129. override func viewDidLoad() {
  130. super.viewDidLoad()
  131.  
  132. self.collectionView.register(BookCell.self)
  133. }
  134.  
  135. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  136.  
  137. let cell: BookCell = collectionView.dequeueReusableCell(for: indexPath)
  138.  
  139. // TODO: configure cell
  140.  
  141. return cell
  142. }
  143. ...
  144. }
  145. ```
  146.  
  147. ## Conclusion
  148. If you are coming from Objective-C it is worth to investigate powerful Swift features like Protocol Extensions and Generics to find alternate and more elegant ways to deal with Cocoa classes.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement