RxCollectionViewSectionedAnimatedDataSource.swift 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. //
  2. // RxCollectionViewSectionedAnimatedDataSource.swift
  3. // RxExample
  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 RxSwift
  13. import RxCocoa
  14. #endif
  15. import Differentiator
  16. open class RxCollectionViewSectionedAnimatedDataSource<Section: AnimatableSectionModelType>
  17. : CollectionViewSectionedDataSource<Section>
  18. , RxCollectionViewDataSourceType {
  19. public typealias Element = [Section]
  20. public typealias DecideViewTransition = (CollectionViewSectionedDataSource<Section>, UICollectionView, [Changeset<Section>]) -> ViewTransition
  21. // animation configuration
  22. public var animationConfiguration: AnimationConfiguration
  23. /// Calculates view transition depending on type of changes
  24. public var decideViewTransition: DecideViewTransition
  25. public init(
  26. animationConfiguration: AnimationConfiguration = AnimationConfiguration(),
  27. decideViewTransition: @escaping DecideViewTransition = { _, _, _ in .animated },
  28. configureCell: @escaping ConfigureCell,
  29. configureSupplementaryView: ConfigureSupplementaryView? = nil,
  30. moveItem: @escaping MoveItem = { _, _, _ in () },
  31. canMoveItemAtIndexPath: @escaping CanMoveItemAtIndexPath = { _, _ in false }
  32. ) {
  33. self.animationConfiguration = animationConfiguration
  34. self.decideViewTransition = decideViewTransition
  35. super.init(
  36. configureCell: configureCell,
  37. configureSupplementaryView: configureSupplementaryView,
  38. moveItem: moveItem,
  39. canMoveItemAtIndexPath: canMoveItemAtIndexPath
  40. )
  41. }
  42. // there is no longer limitation to load initial sections with reloadData
  43. // but it is kept as a feature everyone got used to
  44. var dataSet = false
  45. open func collectionView(_ collectionView: UICollectionView, observedEvent: Event<Element>) {
  46. Binder(self) { dataSource, newSections in
  47. #if DEBUG
  48. dataSource._dataSourceBound = true
  49. #endif
  50. if !dataSource.dataSet {
  51. dataSource.dataSet = true
  52. dataSource.setSections(newSections)
  53. collectionView.reloadData()
  54. }
  55. else {
  56. // if view is not in view hierarchy, performing batch updates will crash the app
  57. if collectionView.window == nil {
  58. dataSource.setSections(newSections)
  59. collectionView.reloadData()
  60. return
  61. }
  62. let oldSections = dataSource.sectionModels
  63. do {
  64. let differences = try Diff.differencesForSectionedView(initialSections: oldSections, finalSections: newSections)
  65. switch dataSource.decideViewTransition(dataSource, collectionView, differences) {
  66. case .animated:
  67. // each difference must be run in a separate 'performBatchUpdates', otherwise it crashes.
  68. // this is a limitation of Diff tool
  69. for difference in differences {
  70. let updateBlock = {
  71. // sections must be set within updateBlock in 'performBatchUpdates'
  72. dataSource.setSections(difference.finalSections)
  73. collectionView.batchUpdates(difference, animationConfiguration: dataSource.animationConfiguration)
  74. }
  75. collectionView.performBatchUpdates(updateBlock, completion: nil)
  76. }
  77. case .reload:
  78. dataSource.setSections(newSections)
  79. collectionView.reloadData()
  80. return
  81. }
  82. }
  83. catch let e {
  84. rxDebugFatalError(e)
  85. dataSource.setSections(newSections)
  86. collectionView.reloadData()
  87. }
  88. }
  89. }.on(observedEvent)
  90. }
  91. }
  92. #endif