TableViewSectionedDataSource.swift 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. //
  2. // TableViewSectionedDataSource.swift
  3. // RxDataSources
  4. //
  5. // Created by Krunoslav Zaher on 6/15/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 TableViewSectionedDataSource<Section: SectionModelType>
  16. : NSObject
  17. , UITableViewDataSource
  18. , SectionedViewDataSourceType {
  19. public typealias Item = Section.Item
  20. public typealias ConfigureCell = (TableViewSectionedDataSource<Section>, UITableView, IndexPath, Item) -> UITableViewCell
  21. public typealias TitleForHeaderInSection = (TableViewSectionedDataSource<Section>, Int) -> String?
  22. public typealias TitleForFooterInSection = (TableViewSectionedDataSource<Section>, Int) -> String?
  23. public typealias CanEditRowAtIndexPath = (TableViewSectionedDataSource<Section>, IndexPath) -> Bool
  24. public typealias CanMoveRowAtIndexPath = (TableViewSectionedDataSource<Section>, IndexPath) -> Bool
  25. #if os(iOS)
  26. public typealias SectionIndexTitles = (TableViewSectionedDataSource<Section>) -> [String]?
  27. public typealias SectionForSectionIndexTitle = (TableViewSectionedDataSource<Section>, _ title: String, _ index: Int) -> Int
  28. #endif
  29. #if os(iOS)
  30. public init(
  31. configureCell: @escaping ConfigureCell,
  32. titleForHeaderInSection: @escaping TitleForHeaderInSection = { _, _ in nil },
  33. titleForFooterInSection: @escaping TitleForFooterInSection = { _, _ in nil },
  34. canEditRowAtIndexPath: @escaping CanEditRowAtIndexPath = { _, _ in false },
  35. canMoveRowAtIndexPath: @escaping CanMoveRowAtIndexPath = { _, _ in false },
  36. sectionIndexTitles: @escaping SectionIndexTitles = { _ in nil },
  37. sectionForSectionIndexTitle: @escaping SectionForSectionIndexTitle = { _, _, index in index }
  38. ) {
  39. self.configureCell = configureCell
  40. self.titleForHeaderInSection = titleForHeaderInSection
  41. self.titleForFooterInSection = titleForFooterInSection
  42. self.canEditRowAtIndexPath = canEditRowAtIndexPath
  43. self.canMoveRowAtIndexPath = canMoveRowAtIndexPath
  44. self.sectionIndexTitles = sectionIndexTitles
  45. self.sectionForSectionIndexTitle = sectionForSectionIndexTitle
  46. }
  47. #else
  48. public init(
  49. configureCell: @escaping ConfigureCell,
  50. titleForHeaderInSection: @escaping TitleForHeaderInSection = { _, _ in nil },
  51. titleForFooterInSection: @escaping TitleForFooterInSection = { _, _ in nil },
  52. canEditRowAtIndexPath: @escaping CanEditRowAtIndexPath = { _, _ in false },
  53. canMoveRowAtIndexPath: @escaping CanMoveRowAtIndexPath = { _, _ in false }
  54. ) {
  55. self.configureCell = configureCell
  56. self.titleForHeaderInSection = titleForHeaderInSection
  57. self.titleForFooterInSection = titleForFooterInSection
  58. self.canEditRowAtIndexPath = canEditRowAtIndexPath
  59. self.canMoveRowAtIndexPath = canMoveRowAtIndexPath
  60. }
  61. #endif
  62. #if DEBUG
  63. // If data source has already been bound, then mutating it
  64. // afterwards isn't something desired.
  65. // This simulates immutability after binding
  66. var _dataSourceBound: Bool = false
  67. private func ensureNotMutatedAfterBinding() {
  68. 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.")
  69. }
  70. #endif
  71. // This structure exists because model can be mutable
  72. // In that case current state value should be preserved.
  73. // The state that needs to be preserved is ordering of items in section
  74. // and their relationship with section.
  75. // If particular item is mutable, that is irrelevant for this logic to function
  76. // properly.
  77. public typealias SectionModelSnapshot = SectionModel<Section, Item>
  78. private var _sectionModels: [SectionModelSnapshot] = []
  79. open var sectionModels: [Section] {
  80. return _sectionModels.map { Section(original: $0.model, items: $0.items) }
  81. }
  82. open subscript(section: Int) -> Section {
  83. let sectionModel = self._sectionModels[section]
  84. return Section(original: sectionModel.model, items: sectionModel.items)
  85. }
  86. open subscript(indexPath: IndexPath) -> Item {
  87. get {
  88. return self._sectionModels[indexPath.section].items[indexPath.item]
  89. }
  90. set(item) {
  91. var section = self._sectionModels[indexPath.section]
  92. section.items[indexPath.item] = item
  93. self._sectionModels[indexPath.section] = section
  94. }
  95. }
  96. open func model(at indexPath: IndexPath) throws -> Any {
  97. return self[indexPath]
  98. }
  99. open func setSections(_ sections: [Section]) {
  100. self._sectionModels = sections.map { SectionModelSnapshot(model: $0, items: $0.items) }
  101. }
  102. open var configureCell: ConfigureCell {
  103. didSet {
  104. #if DEBUG
  105. ensureNotMutatedAfterBinding()
  106. #endif
  107. }
  108. }
  109. open var titleForHeaderInSection: TitleForHeaderInSection {
  110. didSet {
  111. #if DEBUG
  112. ensureNotMutatedAfterBinding()
  113. #endif
  114. }
  115. }
  116. open var titleForFooterInSection: TitleForFooterInSection {
  117. didSet {
  118. #if DEBUG
  119. ensureNotMutatedAfterBinding()
  120. #endif
  121. }
  122. }
  123. open var canEditRowAtIndexPath: CanEditRowAtIndexPath {
  124. didSet {
  125. #if DEBUG
  126. ensureNotMutatedAfterBinding()
  127. #endif
  128. }
  129. }
  130. open var canMoveRowAtIndexPath: CanMoveRowAtIndexPath {
  131. didSet {
  132. #if DEBUG
  133. ensureNotMutatedAfterBinding()
  134. #endif
  135. }
  136. }
  137. #if os(iOS)
  138. open var sectionIndexTitles: SectionIndexTitles {
  139. didSet {
  140. #if DEBUG
  141. ensureNotMutatedAfterBinding()
  142. #endif
  143. }
  144. }
  145. open var sectionForSectionIndexTitle: SectionForSectionIndexTitle {
  146. didSet {
  147. #if DEBUG
  148. ensureNotMutatedAfterBinding()
  149. #endif
  150. }
  151. }
  152. #endif
  153. // UITableViewDataSource
  154. open func numberOfSections(in tableView: UITableView) -> Int {
  155. return _sectionModels.count
  156. }
  157. open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  158. guard _sectionModels.count > section else { return 0 }
  159. return _sectionModels[section].items.count
  160. }
  161. open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  162. precondition(indexPath.item < _sectionModels[indexPath.section].items.count)
  163. return configureCell(self, tableView, indexPath, self[indexPath])
  164. }
  165. open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  166. return titleForHeaderInSection(self, section)
  167. }
  168. open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
  169. return titleForFooterInSection(self, section)
  170. }
  171. open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
  172. return canEditRowAtIndexPath(self, indexPath)
  173. }
  174. open func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
  175. return canMoveRowAtIndexPath(self, indexPath)
  176. }
  177. open func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
  178. self._sectionModels.moveFromSourceIndexPath(sourceIndexPath, destinationIndexPath: destinationIndexPath)
  179. }
  180. #if os(iOS)
  181. open func sectionIndexTitles(for tableView: UITableView) -> [String]? {
  182. return sectionIndexTitles(self)
  183. }
  184. open func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
  185. return sectionForSectionIndexTitle(self, title, index)
  186. }
  187. #endif
  188. }
  189. #endif