CollectionViewSectionedDataSource.swift 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. //
  2. // CollectionViewSectionedDataSource.swift
  3. // RxDataSources
  4. //
  5. // Created by Krunoslav Zaher on 7/2/15.
  6. // Copyright © 2015 Krunoslav Zaher. All rights reserved.
  7. //
  8. #if os(iOS) || os(tvOS)
  9. import Foundation
  10. import UIKit
  11. #if !RX_NO_MODULE
  12. import RxCocoa
  13. #endif
  14. import Differentiator
  15. open class CollectionViewSectionedDataSource<Section: SectionModelType>
  16. : NSObject
  17. , UICollectionViewDataSource
  18. , SectionedViewDataSourceType {
  19. public typealias Item = Section.Item
  20. public typealias Section = Section
  21. public typealias ConfigureCell = (CollectionViewSectionedDataSource<Section>, UICollectionView, IndexPath, Item) -> UICollectionViewCell
  22. public typealias ConfigureSupplementaryView = (CollectionViewSectionedDataSource<Section>, UICollectionView, String, IndexPath) -> UICollectionReusableView
  23. public typealias MoveItem = (CollectionViewSectionedDataSource<Section>, _ sourceIndexPath:IndexPath, _ destinationIndexPath:IndexPath) -> Void
  24. public typealias CanMoveItemAtIndexPath = (CollectionViewSectionedDataSource<Section>, IndexPath) -> Bool
  25. public init(
  26. configureCell: @escaping ConfigureCell,
  27. configureSupplementaryView: ConfigureSupplementaryView? = nil,
  28. moveItem: @escaping MoveItem = { _, _, _ in () },
  29. canMoveItemAtIndexPath: @escaping CanMoveItemAtIndexPath = { _, _ in false }
  30. ) {
  31. self.configureCell = configureCell
  32. self.configureSupplementaryView = configureSupplementaryView
  33. self.moveItem = moveItem
  34. self.canMoveItemAtIndexPath = canMoveItemAtIndexPath
  35. }
  36. #if DEBUG
  37. // If data source has already been bound, then mutating it
  38. // afterwards isn't something desired.
  39. // This simulates immutability after binding
  40. var _dataSourceBound: Bool = false
  41. private func ensureNotMutatedAfterBinding() {
  42. assert(!_dataSourceBound, "Data source is already bound. Please write this line before binding call (`bindTo`, `drive`). Data source must first be completely configured, and then bound after that, otherwise there could be runtime bugs, glitches, or partial malfunctions.")
  43. }
  44. #endif
  45. // This structure exists because model can be mutable
  46. // In that case current state value should be preserved.
  47. // The state that needs to be preserved is ordering of items in section
  48. // and their relationship with section.
  49. // If particular item is mutable, that is irrelevant for this logic to function
  50. // properly.
  51. public typealias SectionModelSnapshot = SectionModel<Section, Item>
  52. private var _sectionModels: [SectionModelSnapshot] = []
  53. open var sectionModels: [Section] {
  54. return _sectionModels.map { Section(original: $0.model, items: $0.items) }
  55. }
  56. open subscript(section: Int) -> Section {
  57. let sectionModel = self._sectionModels[section]
  58. return Section(original: sectionModel.model, items: sectionModel.items)
  59. }
  60. open subscript(indexPath: IndexPath) -> Item {
  61. get {
  62. return self._sectionModels[indexPath.section].items[indexPath.item]
  63. }
  64. set(item) {
  65. var section = self._sectionModels[indexPath.section]
  66. section.items[indexPath.item] = item
  67. self._sectionModels[indexPath.section] = section
  68. }
  69. }
  70. open func model(at indexPath: IndexPath) throws -> Any {
  71. return self[indexPath]
  72. }
  73. open func setSections(_ sections: [Section]) {
  74. self._sectionModels = sections.map { SectionModelSnapshot(model: $0, items: $0.items) }
  75. }
  76. open var configureCell: ConfigureCell {
  77. didSet {
  78. #if DEBUG
  79. ensureNotMutatedAfterBinding()
  80. #endif
  81. }
  82. }
  83. open var configureSupplementaryView: ConfigureSupplementaryView? {
  84. didSet {
  85. #if DEBUG
  86. ensureNotMutatedAfterBinding()
  87. #endif
  88. }
  89. }
  90. open var moveItem: MoveItem {
  91. didSet {
  92. #if DEBUG
  93. ensureNotMutatedAfterBinding()
  94. #endif
  95. }
  96. }
  97. open var canMoveItemAtIndexPath: ((CollectionViewSectionedDataSource<Section>, IndexPath) -> Bool)? {
  98. didSet {
  99. #if DEBUG
  100. ensureNotMutatedAfterBinding()
  101. #endif
  102. }
  103. }
  104. // UICollectionViewDataSource
  105. open func numberOfSections(in collectionView: UICollectionView) -> Int {
  106. return _sectionModels.count
  107. }
  108. open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  109. return _sectionModels[section].items.count
  110. }
  111. open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  112. precondition(indexPath.item < _sectionModels[indexPath.section].items.count)
  113. return configureCell(self, collectionView, indexPath, self[indexPath])
  114. }
  115. open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
  116. return configureSupplementaryView!(self, collectionView, kind, indexPath)
  117. }
  118. open func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
  119. guard let canMoveItem = canMoveItemAtIndexPath?(self, indexPath) else {
  120. return false
  121. }
  122. return canMoveItem
  123. }
  124. open func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
  125. self._sectionModels.moveFromSourceIndexPath(sourceIndexPath, destinationIndexPath: destinationIndexPath)
  126. self.moveItem(self, sourceIndexPath, destinationIndexPath)
  127. }
  128. override open func responds(to aSelector: Selector!) -> Bool {
  129. if aSelector == #selector(UICollectionViewDataSource.collectionView(_:viewForSupplementaryElementOfKind:at:)) {
  130. return configureSupplementaryView != nil
  131. }
  132. else {
  133. return super.responds(to: aSelector)
  134. }
  135. }
  136. }
  137. #endif