diff --git a/RxExample/RxDataSourceStarterKit/Changeset.swift b/RxExample/RxDataSourceStarterKit/Changeset.swift deleted file mode 100644 index 3673182d..00000000 --- a/RxExample/RxDataSourceStarterKit/Changeset.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// Changeset.swift -// RxCocoa -// -// Created by Krunoslav Zaher on 5/30/15. -// Copyright © 2015 Krunoslav Zaher. All rights reserved. -// - -import Foundation -import CoreData -#if !RX_NO_MODULE -import RxSwift -import RxCocoa -#endif - -struct ItemPath : CustomDebugStringConvertible { - let sectionIndex: Int - let itemIndex: Int - - var debugDescription : String { - return "(\(sectionIndex), \(itemIndex))" - } -} - -public struct Changeset : CustomDebugStringConvertible { - typealias I = S.Item - - var reloadData: Bool = false - - var finalSections: [S] = [] - - var insertedSections: [Int] = [] - var deletedSections: [Int] = [] - var movedSections: [(from: Int, to: Int)] = [] - var updatedSections: [Int] = [] - - var insertedItems: [ItemPath] = [] - var deletedItems: [ItemPath] = [] - var movedItems: [(from: ItemPath, to: ItemPath)] = [] - var updatedItems: [ItemPath] = [] - - public static func initialValue(sections: [S]) -> Changeset { - var initialValue = Changeset() - initialValue.insertedSections = Array(0 ..< sections.count) - initialValue.finalSections = sections - initialValue.reloadData = true - - return initialValue - } - - public var debugDescription : String { - let serializedSections = "[\n" + finalSections.map { "\($0)" }.joinWithSeparator(",\n") + "\n]\n" - return " >> Final sections" - + " \n\(serializedSections)" - + (insertedSections.count > 0 || deletedSections.count > 0 || movedSections.count > 0 || updatedSections.count > 0 ? "\nSections:" : "") - + (insertedSections.count > 0 ? "\ninsertedSections:\n\t\(insertedSections)" : "") - + (deletedSections.count > 0 ? "\ndeletedSections:\n\t\(deletedSections)" : "") - + (movedSections.count > 0 ? "\nmovedSections:\n\t\(movedSections)" : "") - + (updatedSections.count > 0 ? "\nupdatesSections:\n\t\(updatedSections)" : "") - + (insertedItems.count > 0 || deletedItems.count > 0 || movedItems.count > 0 || updatedItems.count > 0 ? "\nItems:" : "") - + (insertedItems.count > 0 ? "\ninsertedItems:\n\t\(insertedItems)" : "") - + (deletedItems.count > 0 ? "\ndeletedItems:\n\t\(deletedItems)" : "") - + (movedItems.count > 0 ? "\nmovedItems:\n\t\(movedItems)" : "") - + (updatedItems.count > 0 ? "\nupdatedItems:\n\t\(updatedItems)" : "") - } -} diff --git a/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedAnimatedDataSource.swift b/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedAnimatedDataSource.swift deleted file mode 100644 index c65caed8..00000000 --- a/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedAnimatedDataSource.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// RxCollectionViewSectionedAnimatedDataSource.swift -// RxExample -// -// Created by Krunoslav Zaher on 7/2/15. -// Copyright © 2015 Krunoslav Zaher. All rights reserved. -// - -import Foundation -import UIKit -#if !RX_NO_MODULE -import RxSwift -import RxCocoa -#endif - -class RxCollectionViewSectionedAnimatedDataSource : RxCollectionViewSectionedDataSource - , RxCollectionViewDataSourceType { - typealias Element = [Changeset] - - // For some inexplicable reason, when doing animated updates first time - // it crashes. Still need to figure out that one. - var set = false - - func collectionView(collectionView: UICollectionView, observedEvent: Event) { - UIBindingObserver(UIElement: self) { ds, element in - for c in element { - if !ds.set { - ds.setSections(c.finalSections) - collectionView.reloadData() - ds.set = true - return - } - ds.setSections(c.finalSections) - collectionView.performBatchUpdates(c) - } - }.on(observedEvent) - } -} \ No newline at end of file diff --git a/RxExample/RxDataSourceStarterKit/Differentiator.swift b/RxExample/RxDataSourceStarterKit/Differentiator.swift deleted file mode 100644 index 51154ce7..00000000 --- a/RxExample/RxDataSourceStarterKit/Differentiator.swift +++ /dev/null @@ -1,503 +0,0 @@ -// -// Differentiator.swift -// RxExample -// -// Created by Krunoslav Zaher on 6/27/15. -// Copyright © 2015 Krunoslav Zaher. All rights reserved. -// - -import Foundation - -public enum DifferentiatorError - : ErrorType - , CustomDebugStringConvertible { - case DuplicateItem(item: Any) -} - -extension DifferentiatorError { - public var debugDescription: String { - switch self { - case let .DuplicateItem(item): - return "Duplicate item \(item)" - } - } -} - -enum EditEvent : CustomDebugStringConvertible { - case Inserted // can't be found in old sections - case Deleted // Was in old, not in new, in it's place is something "not new" :(, otherwise it's Updated - case Moved // same item, but was on different index, and needs explicit move - case MovedAutomatically // don't need to specify any changes for those rows - case Untouched -} - -extension EditEvent { - var debugDescription: String { - switch self { - case .Inserted: - return "Inserted" - case .Deleted: - return "Deleted" - case .Moved: - return "Moved" - case .MovedAutomatically: - return "MovedAutomatically" - case .Untouched: - return "Untouched" - } - } -} - -struct SectionAdditionalInfo : CustomDebugStringConvertible { - var event: EditEvent - var indexAfterDelete: Int? -} - -extension SectionAdditionalInfo { - var debugDescription: String { - return "\(event), \(indexAfterDelete)" - } -} - -struct ItemAdditionalInfo : CustomDebugStringConvertible { - var event: EditEvent - var indexAfterDelete: Int? -} - -extension ItemAdditionalInfo { - var debugDescription: String { - return "\(event) \(indexAfterDelete)" - } -} - -func indexSections(sections: [S]) throws -> [S : Int] { - var indexedSections: [S : Int] = [:] - for (i, section) in sections.enumerate() { - guard indexedSections[section] == nil else { - #if DEBUG - precondition(indexedSections[section] == nil, "Section \(section) has already been indexed at \(indexedSections[section]!)") - #endif - throw DifferentiatorError.DuplicateItem(item: section) - } - indexedSections[section] = i - } - - return indexedSections -} - -func indexSectionItems(sections: [S]) throws -> [S.Item : (Int, Int)] { - var totalItems = 0 - for i in 0 ..< sections.count { - totalItems += sections[i].items.count - } - - // let's make sure it's enough - var indexedItems: [S.Item : (Int, Int)] = Dictionary(minimumCapacity: totalItems * 3) - - for i in 0 ..< sections.count { - for (j, item) in sections[i].items.enumerate() { - guard indexedItems[item] == nil else { - #if DEBUG - precondition(indexedItems[item] == nil, "Item \(item) has already been indexed at \(indexedItems[item]!)" ) - #endif - throw DifferentiatorError.DuplicateItem(item: item) - } - indexedItems[item] = (i, j) - } - } - - return indexedItems -} - - -/* - -I've uncovered this case during random stress testing of logic. -This is the hardest generic update case that causes two passes, first delete, and then move/insert - -[ -NumberSection(model: "1", items: [1111]), -NumberSection(model: "2", items: [2222]), -] - -[ -NumberSection(model: "2", items: [0]), -NumberSection(model: "1", items: []), -] - -If update is in the form - -* Move section from 2 to 1 -* Delete Items at paths 0 - 0, 1 - 0 -* Insert Items at paths 0 - 0 - -or - -* Move section from 2 to 1 -* Delete Items at paths 0 - 0 -* Reload Items at paths 1 - 0 - -or - -* Move section from 2 to 1 -* Delete Items at paths 0 - 0 -* Reload Items at paths 0 - 0 - -it crashes table view. - -No matter what change is performed, it fails for me. -If anyone knows how to make this work for one Changeset, PR is welcome. - -*/ - -// If you are considering working out your own algorithm, these are tricky -// transition cases that you can use. - -// case 1 -/* -from = [ - NumberSection(model: "section 4", items: [10, 11, 12]), - NumberSection(model: "section 9", items: [25, 26, 27]), -] -to = [ - HashableSectionModel(model: "section 9", items: [11, 26, 27]), - HashableSectionModel(model: "section 4", items: [10, 12]) -] -*/ - -// case 2 -/* -from = [ - HashableSectionModel(model: "section 10", items: [26]), - HashableSectionModel(model: "section 7", items: [5, 29]), - HashableSectionModel(model: "section 1", items: [14]), - HashableSectionModel(model: "section 5", items: [16]), - HashableSectionModel(model: "section 4", items: []), - HashableSectionModel(model: "section 8", items: [3, 15, 19, 23]), - HashableSectionModel(model: "section 3", items: [20]) -] -to = [ - HashableSectionModel(model: "section 10", items: [26]), - HashableSectionModel(model: "section 1", items: [14]), - HashableSectionModel(model: "section 9", items: [3]), - HashableSectionModel(model: "section 5", items: [16, 8]), - HashableSectionModel(model: "section 8", items: [15, 19, 23]), - HashableSectionModel(model: "section 3", items: [20]), - HashableSectionModel(model: "Section 2", items: [7]) -] -*/ - -// case 3 -/* -from = [ - HashableSectionModel(model: "section 4", items: [5]), - HashableSectionModel(model: "section 6", items: [20, 14]), - HashableSectionModel(model: "section 9", items: []), - HashableSectionModel(model: "section 2", items: [2, 26]), - HashableSectionModel(model: "section 8", items: [23]), - HashableSectionModel(model: "section 10", items: [8, 18, 13]), - HashableSectionModel(model: "section 1", items: [28, 25, 6, 11, 10, 29, 24, 7, 19]) -] -to = [ - HashableSectionModel(model: "section 4", items: [5]), - HashableSectionModel(model: "section 6", items: [20, 14]), - HashableSectionModel(model: "section 9", items: [16]), - HashableSectionModel(model: "section 7", items: [17, 15, 4]), - HashableSectionModel(model: "section 2", items: [2, 26, 23]), - HashableSectionModel(model: "section 8", items: []), - HashableSectionModel(model: "section 10", items: [8, 18, 13]), - HashableSectionModel(model: "section 1", items: [28, 25, 6, 11, 10, 29, 24, 7, 19]) -] -*/ - -// Generates differential changes suitable for sectioned view consumption. -// It will not only detect changes between two states, but it will also try to compress those changes into -// almost minimal set of changes. -// -// I know, I know, it's ugly :( Totally agree, but this is the only general way I could find that works 100%, and -// avoids UITableView quirks. -// -// Please take into consideration that I was also convinced about 20 times that I've found a simple general -// solution, but then UITableView falls apart under stress testing :( -// -// Sincerely, if somebody else would present me this 250 lines of code, I would call him a mad man. I would think -// that there has to be a simpler solution. Well, after 3 days, I'm not convinced any more :) -// -// Maybe it can be made somewhat simpler, but don't think it can be made much simpler. -// -// The algorithm could take anywhere from 1 to 3 table view transactions to finish the updates. -// -// * stage 1 - remove deleted sections and items -// * stage 2 - move sections into place -// * stage 3 - fix moved and new items -// -// There maybe exists a better division, but time will tell. -// -func differencesForSectionedView( - initialSections: [S], - finalSections: [S] - ) - throws -> [Changeset] { - - typealias I = S.Item - - var deletes = Changeset() - var newAndMovedSections = Changeset() - var newAndMovedItems = Changeset() - - var initialSectionInfos = [SectionAdditionalInfo](count: initialSections.count, repeatedValue: SectionAdditionalInfo(event: .Untouched, indexAfterDelete: nil)) - var finalSectionInfos = [SectionAdditionalInfo](count: finalSections.count, repeatedValue: SectionAdditionalInfo(event: .Untouched, indexAfterDelete: nil)) - - var initialSectionIndexes: [S : Int] = [:] - var finalSectionIndexes: [S : Int] = [:] - - let defaultItemInfo = ItemAdditionalInfo(event: .Untouched, indexAfterDelete: nil) - var initialItemInfos = initialSections.map { s in - return [ItemAdditionalInfo](count: s.items.count, repeatedValue: defaultItemInfo) - } - - var finalItemInfos = finalSections.map { s in - return [ItemAdditionalInfo](count: s.items.count, repeatedValue: defaultItemInfo) - } - - initialSectionIndexes = try indexSections(initialSections) - finalSectionIndexes = try indexSections(finalSections) - - var initialItemIndexes: [I: (Int, Int)] = try indexSectionItems(initialSections) - var finalItemIndexes: [I: (Int, Int)] = try indexSectionItems(finalSections) - - // mark deleted sections { - // 1rst stage - var sectionIndexAfterDelete = 0 - for (i, initialSection) in initialSections.enumerate() { - if finalSectionIndexes[initialSection] == nil { - initialSectionInfos[i].event = .Deleted - deletes.deletedSections.append(i) - } - else { - initialSectionInfos[i].indexAfterDelete = sectionIndexAfterDelete - sectionIndexAfterDelete += 1 - } - } - - deletes.deletedSections = deletes.deletedSections.reverse() - - // } - - var untouchedOldIndex: Int? = 0 - let findNextUntouchedOldIndex = { (initialSearchIndex: Int?) -> Int? in - var i = initialSearchIndex - - while i != nil && i < initialSections.count { - if initialSectionInfos[i!].event == .Untouched { - return i - } - - i = i! + 1 - } - - return nil - } - - // inserted and moved sections { - // this should fix all sections and move them into correct places - // 2nd stage - for (i, finalSection) in finalSections.enumerate() { - untouchedOldIndex = findNextUntouchedOldIndex(untouchedOldIndex) - - // oh, it did exist - if let oldSectionIndex = initialSectionIndexes[finalSection] { - let moveType = oldSectionIndex != untouchedOldIndex ? EditEvent.Moved : EditEvent.MovedAutomatically - - finalSectionInfos[i].event = moveType - initialSectionInfos[oldSectionIndex].event = moveType - - if moveType == .Moved { - let moveCommand = (from: initialSectionInfos[oldSectionIndex].indexAfterDelete!, to: i) - newAndMovedSections.movedSections.append(moveCommand) - } - } - else { - finalSectionInfos[i].event = .Inserted - newAndMovedSections.insertedSections.append(i) - } - } - // } - - // mark deleted items { - // 1rst stage again (I know, I know ...) - for (i, initialSection) in initialSections.enumerate() { - let event = initialSectionInfos[i].event - - // Deleted section will take care of deleting child items. - // In case of moving an item from deleted section, tableview will - // crash anyway, so this is not limiting anything. - if event == .Deleted { - continue - } - - var indexAfterDelete = 0 - for (j, initialItem) in initialSection.items.enumerate() { - if let finalItemIndex = finalItemIndexes[initialItem] { - let targetSectionEvent = finalSectionInfos[finalItemIndex.0].event - // In case there is move of item from existing section into new section - // that is also considered a "delete" - if targetSectionEvent == .Inserted { - initialItemInfos[i][j].event = .Deleted - deletes.deletedItems.append(ItemPath(sectionIndex: i, itemIndex: j)) - continue - } - - initialItemInfos[i][j].indexAfterDelete = indexAfterDelete - indexAfterDelete += 1 - } - else { - initialItemInfos[i][j].event = .Deleted - deletes.deletedItems.append(ItemPath(sectionIndex: i, itemIndex: j)) - } - } - } - // } - - deletes.deletedItems = deletes.deletedItems.reverse() - - // mark new and moved items { - // 3rd stage - for (i, _) in finalSections.enumerate() { - let finalSection = finalSections[i] - - let originalSection: Int? = initialSectionIndexes[finalSection] - - var untouchedOldIndex: Int? = 0 - let findNextUntouchedOldIndex = { (initialSearchIndex: Int?) -> Int? in - var i2 = initialSearchIndex - - while originalSection != nil && i2 != nil && i2! < initialItemInfos[originalSection!].count { - if initialItemInfos[originalSection!][i2!].event == .Untouched { - return i2 - } - - i2 = i2! + 1 - } - - return nil - } - - let sectionEvent = finalSectionInfos[i].event - // new and deleted sections cause reload automatically - if sectionEvent != .Moved && sectionEvent != .MovedAutomatically { - continue - } - - for (j, finalItem) in finalSection.items.enumerate() { - let currentItemEvent = finalItemInfos[i][j].event - - precondition(currentItemEvent == .Untouched) - - untouchedOldIndex = findNextUntouchedOldIndex(untouchedOldIndex) - - // ok, so it was moved from somewhere - if let originalIndex = initialItemIndexes[finalItem] { - - // In case trying to move from deleted section, abort, otherwise it will crash table view - if initialSectionInfos[originalIndex.0].event == .Deleted { - finalItemInfos[i][j].event = .Inserted - newAndMovedItems.insertedItems.append(ItemPath(sectionIndex: i, itemIndex: j)) - } - // original section can't be inserted - else if initialSectionInfos[originalIndex.0].event == .Inserted { - fatalError("New section in initial sections, that is wrong") - } - // what's left is moved section - else { - precondition(initialSectionInfos[originalIndex.0].event == .Moved || initialSectionInfos[originalIndex.0].event == .MovedAutomatically) - - let eventType = - originalIndex.0 == (originalSection ?? -1) - && originalIndex.1 == (untouchedOldIndex ?? -1) - - ? EditEvent.MovedAutomatically : EditEvent.Moved - - // print("\(finalItem) \(eventType) \(originalIndex), \(originalSection) \(untouchedOldIndex)") - - initialItemInfos[originalIndex.0][originalIndex.1].event = eventType - finalItemInfos[i][j].event = eventType - - if eventType == .Moved { - let finalSectionIndex = finalSectionIndexes[initialSections[originalIndex.0]]! - let moveFromItemWithIndex = initialItemInfos[originalIndex.0][originalIndex.1].indexAfterDelete! - - let moveCommand = ( - from: ItemPath(sectionIndex: finalSectionIndex, itemIndex: moveFromItemWithIndex), - to: ItemPath(sectionIndex: i, itemIndex: j) - ) - newAndMovedItems.movedItems.append(moveCommand) - } - } - } - // if it wasn't moved from anywhere, it's inserted - else { - finalItemInfos[i][j].event = .Inserted - newAndMovedItems.insertedItems.append(ItemPath(sectionIndex: i, itemIndex: j)) - } - } - } - // } - - var result: [Changeset] = [] - - if deletes.deletedItems.count > 0 || deletes.deletedSections.count > 0 { - deletes.finalSections = [] - for (i, s) in initialSections.enumerate() { - if initialSectionInfos[i].event == .Deleted { - continue - } - - var items: [I] = [] - for (j, item) in s.items.enumerate() { - if initialItemInfos[i][j].event != .Deleted { - items.append(item) - } - } - deletes.finalSections.append(S(original: s, items: items)) - } - result.append(deletes) - } - - if newAndMovedSections.insertedSections.count > 0 || newAndMovedSections.movedSections.count > 0 || newAndMovedSections.updatedSections.count != 0 { - // sections should be in place, but items should be original without deleted ones - newAndMovedSections.finalSections = [] - for (i, s) in finalSections.enumerate() { - let event = finalSectionInfos[i].event - - if event == .Inserted { - // it's already set up - newAndMovedSections.finalSections.append(s) - } - else if event == .Moved || event == .MovedAutomatically { - let originalSectionIndex = initialSectionIndexes[s]! - let originalSection = initialSections[originalSectionIndex] - - var items: [I] = [] - for (j, item) in originalSection.items.enumerate() { - if initialItemInfos[originalSectionIndex][j].event != .Deleted { - items.append(item) - } - } - - newAndMovedSections.finalSections.append(S(original: s, items: items)) - } - else { - fatalError("This is weird, this shouldn't happen") - } - } - - result.append(newAndMovedSections) - } - - newAndMovedItems.finalSections = finalSections - result.append(newAndMovedItems) - - return result -} - diff --git a/RxExample/RxDataSourceStarterKit/RxDataSourceStarterKit.swift b/RxExample/RxDataSourceStarterKit/RxDataSourceStarterKit.swift deleted file mode 100644 index e55048d1..00000000 --- a/RxExample/RxDataSourceStarterKit/RxDataSourceStarterKit.swift +++ /dev/null @@ -1,10 +0,0 @@ -// -// RxDataSourceStarterKit.swift -// RxExample -// -// Created by Krunoslav Zaher on 8/29/15. -// Copyright © 2015 Krunoslav Zaher. All rights reserved. -// - -import Foundation - diff --git a/RxExample/RxDataSourceStarterKit/SectionModel.swift b/RxExample/RxDataSourceStarterKit/SectionModel.swift deleted file mode 100644 index 3a6ead4b..00000000 --- a/RxExample/RxDataSourceStarterKit/SectionModel.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// SectionModel.swift -// RxCocoa -// -// Created by Krunoslav Zaher on 6/16/15. -// Copyright © 2015 Krunoslav Zaher. All rights reserved. -// - -import Foundation - -public struct SectionModel : SectionModelType, CustomStringConvertible { - public typealias Item = ItemType - public var model: Section - - public var items: [Item] - - public init(model: Section, items: [Item]) { - self.model = model - self.items = items - } - - public init(original: SectionModel, items: [Item]) { - self.model = original.model - self.items = items - } - - public var description: String { - return "\(self.model) > \(items)" - } -} - -public struct HashableSectionModel : Hashable, SectionModelType, CustomStringConvertible { - public typealias Item = ItemType - public var model: Section - - public var items: [Item] - - public init(model: Section, items: [Item]) { - self.model = model - self.items = items - } - - public init(original: HashableSectionModel, items: [Item]) { - self.model = original.model - self.items = items - } - - public var description: String { - return "HashableSectionModel(model: \"\(self.model)\", items: \(items))" - } - - public var hashValue: Int { - return self.model.hashValue - } - -} - -public func == (lhs: HashableSectionModel, rhs: HashableSectionModel) -> Bool { - return lhs.model == rhs.model -} \ No newline at end of file diff --git a/RxExample/RxDataSourceStarterKit/ObservableConvertibleType+Differentiator.swift b/RxExample/RxDataSources/DataSources+Rx/ObservableConvertibleType+Differentiator.swift similarity index 84% rename from RxExample/RxDataSourceStarterKit/ObservableConvertibleType+Differentiator.swift rename to RxExample/RxDataSources/DataSources+Rx/ObservableConvertibleType+Differentiator.swift index f504719e..6e7c4e6e 100644 --- a/RxExample/RxDataSourceStarterKit/ObservableConvertibleType+Differentiator.swift +++ b/RxExample/RxDataSources/DataSources+Rx/ObservableConvertibleType+Differentiator.swift @@ -12,10 +12,10 @@ import Foundation import RxCocoa #endif -extension ObservableConvertibleType where E: SequenceType, E.Generator.Element : protocol, E.Generator.Element.Item: Hashable { +extension ObservableConvertibleType where E: SequenceType, E.Generator.Element : AnimatableSectionModelType { typealias Section = E.Generator.Element - func differentiateForSectionedView() + public func differentiateForSectionedView() -> Observable<[Changeset
]> { return self.asObservable().multicast({ @@ -31,9 +31,9 @@ extension ObservableConvertibleType where E: SequenceType, E.Generator.Element : do { return try differencesForSectionedView(Array(oldSections), finalSections: Array(newSections)) } - // in case of error, print it to terminal only + // in case of error, print it to terminal only because this is binding to UI Step catch let e { - print(e) + rxDebugFatalError(e) return [Changeset.initialValue(Array(newSections))] } } diff --git a/RxExample/RxDataSources/DataSources+Rx/RxCollectionViewSectionedAnimatedDataSource.swift b/RxExample/RxDataSources/DataSources+Rx/RxCollectionViewSectionedAnimatedDataSource.swift new file mode 100644 index 00000000..17eeee10 --- /dev/null +++ b/RxExample/RxDataSources/DataSources+Rx/RxCollectionViewSectionedAnimatedDataSource.swift @@ -0,0 +1,45 @@ +// +// RxCollectionViewSectionedAnimatedDataSource.swift +// RxExample +// +// Created by Krunoslav Zaher on 7/2/15. +// Copyright © 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import UIKit +#if !RX_NO_MODULE +import RxSwift +import RxCocoa +#endif + +public class RxCollectionViewSectionedAnimatedDataSource + : CollectionViewSectionedDataSource + , RxCollectionViewDataSourceType { + public typealias Element = [Changeset] + public var animationConfiguration: AnimationConfiguration? = nil + + // For some inexplicable reason, when doing animated updates first time + // it crashes. Still need to figure out that one. + var set = false + + public override init() { + super.init() + } + + public func collectionView(collectionView: UICollectionView, observedEvent: Event) { + UIBindingObserver(UIElement: self) { dataSource, element in + for c in element { + if !dataSource.set { + dataSource.setSections(c.finalSections) + collectionView.reloadData() + dataSource.set = true + return + } + dataSource.setSections(c.finalSections) + collectionView.performBatchUpdates(c, animationConfiguration: self.animationConfiguration) + } + + }.on(observedEvent) + } +} \ No newline at end of file diff --git a/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedReloadDataSource.swift b/RxExample/RxDataSources/DataSources+Rx/RxCollectionViewSectionedReloadDataSource.swift similarity index 56% rename from RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedReloadDataSource.swift rename to RxExample/RxDataSources/DataSources+Rx/RxCollectionViewSectionedReloadDataSource.swift index 92542cc3..9af39aac 100644 --- a/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedReloadDataSource.swift +++ b/RxExample/RxDataSources/DataSources+Rx/RxCollectionViewSectionedReloadDataSource.swift @@ -13,11 +13,17 @@ import RxSwift import RxCocoa #endif -class RxCollectionViewSectionedReloadDataSource : RxCollectionViewSectionedDataSource - , RxCollectionViewDataSourceType { - typealias Element = [S] +public class RxCollectionViewSectionedReloadDataSource + : CollectionViewSectionedDataSource + , RxCollectionViewDataSourceType { - func collectionView(collectionView: UICollectionView, observedEvent: Event) { + public typealias Element = [S] + + public override init() { + super.init() + } + + public func collectionView(collectionView: UICollectionView, observedEvent: Event) { UIBindingObserver(UIElement: self) { dataSource, element in dataSource.setSections(element) collectionView.reloadData() diff --git a/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedAnimatedDataSource.swift b/RxExample/RxDataSources/DataSources+Rx/RxTableViewSectionedAnimatedDataSource.swift similarity index 50% rename from RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedAnimatedDataSource.swift rename to RxExample/RxDataSources/DataSources+Rx/RxTableViewSectionedAnimatedDataSource.swift index 25e18d02..f41092c0 100644 --- a/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedAnimatedDataSource.swift +++ b/RxExample/RxDataSources/DataSources+Rx/RxTableViewSectionedAnimatedDataSource.swift @@ -13,23 +13,26 @@ import RxSwift import RxCocoa #endif -/** - Code for reactive data sources is packed in [RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources) project. - */ -class RxTableViewSectionedAnimatedDataSource : RxTableViewSectionedDataSource - , RxTableViewDataSourceType { - typealias Element = [Changeset] +public class RxTableViewSectionedAnimatedDataSource + : RxTableViewSectionedDataSource + , RxTableViewDataSourceType { - func tableView(tableView: UITableView, observedEvent: Event) { + public typealias Element = [Changeset] + public var animationConfiguration: AnimationConfiguration? = nil + + public override init() { + super.init() + } + + public func tableView(tableView: UITableView, observedEvent: Event) { UIBindingObserver(UIElement: self) { dataSource, element in for c in element { - //print("Animating ==============================\n\(c)\n===============================\n") dataSource.setSections(c.finalSections) if c.reloadData { tableView.reloadData() } else { - tableView.performBatchUpdates(c) + tableView.performBatchUpdates(c, animationConfiguration: self.animationConfiguration) } } }.on(observedEvent) diff --git a/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedReloadDataSource.swift b/RxExample/RxDataSources/DataSources+Rx/RxTableViewSectionedReloadDataSource.swift similarity index 50% rename from RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedReloadDataSource.swift rename to RxExample/RxDataSources/DataSources+Rx/RxTableViewSectionedReloadDataSource.swift index 5d9095db..c09e5b1a 100644 --- a/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedReloadDataSource.swift +++ b/RxExample/RxDataSources/DataSources+Rx/RxTableViewSectionedReloadDataSource.swift @@ -13,14 +13,16 @@ import RxSwift import RxCocoa #endif -/** - Code for reactive data sources is packed in [RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources) project. - */ -class RxTableViewSectionedReloadDataSource : RxTableViewSectionedDataSource - , RxTableViewDataSourceType { - typealias Element = [S] - - func tableView(tableView: UITableView, observedEvent: Event) { +public class RxTableViewSectionedReloadDataSource + : RxTableViewSectionedDataSource + , RxTableViewDataSourceType { + public typealias Element = [S] + + public override init() { + super.init() + } + + public func tableView(tableView: UITableView, observedEvent: Event) { UIBindingObserver(UIElement: self) { dataSource, element in dataSource.setSections(element) tableView.reloadData() diff --git a/RxExample/RxDataSourceStarterKit/UISectionedViewType+RxAnimatedDataSource.swift b/RxExample/RxDataSources/DataSources+Rx/UISectionedViewType+RxAnimatedDataSource.swift similarity index 77% rename from RxExample/RxDataSourceStarterKit/UISectionedViewType+RxAnimatedDataSource.swift rename to RxExample/RxDataSources/DataSources+Rx/UISectionedViewType+RxAnimatedDataSource.swift index 96fd5d6b..b4769879 100644 --- a/RxExample/RxDataSourceStarterKit/UISectionedViewType+RxAnimatedDataSource.swift +++ b/RxExample/RxDataSources/DataSources+Rx/UISectionedViewType+RxAnimatedDataSource.swift @@ -16,14 +16,11 @@ import RxCocoa extension UITableView { public func rx_itemsAnimatedWithDataSource< DataSource: protocol, - S: SequenceType, O: ObservableConvertibleType, - Section: protocol + Section: AnimatableSectionModelType where DataSource.Element == [Changeset
], - O.E == S, - S.Generator.Element == Section, - Section.Item: Hashable + O.E == [Section] > (dataSource: DataSource) (source: O) @@ -36,14 +33,11 @@ extension UITableView { extension UICollectionView { public func rx_itemsAnimatedWithDataSource< DataSource: protocol, - S: SequenceType, O: ObservableConvertibleType, - Section: protocol + Section: AnimatableSectionModelType where DataSource.Element == [Changeset
], - O.E == S, - S.Generator.Element == Section, - Section.Item: Hashable + O.E == [Section] > (dataSource: DataSource) (source: O) diff --git a/RxExample/RxDataSources/DataSources/AnimatableSectionModel.swift b/RxExample/RxDataSources/DataSources/AnimatableSectionModel.swift new file mode 100644 index 00000000..0a319ca1 --- /dev/null +++ b/RxExample/RxDataSources/DataSources/AnimatableSectionModel.swift @@ -0,0 +1,47 @@ +// +// AnimatableSectionModel.swift +// RxDataSources +// +// Created by Krunoslav Zaher on 1/10/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public struct AnimatableSectionModel + : Hashable + , AnimatableSectionModelType + , CustomStringConvertible { + public typealias Item = IdentitifiableValue + public typealias Identity = Section + + public var model: Section + + public var items: [Item] + + public var identity: Section { + return model + } + + public init(model: Section, items: [ItemType]) { + self.model = model + self.items = items.map(IdentitifiableValue.init) + } + + public init(original: AnimatableSectionModel, items: [Item]) { + self.model = original.model + self.items = items + } + + public var description: String { + return "HashableSectionModel(model: \"\(self.model)\", items: \(items))" + } + + public var hashValue: Int { + return self.model.hashValue + } +} + +public func == (lhs: AnimatableSectionModel, rhs: AnimatableSectionModel) -> Bool { + return lhs.model == rhs.model +} \ No newline at end of file diff --git a/RxExample/RxDataSources/DataSources/AnimatableSectionModelType+ItemPath.swift b/RxExample/RxDataSources/DataSources/AnimatableSectionModelType+ItemPath.swift new file mode 100644 index 00000000..8a2b8629 --- /dev/null +++ b/RxExample/RxDataSources/DataSources/AnimatableSectionModelType+ItemPath.swift @@ -0,0 +1,15 @@ +// +// AnimatableSectionModelType+ItemPath.swift +// RxDataSources +// +// Created by Krunoslav Zaher on 1/9/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +extension Array where Element: AnimatableSectionModelType { + subscript(index: ItemPath) -> Element.Item { + return self[index.sectionIndex].items[index.itemIndex] + } +} \ No newline at end of file diff --git a/RxExample/RxDataSources/DataSources/AnimatableSectionModelType.swift b/RxExample/RxDataSources/DataSources/AnimatableSectionModelType.swift new file mode 100644 index 00000000..aad0f405 --- /dev/null +++ b/RxExample/RxDataSources/DataSources/AnimatableSectionModelType.swift @@ -0,0 +1,17 @@ +// +// AnimatableSectionModelType.swift +// RxDataSources +// +// Created by Krunoslav Zaher on 1/6/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public protocol AnimatableSectionModelType + : SectionModelType + , IdentifiableType { + typealias Item : IdentifiableType, Equatable + + init(original: Self, items: [Item]) +} \ No newline at end of file diff --git a/RxExample/RxDataSources/DataSources/AnimationConfiguration.swift b/RxExample/RxDataSources/DataSources/AnimationConfiguration.swift new file mode 100644 index 00000000..58564316 --- /dev/null +++ b/RxExample/RxDataSources/DataSources/AnimationConfiguration.swift @@ -0,0 +1,27 @@ +// +// AnimationConfiguration.swift +// RxDataSources +// +// Created by Esteban Torres on 5/2/16. +// Copyright © 2016 kzaher. All rights reserved. +// + +import Foundation +import UIKit + +/** + Exposes custom animation styles for insertion, deletion and reloading behavior. +*/ +public struct AnimationConfiguration { + let insertAnimation: UITableViewRowAnimation + let reloadAnimation: UITableViewRowAnimation + let deleteAnimation: UITableViewRowAnimation + + public init(insertAnimation: UITableViewRowAnimation = .Automatic, + reloadAnimation: UITableViewRowAnimation = .Automatic, + deleteAnimation: UITableViewRowAnimation = .Automatic) { + self.insertAnimation = insertAnimation + self.reloadAnimation = reloadAnimation + self.deleteAnimation = deleteAnimation + } +} \ No newline at end of file diff --git a/RxExample/RxDataSources/DataSources/Changeset.swift b/RxExample/RxDataSources/DataSources/Changeset.swift new file mode 100644 index 00000000..76f6c420 --- /dev/null +++ b/RxExample/RxDataSources/DataSources/Changeset.swift @@ -0,0 +1,94 @@ +// +// Changeset.swift +// RxDataSources +// +// Created by Krunoslav Zaher on 5/30/15. +// Copyright © 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation +import CoreData +#if !RX_NO_MODULE +import RxSwift +import RxCocoa +#endif + +public struct Changeset { + public typealias I = S.Item + + public let reloadData: Bool + + public let finalSections: [S] + + public let insertedSections: [Int] + public let deletedSections: [Int] + public let movedSections: [(from: Int, to: Int)] + public let updatedSections: [Int] + + public let insertedItems: [ItemPath] + public let deletedItems: [ItemPath] + public let movedItems: [(from: ItemPath, to: ItemPath)] + public let updatedItems: [ItemPath] + + init(reloadData: Bool = false, + finalSections: [S] = [], + insertedSections: [Int] = [], + deletedSections: [Int] = [], + movedSections: [(from: Int, to: Int)] = [], + updatedSections: [Int] = [], + + insertedItems: [ItemPath] = [], + deletedItems: [ItemPath] = [], + movedItems: [(from: ItemPath, to: ItemPath)] = [], + updatedItems: [ItemPath] = [] + ) { + self.reloadData = reloadData + + self.finalSections = finalSections + + self.insertedSections = insertedSections + self.deletedSections = deletedSections + self.movedSections = movedSections + self.updatedSections = updatedSections + + self.insertedItems = insertedItems + self.deletedItems = deletedItems + self.movedItems = movedItems + self.updatedItems = updatedItems + } + + public static func initialValue(sections: [S]) -> Changeset { + return Changeset( + insertedSections: Array(0 ..< sections.count) as [Int], + finalSections: sections, + reloadData: true + ) + } +} + +extension ItemPath + : CustomDebugStringConvertible { + public var debugDescription : String { + return "(\(sectionIndex), \(itemIndex))" + } +} + +extension Changeset + : CustomDebugStringConvertible { + + public var debugDescription : String { + let serializedSections = "[\n" + finalSections.map { "\($0)" }.joinWithSeparator(",\n") + "\n]\n" + return " >> Final sections" + + " \n\(serializedSections)" + + (insertedSections.count > 0 || deletedSections.count > 0 || movedSections.count > 0 || updatedSections.count > 0 ? "\nSections:" : "") + + (insertedSections.count > 0 ? "\ninsertedSections:\n\t\(insertedSections)" : "") + + (deletedSections.count > 0 ? "\ndeletedSections:\n\t\(deletedSections)" : "") + + (movedSections.count > 0 ? "\nmovedSections:\n\t\(movedSections)" : "") + + (updatedSections.count > 0 ? "\nupdatesSections:\n\t\(updatedSections)" : "") + + (insertedItems.count > 0 || deletedItems.count > 0 || movedItems.count > 0 || updatedItems.count > 0 ? "\nItems:" : "") + + (insertedItems.count > 0 ? "\ninsertedItems:\n\t\(insertedItems)" : "") + + (deletedItems.count > 0 ? "\ndeletedItems:\n\t\(deletedItems)" : "") + + (movedItems.count > 0 ? "\nmovedItems:\n\t\(movedItems)" : "") + + (updatedItems.count > 0 ? "\nupdatedItems:\n\t\(updatedItems)" : "") + } +} diff --git a/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedDataSource.swift b/RxExample/RxDataSources/DataSources/CollectionViewSectionedDataSource.swift similarity index 58% rename from RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedDataSource.swift rename to RxExample/RxDataSources/DataSources/CollectionViewSectionedDataSource.swift index 97e7a2a9..5cf2fa97 100644 --- a/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedDataSource.swift +++ b/RxExample/RxDataSources/DataSources/CollectionViewSectionedDataSource.swift @@ -1,6 +1,6 @@ // -// RxCollectionViewSectionedDataSource.swift -// RxExample +// CollectionViewSectionedDataSource.swift +// RxDataSources // // Created by Krunoslav Zaher on 7/2/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. @@ -8,13 +8,11 @@ import Foundation import UIKit -#if !RX_NO_MODULE -import RxSwift import RxCocoa -#endif -public class _RxCollectionViewSectionedDataSource : NSObject - , UICollectionViewDataSource { +public class _CollectionViewSectionedDataSource + : NSObject + , UICollectionViewDataSource { func _numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { return 0 @@ -47,17 +45,31 @@ public class _RxCollectionViewSectionedDataSource : NSObject public func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { return _collectionView(collectionView, viewForSupplementaryElementOfKind: kind, atIndexPath: indexPath) } + + func _collectionView(collectionView: UICollectionView, canMoveItemAtIndexPath indexPath: NSIndexPath) -> Bool { + return false + } + + public func collectionView(collectionView: UICollectionView, canMoveItemAtIndexPath indexPath: NSIndexPath) -> Bool { + return _collectionView(collectionView, canMoveItemAtIndexPath: indexPath) + } + + func _collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) { + + } + public func collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) { + _collectionView(collectionView, moveItemAtIndexPath: sourceIndexPath, toIndexPath: destinationIndexPath) + } + } -public class RxCollectionViewSectionedDataSource : _RxCollectionViewSectionedDataSource { +public class CollectionViewSectionedDataSource + : _CollectionViewSectionedDataSource + , SectionedViewDataSourceType { public typealias I = S.Item public typealias Section = S - public typealias CellFactory = (UICollectionView, NSIndexPath, I) -> UICollectionViewCell - public typealias SupplementaryViewFactory = (UICollectionView, String, NSIndexPath) -> UICollectionReusableView - - public typealias IncrementalUpdateObserver = AnyObserver> - - public typealias IncrementalUpdateDisposeKey = Bag.KeyType + public typealias CellFactory = (CollectionViewSectionedDataSource, UICollectionView, NSIndexPath, I) -> UICollectionViewCell + public typealias SupplementaryViewFactory = (CollectionViewSectionedDataSource, UICollectionView, String, NSIndexPath) -> UICollectionReusableView // This structure exists because model can be mutable // In that case current state value should be preserved. @@ -72,11 +84,15 @@ public class RxCollectionViewSectionedDataSource : _RxColle public func sectionAtIndex(section: Int) -> S { return self._sectionModels[section].model } - + public func itemAtIndexPath(indexPath: NSIndexPath) -> I { return self._sectionModels[indexPath.section].items[indexPath.item] } + public func modelAtIndexPath(indexPath: NSIndexPath) throws -> Any { + return itemAtIndexPath(indexPath) + } + public func setSections(sections: [S]) { self._sectionModels = sections.map { SectionModelSnapshot(model: $0, items: $0.items) } } @@ -84,9 +100,12 @@ public class RxCollectionViewSectionedDataSource : _RxColle public var cellFactory: CellFactory! = nil public var supplementaryViewFactory: SupplementaryViewFactory + public var moveItem: ((CollectionViewSectionedDataSource, sourceIndexPath:NSIndexPath, destinationIndexPath:NSIndexPath) -> Void)? + public var canMoveItemAtIndexPath: ((CollectionViewSectionedDataSource, indexPath:NSIndexPath) -> Bool)? + public override init() { - self.cellFactory = { _, _, _ in return (nil as UICollectionViewCell?)! } - self.supplementaryViewFactory = { _, _, _ in (nil as UICollectionReusableView?)! } + self.cellFactory = {_, _, _, _ in return (nil as UICollectionViewCell?)! } + self.supplementaryViewFactory = {_, _, _, _ in (nil as UICollectionReusableView?)! } super.init() @@ -96,13 +115,13 @@ public class RxCollectionViewSectionedDataSource : _RxColle return (nil as UICollectionViewCell!)! } - self.supplementaryViewFactory = { [weak self] _, _, _ in + self.supplementaryViewFactory = { [weak self] _ in precondition(false, "There is a minor problem. `supplementaryViewFactory` property on \(self!) was not set.") return (nil as UICollectionReusableView?)! } } - // UITableViewDataSource + // UICollectionViewDataSource override func _numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { return _sectionModels.count @@ -115,10 +134,21 @@ public class RxCollectionViewSectionedDataSource : _RxColle override func _collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { precondition(indexPath.item < _sectionModels[indexPath.section].items.count) - return cellFactory(collectionView, indexPath, itemAtIndexPath(indexPath)) + return cellFactory(self, collectionView, indexPath, itemAtIndexPath(indexPath)) } override func _collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { - return supplementaryViewFactory(collectionView, kind, indexPath) + return supplementaryViewFactory(self, collectionView, kind, indexPath) } + + override func _collectionView(collectionView: UICollectionView, canMoveItemAtIndexPath indexPath: NSIndexPath) -> Bool { + return canMoveItemAtIndexPath?(self, indexPath: indexPath) ?? + super._collectionView(collectionView, canMoveItemAtIndexPath: indexPath) + } + + override func _collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) { + return moveItem?(self, sourceIndexPath:sourceIndexPath, destinationIndexPath: destinationIndexPath) ?? + super._collectionView(collectionView, moveItemAtIndexPath: sourceIndexPath, toIndexPath: destinationIndexPath) + } + } \ No newline at end of file diff --git a/RxExample/RxDataSources/DataSources/DataSources.swift b/RxExample/RxDataSources/DataSources/DataSources.swift new file mode 100644 index 00000000..64327b5e --- /dev/null +++ b/RxExample/RxDataSources/DataSources/DataSources.swift @@ -0,0 +1,35 @@ +// +// DataSources.swift +// RxDataSources +// +// Created by Krunoslav Zaher on 1/8/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +enum RxDataSourceError : ErrorType { + case UnwrappingOptional + case PreconditionFailed(message: String) +} + +func rxPrecondition(condition: Bool, @autoclosure _ message: () -> String) throws -> () { + if condition { + return + } + rxDebugFatalError("Precondition failed") + + throw RxDataSourceError.PreconditionFailed(message: message()) +} + +func rxDebugFatalError(error: ErrorType) { + rxDebugFatalError("\(error)") +} + +func rxDebugFatalError(message: String) { + #if DEBUG + fatalError(message) + #else + print(message) + #endif +} \ No newline at end of file diff --git a/RxExample/RxDataSources/DataSources/Differentiator.swift b/RxExample/RxDataSources/DataSources/Differentiator.swift new file mode 100644 index 00000000..e056d6bb --- /dev/null +++ b/RxExample/RxDataSources/DataSources/Differentiator.swift @@ -0,0 +1,685 @@ +// +// Differentiator.swift +// RxDataSources +// +// Created by Krunoslav Zaher on 6/27/15. +// Copyright © 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public enum DifferentiatorError + : ErrorType + , CustomDebugStringConvertible { + case DuplicateItem(item: Any) + case DuplicateSection(section: Any) +} + +extension DifferentiatorError { + public var debugDescription: String { + switch self { + case let .DuplicateItem(item): + return "Duplicate item \(item)" + case let .DuplicateSection(section): + return "Duplicate section \(section)" + } + } +} + +enum EditEvent : CustomDebugStringConvertible { + case Inserted // can't be found in old sections + case InsertedAutomatically // Item inside section being inserted + case Deleted // Was in old, not in new, in it's place is something "not new" :(, otherwise it's Updated + case DeletedAutomatically // Item inside section that is being deleted + case Moved // same item, but was on different index, and needs explicit move + case MovedAutomatically // don't need to specify any changes for those rows + case Untouched +} + +extension EditEvent { + var debugDescription: String { + get { + switch self { + case .Inserted: + return "Inserted" + case .InsertedAutomatically: + return "InsertedAutomatically" + case .Deleted: + return "Deleted" + case .DeletedAutomatically: + return "DeletedAutomatically" + case .Moved: + return "Moved" + case .MovedAutomatically: + return "MovedAutomatically" + case .Untouched: + return "Untouched" + } + } + } +} + +struct SectionAssociatedData { + var event: EditEvent + var indexAfterDelete: Int? + var moveIndex: Int? +} + +extension SectionAssociatedData : CustomDebugStringConvertible { + var debugDescription: String { + get { + return "\(event), \(indexAfterDelete)" + } + } +} + +extension SectionAssociatedData { + static var initial: SectionAssociatedData { + return SectionAssociatedData(event: .Untouched, indexAfterDelete: nil, moveIndex: nil) + } +} + +struct ItemAssociatedData { + var event: EditEvent + var indexAfterDelete: Int? + var moveIndex: ItemPath? +} + +extension ItemAssociatedData : CustomDebugStringConvertible { + var debugDescription: String { + get { + return "\(event) \(indexAfterDelete)" + } + } +} + +extension ItemAssociatedData { + static var initial : ItemAssociatedData { + return ItemAssociatedData(event: .Untouched, indexAfterDelete: nil, moveIndex: nil) + } +} + +func indexSections(sections: [S]) throws -> [S.Identity : Int] { + var indexedSections: [S.Identity : Int] = [:] + for (i, section) in sections.enumerate() { + guard indexedSections[section.identity] == nil else { + #if DEBUG + precondition(indexedSections[section.identity] == nil, "Section \(section) has already been indexed at \(indexedSections[section]!)") + #endif + throw DifferentiatorError.DuplicateItem(item: section) + } + indexedSections[section.identity] = i + } + + return indexedSections +} + +func indexSectionItems(sections: [S]) throws -> [S.Item.Identity : (Int, Int)] { + var totalItems = 0 + for i in 0 ..< sections.count { + totalItems += sections[i].items.count + } + + // let's make sure it's enough + var indexedItems: [S.Item.Identity : (Int, Int)] = Dictionary(minimumCapacity: totalItems * 3) + + for i in 0 ..< sections.count { + for (j, item) in sections[i].items.enumerate() { + guard indexedItems[item.identity] == nil else { + #if DEBUG + precondition(indexedItems[item.identity] == nil, "Item \(item) has already been indexed at \(indexedItems[item]!)" ) + #endif + throw DifferentiatorError.DuplicateItem(item: item) + } + indexedItems[item.identity] = (i, j) + } + } + + return indexedItems +} + + +/* + +I've uncovered this case during random stress testing of logic. +This is the hardest generic update case that causes two passes, first delete, and then move/insert + +[ +NumberSection(model: "1", items: [1111]), +NumberSection(model: "2", items: [2222]), +] + +[ +NumberSection(model: "2", items: [0]), +NumberSection(model: "1", items: []), +] + +If update is in the form + +* Move section from 2 to 1 +* Delete Items at paths 0 - 0, 1 - 0 +* Insert Items at paths 0 - 0 + +or + +* Move section from 2 to 1 +* Delete Items at paths 0 - 0 +* Reload Items at paths 1 - 0 + +or + +* Move section from 2 to 1 +* Delete Items at paths 0 - 0 +* Reload Items at paths 0 - 0 + +it crashes table view. + +No matter what change is performed, it fails for me. +If anyone knows how to make this work for one Changeset, PR is welcome. + +*/ + +// If you are considering working out your own algorithm, these are tricky +// transition cases that you can use. + +// case 1 +/* +from = [ + NumberSection(model: "section 4", items: [10, 11, 12]), + NumberSection(model: "section 9", items: [25, 26, 27]), +] +to = [ + HashableSectionModel(model: "section 9", items: [11, 26, 27]), + HashableSectionModel(model: "section 4", items: [10, 12]) +] +*/ + +// case 2 +/* +from = [ + HashableSectionModel(model: "section 10", items: [26]), + HashableSectionModel(model: "section 7", items: [5, 29]), + HashableSectionModel(model: "section 1", items: [14]), + HashableSectionModel(model: "section 5", items: [16]), + HashableSectionModel(model: "section 4", items: []), + HashableSectionModel(model: "section 8", items: [3, 15, 19, 23]), + HashableSectionModel(model: "section 3", items: [20]) +] +to = [ + HashableSectionModel(model: "section 10", items: [26]), + HashableSectionModel(model: "section 1", items: [14]), + HashableSectionModel(model: "section 9", items: [3]), + HashableSectionModel(model: "section 5", items: [16, 8]), + HashableSectionModel(model: "section 8", items: [15, 19, 23]), + HashableSectionModel(model: "section 3", items: [20]), + HashableSectionModel(model: "Section 2", items: [7]) +] +*/ + +// case 3 +/* +from = [ + HashableSectionModel(model: "section 4", items: [5]), + HashableSectionModel(model: "section 6", items: [20, 14]), + HashableSectionModel(model: "section 9", items: []), + HashableSectionModel(model: "section 2", items: [2, 26]), + HashableSectionModel(model: "section 8", items: [23]), + HashableSectionModel(model: "section 10", items: [8, 18, 13]), + HashableSectionModel(model: "section 1", items: [28, 25, 6, 11, 10, 29, 24, 7, 19]) +] +to = [ + HashableSectionModel(model: "section 4", items: [5]), + HashableSectionModel(model: "section 6", items: [20, 14]), + HashableSectionModel(model: "section 9", items: [16]), + HashableSectionModel(model: "section 7", items: [17, 15, 4]), + HashableSectionModel(model: "section 2", items: [2, 26, 23]), + HashableSectionModel(model: "section 8", items: []), + HashableSectionModel(model: "section 10", items: [8, 18, 13]), + HashableSectionModel(model: "section 1", items: [28, 25, 6, 11, 10, 29, 24, 7, 19]) +] +*/ + +// Generates differential changes suitable for sectioned view consumption. +// It will not only detect changes between two states, but it will also try to compress those changes into +// almost minimal set of changes. +// +// I know, I know, it's ugly :( Totally agree, but this is the only general way I could find that works 100%, and +// avoids UITableView quirks. +// +// Please take into consideration that I was also convinced about 20 times that I've found a simple general +// solution, but then UITableView falls apart under stress testing :( +// +// Sincerely, if somebody else would present me this 250 lines of code, I would call him a mad man. I would think +// that there has to be a simpler solution. Well, after 3 days, I'm not convinced any more :) +// +// Maybe it can be made somewhat simpler, but don't think it can be made much simpler. +// +// The algorithm could take anywhere from 1 to 3 table view transactions to finish the updates. +// +// * stage 1 - remove deleted sections and items +// * stage 2 - move sections into place +// * stage 3 - fix moved and new items +// +// There maybe exists a better division, but time will tell. +// +public func differencesForSectionedView( + initialSections: [S], + finalSections: [S] + ) + throws -> [Changeset] { + typealias I = S.Item + + var result: [Changeset] = [] + + var sectionCommands = try CommandGenerator.generatorForInitialSections(initialSections, finalSections: finalSections) + + result.appendContentsOf(try sectionCommands.generateDeleteSections()) + result.appendContentsOf(try sectionCommands.generateInsertAndMoveSections()) + result.appendContentsOf(try sectionCommands.generateNewAndMovedItems()) + + return result +} + +struct CommandGenerator { + let initialSections: [S] + let finalSections: [S] + + let initialSectionData: [SectionAssociatedData] + let finalSectionData: [SectionAssociatedData] + + let initialItemData: [[ItemAssociatedData]] + let finalItemData: [[ItemAssociatedData]] + + static func generatorForInitialSections( + initialSections: [S], + finalSections: [S] + ) throws -> CommandGenerator { + + let (initialSectionData, finalSectionData) = try calculateSectionMovementsForInitialSections(initialSections, finalSections: finalSections) + let (initialItemData, finalItemData) = try calculateItemMovementsForInitialSections(initialSections, + finalSections: finalSections, + initialSectionData: initialSectionData, + finalSectionData: finalSectionData + ) + + return CommandGenerator( + initialSections: initialSections, + finalSections: finalSections, + + initialSectionData: initialSectionData, + finalSectionData: finalSectionData, + + initialItemData: initialItemData, + finalItemData: finalItemData + ) + } + + static func calculateItemMovementsForInitialSections(initialSections: [S], finalSections: [S], + initialSectionData: [SectionAssociatedData], finalSectionData: [SectionAssociatedData]) throws -> ([[ItemAssociatedData]], [[ItemAssociatedData]]) { + var initialItemData = initialSections.map { s in + return [ItemAssociatedData](count: s.items.count, repeatedValue: ItemAssociatedData.initial) + } + + var finalItemData = finalSections.map { s in + return [ItemAssociatedData](count: s.items.count, repeatedValue: ItemAssociatedData.initial) + } + + let initialItemIndexes = try indexSectionItems(initialSections) + + for i in 0 ..< finalSections.count { + for (j, item) in finalSections[i].items.enumerate() { + guard let initialItemIndex = initialItemIndexes[item.identity] else { + continue + } + if initialItemData[initialItemIndex.0][initialItemIndex.1].moveIndex != nil { + throw DifferentiatorError.DuplicateItem(item: item) + } + + initialItemData[initialItemIndex.0][initialItemIndex.1].moveIndex = ItemPath(sectionIndex: i, itemIndex: j) + finalItemData[i][j].moveIndex = ItemPath(sectionIndex: initialItemIndex.0, itemIndex: initialItemIndex.1) + } + } + + let findNextUntouchedOldIndex = { (initialSectionIndex: Int, initialSearchIndex: Int?) -> Int? in + guard var i2 = initialSearchIndex else { + return nil + } + + while i2 < initialSections[initialSectionIndex].items.count { + if initialItemData[initialSectionIndex][i2].event == .Untouched { + return i2 + } + + i2 = i2 + 1 + } + + return nil + } + + // first mark deleted items + for i in 0 ..< initialSections.count { + guard let _ = initialSectionData[i].moveIndex else { + continue + } + + var indexAfterDelete = 0 + for j in 0 ..< initialSections[i].items.count { + + guard let finalIndexPath = initialItemData[i][j].moveIndex else { + initialItemData[i][j].event = .Deleted + continue + } + + // from this point below, section has to be move type because it's initial and not deleted + + // because there is no move to inserted section + if finalSectionData[finalIndexPath.sectionIndex].event == .Inserted { + initialItemData[i][j].event = .Deleted + continue + } + + initialItemData[i][j].indexAfterDelete = indexAfterDelete + indexAfterDelete += 1 + } + } + + // mark moved or moved automatically + for i in 0 ..< finalSections.count { + guard let originalSectionIndex = finalSectionData[i].moveIndex else { + continue + } + + var untouchedIndex: Int? = 0 + for j in 0 ..< finalSections[i].items.count { + untouchedIndex = findNextUntouchedOldIndex(originalSectionIndex, untouchedIndex) + + guard let originalIndex = finalItemData[i][j].moveIndex else { + finalItemData[i][j].event = .Inserted + continue + } + + // In case trying to move from deleted section, abort, otherwise it will crash table view + if initialSectionData[originalIndex.sectionIndex].event == .Deleted { + finalItemData[i][j].event = .Inserted + continue + } + // original section can't be inserted + else if initialSectionData[originalIndex.sectionIndex].event == .Inserted { + try rxPrecondition(false, "New section in initial sections, that is wrong") + } + + let initialSectionEvent = initialSectionData[originalIndex.sectionIndex].event + try rxPrecondition(initialSectionEvent == .Moved || initialSectionEvent == .MovedAutomatically, "Section not moved") + + let eventType = originalIndex == ItemPath(sectionIndex: originalSectionIndex, itemIndex: untouchedIndex ?? -1) + ? EditEvent.MovedAutomatically : EditEvent.Moved + + initialItemData[originalIndex.sectionIndex][originalIndex.itemIndex].event = eventType + finalItemData[i][j].event = eventType + + } + } + + return (initialItemData, finalItemData) + } + + static func calculateSectionMovementsForInitialSections(initialSections: [S], finalSections: [S]) throws -> ([SectionAssociatedData], [SectionAssociatedData]) { + + let initialSectionIndexes = try indexSections(initialSections) + + var initialSectionData = [SectionAssociatedData](count: initialSections.count, repeatedValue: SectionAssociatedData.initial) + var finalSectionData = [SectionAssociatedData](count: finalSections.count, repeatedValue: SectionAssociatedData.initial) + + for (i, section) in finalSections.enumerate() { + guard let initialSectionIndex = initialSectionIndexes[section.identity] else { + continue + } + + if initialSectionData[initialSectionIndex].moveIndex != nil { + throw DifferentiatorError.DuplicateSection(section: section) + } + + initialSectionData[initialSectionIndex].moveIndex = i + finalSectionData[i].moveIndex = initialSectionIndex + } + + var sectionIndexAfterDelete = 0 + + // deleted sections + for i in 0 ..< initialSectionData.count { + if initialSectionData[i].moveIndex == nil { + initialSectionData[i].event = .Deleted + continue + } + + initialSectionData[i].indexAfterDelete = sectionIndexAfterDelete + sectionIndexAfterDelete += 1 + } + + // moved sections + + var untouchedOldIndex: Int? = 0 + let findNextUntouchedOldIndex = { (initialSearchIndex: Int?) -> Int? in + guard var i = initialSearchIndex else { + return nil + } + + while i < initialSections.count { + if initialSectionData[i].event == .Untouched { + return i + } + + i = i + 1 + } + + return nil + } + + // inserted and moved sections { + // this should fix all sections and move them into correct places + // 2nd stage + for i in 0 ..< finalSections.count { + untouchedOldIndex = findNextUntouchedOldIndex(untouchedOldIndex) + + // oh, it did exist + if let oldSectionIndex = finalSectionData[i].moveIndex { + let moveType = oldSectionIndex != untouchedOldIndex ? EditEvent.Moved : EditEvent.MovedAutomatically + + finalSectionData[i].event = moveType + initialSectionData[oldSectionIndex].event = moveType + } + else { + finalSectionData[i].event = .Inserted + } + } + + // inserted sections + for (i, section) in finalSectionData.enumerate() { + if section.moveIndex == nil { + finalSectionData[i].event == .Inserted + } + } + + return (initialSectionData, finalSectionData) + } + + mutating func generateDeleteSections() throws -> [Changeset] { + var deletedSections = [Int]() + var deletedItems = [ItemPath]() + var updatedItems = [ItemPath]() + + var afterDeleteState = [S]() + + // mark deleted items { + // 1rst stage again (I know, I know ...) + for (i, initialSection) in initialSections.enumerate() { + let event = initialSectionData[i].event + + // Deleted section will take care of deleting child items. + // In case of moving an item from deleted section, tableview will + // crash anyway, so this is not limiting anything. + if event == .Deleted { + deletedSections.append(i) + continue + } + + var afterDeleteItems: [S.Item] = [] + for j in 0 ..< initialSection.items.count { + let event = initialItemData[i][j].event + switch event { + case .Deleted: + deletedItems.append(ItemPath(sectionIndex: i, itemIndex: j)) + case .Moved, .MovedAutomatically: + let finalItemIndex = try initialItemData[i][j].moveIndex.unwrap() + let finalItem = finalSections[finalItemIndex] + if finalItem != initialSections[i].items[j] { + updatedItems.append(ItemPath(sectionIndex: i, itemIndex: j)) + } + afterDeleteItems.append(finalItem) + default: + try rxPrecondition(false, "Unhandled case") + } + } + + afterDeleteState.append(S(original: initialSection, items: afterDeleteItems)) + } + // } + + if deletedItems.count == 0 && deletedSections.count == 0 && updatedItems.count == 0 { + return [] + } + + return [Changeset( + finalSections: afterDeleteState, + deletedSections: deletedSections, + deletedItems: deletedItems, + updatedItems: updatedItems + )] + } + + func generateInsertAndMoveSections() throws -> [Changeset] { + + var movedSections = [(from: Int, to: Int)]() + var insertedSections = [Int]() + + for i in 0 ..< initialSections.count { + switch initialSectionData[i].event { + case .Deleted: + break + case .Moved: + movedSections.append((from: try initialSectionData[i].indexAfterDelete.unwrap(), to: try initialSectionData[i].moveIndex.unwrap())) + case .MovedAutomatically: + break + default: + try rxPrecondition(false, "Unhandled case in initial sections") + } + } + + for i in 0 ..< finalSections.count { + switch finalSectionData[i].event { + case .Inserted: + insertedSections.append(i) + default: + break + } + } + + if insertedSections.count == 0 && movedSections.count == 0 { + return [] + } + + // sections should be in place, but items should be original without deleted ones + let sectionsAfterChange: [S] = try self.finalSections.enumerate().map { i, s -> S in + let event = self.finalSectionData[i].event + + if event == .Inserted { + // it's already set up + return s + } + else if event == .Moved || event == .MovedAutomatically { + let originalSectionIndex = try finalSectionData[i].moveIndex.unwrap() + let originalSection = initialSections[originalSectionIndex] + + var items: [S.Item] = [] + for (j, _) in originalSection.items.enumerate() { + let initialData = self.initialItemData[originalSectionIndex][j] + + guard initialData.event != .Deleted else { + continue + } + + guard let finalIndex = initialData.moveIndex else { + try rxPrecondition(false, "Item was moved, but no final location.") + continue + } + + items.append(self.finalSections[finalIndex.sectionIndex].items[finalIndex.itemIndex]) + } + + return S(original: s, items: items) + } + else { + try rxPrecondition(false, "This is weird, this shouldn't happen") + return s + } + } + + return [Changeset( + finalSections: sectionsAfterChange, + insertedSections: insertedSections, + movedSections: movedSections + )] + } + + mutating func generateNewAndMovedItems() throws -> [Changeset] { + var insertedItems = [ItemPath]() + var movedItems = [(from: ItemPath, to: ItemPath)]() + + // mark new and moved items { + // 3rd stage + for i in 0 ..< finalSections.count { + let finalSection = finalSections[i] + + let sectionEvent = finalSectionData[i].event + // new and deleted sections cause reload automatically + if sectionEvent != .Moved && sectionEvent != .MovedAutomatically { + continue + } + + for j in 0 ..< finalSection.items.count { + let currentItemEvent = finalItemData[i][j].event + + try rxPrecondition(currentItemEvent != .Untouched, "Current event is not untouched") + + let event = finalItemData[i][j].event + + switch event { + case .Inserted: + insertedItems.append(ItemPath(sectionIndex: i, itemIndex: j)) + case .Moved: + let originalIndex = try finalItemData[i][j].moveIndex.unwrap() + let finalSectionIndex = try initialSectionData[originalIndex.sectionIndex].moveIndex.unwrap() + let moveFromItemWithIndex = try initialItemData[originalIndex.sectionIndex][originalIndex.itemIndex].indexAfterDelete.unwrap() + + let moveCommand = ( + from: ItemPath(sectionIndex: finalSectionIndex, itemIndex: moveFromItemWithIndex), + to: ItemPath(sectionIndex: i, itemIndex: j) + ) + movedItems.append(moveCommand) + default: + break + } + } + } + // } + + if insertedItems.count == 0 && movedItems.count == 0 { + return [] + } + return [Changeset( + finalSections: finalSections, + insertedItems: insertedItems, + movedItems: movedItems + )] + } +} \ No newline at end of file diff --git a/RxExample/RxDataSources/DataSources/IdentifiableType.swift b/RxExample/RxDataSources/DataSources/IdentifiableType.swift new file mode 100644 index 00000000..b5efe3aa --- /dev/null +++ b/RxExample/RxDataSources/DataSources/IdentifiableType.swift @@ -0,0 +1,15 @@ +// +// IdentifiableType.swift +// RxDataSources +// +// Created by Krunoslav Zaher on 1/6/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public protocol IdentifiableType { + typealias Identity: Hashable + + var identity : Identity { get } +} \ No newline at end of file diff --git a/RxExample/RxDataSources/DataSources/IdentifiableValue.swift b/RxExample/RxDataSources/DataSources/IdentifiableValue.swift new file mode 100644 index 00000000..ded89cc0 --- /dev/null +++ b/RxExample/RxDataSources/DataSources/IdentifiableValue.swift @@ -0,0 +1,35 @@ +// +// IdentifiableValue.swift +// RxDataSources +// +// Created by Krunoslav Zaher on 1/7/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public struct IdentitifiableValue + : IdentifiableType + , Equatable + , CustomStringConvertible + , CustomDebugStringConvertible { + public typealias Identity = Value + + public let value: Value + + public var identity : Identity { + return value + } + + public var description: String { + return "\(value)" + } + + public var debugDescription: String { + return "\(value)" + } +} + +public func == (lhs: IdentitifiableValue, rhs: IdentitifiableValue) -> Bool { + return lhs.value == rhs.value +} \ No newline at end of file diff --git a/RxExample/RxDataSources/DataSources/ItemPath.swift b/RxExample/RxDataSources/DataSources/ItemPath.swift new file mode 100644 index 00000000..5f073633 --- /dev/null +++ b/RxExample/RxDataSources/DataSources/ItemPath.swift @@ -0,0 +1,22 @@ +// +// ItemPath.swift +// RxDataSources +// +// Created by Krunoslav Zaher on 1/9/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public struct ItemPath { + public let sectionIndex: Int + public let itemIndex: Int +} + +extension ItemPath : Equatable { + +} + +public func == (lhs: ItemPath, rhs: ItemPath) -> Bool { + return lhs.sectionIndex == rhs.sectionIndex && lhs.itemIndex == rhs.itemIndex +} \ No newline at end of file diff --git a/RxExample/RxDataSources/DataSources/Optional+Extensions.swift b/RxExample/RxDataSources/DataSources/Optional+Extensions.swift new file mode 100644 index 00000000..a8d5763c --- /dev/null +++ b/RxExample/RxDataSources/DataSources/Optional+Extensions.swift @@ -0,0 +1,21 @@ +// +// Optional+Extensions.swift +// RxDataSources +// +// Created by Krunoslav Zaher on 1/8/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +extension Optional { + func unwrap() throws -> Wrapped { + if let unwrapped = self { + return unwrapped + } + else { + rxDebugFatalError("Error during unwrapping optional") + throw RxDataSourceError.UnwrappingOptional + } + } +} \ No newline at end of file diff --git a/RxExample/RxDataSources/DataSources/SectionModel.swift b/RxExample/RxDataSources/DataSources/SectionModel.swift new file mode 100644 index 00000000..b3e55209 --- /dev/null +++ b/RxExample/RxDataSources/DataSources/SectionModel.swift @@ -0,0 +1,33 @@ +// +// SectionModel.swift +// RxDataSources +// +// Created by Krunoslav Zaher on 6/16/15. +// Copyright © 2015 Krunoslav Zaher. All rights reserved. +// + +import Foundation + +public struct SectionModel + : SectionModelType + , CustomStringConvertible { + public typealias Identity = Section + public typealias Item = ItemType + public var model: Section + + public var identity: Section { + return model + } + + public var items: [Item] + + public init(model: Section, items: [Item]) { + self.model = model + self.items = items + } + + public var description: String { + return "\(self.model) > \(items)" + } +} + diff --git a/RxExample/RxDataSourceStarterKit/SectionModelType.swift b/RxExample/RxDataSources/DataSources/SectionModelType.swift similarity index 78% rename from RxExample/RxDataSourceStarterKit/SectionModelType.swift rename to RxExample/RxDataSources/DataSources/SectionModelType.swift index f6bf8489..4d40e1c7 100644 --- a/RxExample/RxDataSourceStarterKit/SectionModelType.swift +++ b/RxExample/RxDataSources/DataSources/SectionModelType.swift @@ -1,6 +1,6 @@ // // SectionModelType.swift -// RxExample +// RxDataSources // // Created by Krunoslav Zaher on 6/28/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. @@ -10,8 +10,6 @@ import Foundation public protocol SectionModelType { typealias Item - + var items: [Item] { get } - - init(original: Self, items: [Item]) } \ No newline at end of file diff --git a/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedDataSource.swift b/RxExample/RxDataSources/DataSources/TableViewSectionedDataSource.swift similarity index 50% rename from RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedDataSource.swift rename to RxExample/RxDataSources/DataSources/TableViewSectionedDataSource.swift index 1caa192a..2198a245 100644 --- a/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedDataSource.swift +++ b/RxExample/RxDataSources/DataSources/TableViewSectionedDataSource.swift @@ -1,6 +1,6 @@ // -// RxTableViewSectionedDataSource.swift -// RxCocoa +// TableViewSectionedDataSource.swift +// RxDataSources // // Created by Krunoslav Zaher on 6/15/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. @@ -8,14 +8,12 @@ import Foundation import UIKit -#if !RX_NO_MODULE -import RxSwift import RxCocoa -#endif // objc monkey business -public class _RxTableViewSectionedDataSource : NSObject - , UITableViewDataSource { +public class _TableViewSectionedDataSource + : NSObject + , UITableViewDataSource { func _numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 @@ -56,13 +54,48 @@ public class _RxTableViewSectionedDataSource : NSObject public func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? { return _tableView(tableView, titleForFooterInSection: section) } + + func _tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { + return false + } + + public func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { + return _tableView(tableView, canEditRowAtIndexPath: indexPath) + } + + func _tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool { + return false + } + + public func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool { + return _tableView(tableView, canMoveRowAtIndexPath: indexPath) + } + + func _sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? { + return nil + } + + public func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? { + return _sectionIndexTitlesForTableView(tableView) + } + + func _tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int { + return 0 + } + + public func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int { + return _tableView(tableView, sectionForSectionIndexTitle: title, atIndex: index) + } + } -public class RxTableViewSectionedDataSource : _RxTableViewSectionedDataSource { +public class RxTableViewSectionedDataSource + : _TableViewSectionedDataSource + , SectionedViewDataSourceType { public typealias I = S.Item public typealias Section = S - public typealias CellFactory = (UITableView, NSIndexPath, I) -> UITableViewCell + public typealias CellFactory = (RxTableViewSectionedDataSource, UITableView, NSIndexPath, I) -> UITableViewCell // This structure exists because model can be mutable // In that case current state value should be preserved. @@ -74,6 +107,10 @@ public class RxTableViewSectionedDataSource : _RxTableViewS private var _sectionModels: [SectionModelSnapshot] = [] + public var sectionModels: [S] { + return _sectionModels.map { $0.model } + } + public func sectionAtIndex(section: Int) -> S { return self._sectionModels[section].model } @@ -82,20 +119,31 @@ public class RxTableViewSectionedDataSource : _RxTableViewS return self._sectionModels[indexPath.section].items[indexPath.item] } + public func modelAtIndexPath(indexPath: NSIndexPath) throws -> Any { + return itemAtIndexPath(indexPath) + } + public func setSections(sections: [S]) { self._sectionModels = sections.map { SectionModelSnapshot(model: $0, items: $0.items) } } + + + public var configureCell: CellFactory! = nil - public var cellFactory: CellFactory! = nil + public var titleForHeaderInSection: ((RxTableViewSectionedDataSource, section: Int) -> String?)? + public var titleForFooterInSection: ((RxTableViewSectionedDataSource, section: Int) -> String?)? - public var titleForHeaderInSection: ((section: Int) -> String)? - public var titleForFooterInSection: ((section: Int) -> String)? + public var canEditRowAtIndexPath: ((RxTableViewSectionedDataSource, indexPath: NSIndexPath) -> Bool)? + public var canMoveRowAtIndexPath: ((RxTableViewSectionedDataSource, indexPath: NSIndexPath) -> Bool)? + + public var sectionIndexTitles: ((RxTableViewSectionedDataSource) -> [String]?)? + public var sectionForSectionIndexTitle:((RxTableViewSectionedDataSource, title: String, index: Int) -> Int)? public var rowAnimation: UITableViewRowAnimation = .Automatic public override init() { super.init() - self.cellFactory = { [weak self] _ in + self.configureCell = { [weak self] _ in if let strongSelf = self { precondition(false, "There is a minor problem. `cellFactory` property on \(strongSelf) was not set. Please set it manually, or use one of the `rx_bindTo` methods.") } @@ -117,15 +165,34 @@ public class RxTableViewSectionedDataSource : _RxTableViewS override func _tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { precondition(indexPath.item < _sectionModels[indexPath.section].items.count) - return cellFactory(tableView, indexPath, itemAtIndexPath(indexPath)) + return configureCell(self, tableView, indexPath, itemAtIndexPath(indexPath)) } override func _tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return titleForHeaderInSection?(section: section) + return titleForHeaderInSection?(self, section: section) } override func _tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return titleForFooterInSection?(section: section) + return titleForFooterInSection?(self, section: section) } -} \ No newline at end of file + override func _tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { + return canEditRowAtIndexPath?(self, indexPath: indexPath) ?? + super._tableView(tableView, canMoveRowAtIndexPath: indexPath) + } + + override func _tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool { + return canMoveRowAtIndexPath?(self, indexPath: indexPath) ?? + super._tableView(tableView, canMoveRowAtIndexPath: indexPath) + } + + override func _sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? { + return sectionIndexTitles?(self) ?? super._sectionIndexTitlesForTableView(tableView) + } + + override func _tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int { + return sectionForSectionIndexTitle?(self, title: title, index: index) ?? + super._tableView(tableView, sectionForSectionIndexTitle: title, atIndex: index) + } + +} diff --git a/RxExample/RxDataSourceStarterKit/SectionedViewType.swift b/RxExample/RxDataSources/DataSources/UI+SectionedViewType.swift similarity index 55% rename from RxExample/RxDataSourceStarterKit/SectionedViewType.swift rename to RxExample/RxDataSources/DataSources/UI+SectionedViewType.swift index 7577e34d..80d7f834 100644 --- a/RxExample/RxDataSourceStarterKit/SectionedViewType.swift +++ b/RxExample/RxDataSources/DataSources/UI+SectionedViewType.swift @@ -1,6 +1,6 @@ // -// SectionedViewType.swift -// RxExample +// UI+SectionedViewType.swift +// RxDataSources // // Created by Krunoslav Zaher on 6/27/15. // Copyright © 2015 Krunoslav Zaher. All rights reserved. @@ -18,79 +18,80 @@ func indexSet(values: [Int]) -> NSIndexSet { } extension UITableView : SectionedViewType { - func insertItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) { + + public func insertItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) { self.insertRowsAtIndexPaths(paths, withRowAnimation: animationStyle) } - func deleteItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) { + public func deleteItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) { self.deleteRowsAtIndexPaths(paths, withRowAnimation: animationStyle) } - func moveItemAtIndexPath(from: NSIndexPath, to: NSIndexPath) { + public func moveItemAtIndexPath(from: NSIndexPath, to: NSIndexPath) { self.moveRowAtIndexPath(from, toIndexPath: to) } - func reloadItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) { + public func reloadItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) { self.reloadRowsAtIndexPaths(paths, withRowAnimation: animationStyle) } - func insertSections(sections: [Int], animationStyle: UITableViewRowAnimation) { + public func insertSections(sections: [Int], animationStyle: UITableViewRowAnimation) { self.insertSections(indexSet(sections), withRowAnimation: animationStyle) } - func deleteSections(sections: [Int], animationStyle: UITableViewRowAnimation) { + public func deleteSections(sections: [Int], animationStyle: UITableViewRowAnimation) { self.deleteSections(indexSet(sections), withRowAnimation: animationStyle) } - func moveSection(from: Int, to: Int) { + public func moveSection(from: Int, to: Int) { self.moveSection(from, toSection: to) } - func reloadSections(sections: [Int], animationStyle: UITableViewRowAnimation) { + public func reloadSections(sections: [Int], animationStyle: UITableViewRowAnimation) { self.reloadSections(indexSet(sections), withRowAnimation: animationStyle) } - func performBatchUpdates(changes: Changeset) { + public func performBatchUpdates(changes: Changeset, animationConfiguration: AnimationConfiguration?=nil) { self.beginUpdates() - _performBatchUpdates(self, changes: changes) + _performBatchUpdates(self, changes: changes, animationConfiguration: animationConfiguration) self.endUpdates() } } extension UICollectionView : SectionedViewType { - func insertItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) { + public func insertItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) { self.insertItemsAtIndexPaths(paths) } - func deleteItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) { + public func deleteItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) { self.deleteItemsAtIndexPaths(paths) } - func moveItemAtIndexPath(from: NSIndexPath, to: NSIndexPath) { + public func moveItemAtIndexPath(from: NSIndexPath, to: NSIndexPath) { self.moveItemAtIndexPath(from, toIndexPath: to) } - func reloadItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) { + public func reloadItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) { self.reloadItemsAtIndexPaths(paths) } - func insertSections(sections: [Int], animationStyle: UITableViewRowAnimation) { + public func insertSections(sections: [Int], animationStyle: UITableViewRowAnimation) { self.insertSections(indexSet(sections)) } - func deleteSections(sections: [Int], animationStyle: UITableViewRowAnimation) { + public func deleteSections(sections: [Int], animationStyle: UITableViewRowAnimation) { self.deleteSections(indexSet(sections)) } - func moveSection(from: Int, to: Int) { + public func moveSection(from: Int, to: Int) { self.moveSection(from, toSection: to) } - func reloadSections(sections: [Int], animationStyle: UITableViewRowAnimation) { + public func reloadSections(sections: [Int], animationStyle: UITableViewRowAnimation) { self.reloadSections(indexSet(sections)) } - func performBatchUpdates(changes: Changeset) { + public func performBatchUpdates(changes: Changeset, animationConfiguration:AnimationConfiguration?=nil) { self.performBatchUpdates({ () -> Void in _performBatchUpdates(self, changes: changes) }, completion: { (completed: Bool) -> Void in @@ -98,7 +99,7 @@ extension UICollectionView : SectionedViewType { } } -protocol SectionedViewType { +public protocol SectionedViewType { func insertItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) func deleteItemsAtIndexPaths(paths: [NSIndexPath], animationStyle: UITableViewRowAnimation) func moveItemAtIndexPath(from: NSIndexPath, to: NSIndexPath) @@ -109,42 +110,33 @@ protocol SectionedViewType { func moveSection(from: Int, to: Int) func reloadSections(sections: [Int], animationStyle: UITableViewRowAnimation) - func performBatchUpdates(changes: Changeset) + func performBatchUpdates(changes: Changeset, animationConfiguration: AnimationConfiguration?) } -func setFor(items: [E], transform: E -> K) -> [K : K] { - var res = [K : K]() - - for i in items { - let k = transform(i) - res[k] = k - } - - return res -} - -func _performBatchUpdates(view: V, changes: Changeset) { +func _performBatchUpdates(view: V, changes: Changeset, animationConfiguration :AnimationConfiguration?=nil) { typealias I = S.Item - let rowAnimation = UITableViewRowAnimation.Automatic - - view.deleteSections(changes.deletedSections, animationStyle: rowAnimation) - view.reloadSections(changes.updatedSections, animationStyle: rowAnimation) - view.insertSections(changes.insertedSections, animationStyle: rowAnimation) + + let animationConfiguration = animationConfiguration ?? AnimationConfiguration() + view.deleteSections(changes.deletedSections, animationStyle: animationConfiguration.deleteAnimation) + // Updated sections doesn't mean reload entire section, somebody needs to update the section view manually + // otherwise all cells will be reloaded for nothing. + //view.reloadSections(changes.updatedSections, animationStyle: rowAnimation) + view.insertSections(changes.insertedSections, animationStyle: animationConfiguration.insertAnimation) for (from, to) in changes.movedSections { view.moveSection(from, to: to) } view.deleteItemsAtIndexPaths( changes.deletedItems.map { NSIndexPath(forItem: $0.itemIndex, inSection: $0.sectionIndex) }, - animationStyle: rowAnimation + animationStyle: animationConfiguration.deleteAnimation ) view.insertItemsAtIndexPaths( changes.insertedItems.map { NSIndexPath(forItem: $0.itemIndex, inSection: $0.sectionIndex) }, - animationStyle: rowAnimation + animationStyle: animationConfiguration.insertAnimation ) view.reloadItemsAtIndexPaths( changes.updatedItems.map { NSIndexPath(forItem: $0.itemIndex, inSection: $0.sectionIndex) }, - animationStyle: rowAnimation + animationStyle: animationConfiguration.reloadAnimation ) for (from, to) in changes.movedItems { diff --git a/RxExample/RxDataSourceStarterKit/README.md b/RxExample/RxDataSources/README.md similarity index 97% rename from RxExample/RxDataSourceStarterKit/README.md rename to RxExample/RxDataSources/README.md index c4ad6ebe..6794a5a1 100644 --- a/RxExample/RxDataSourceStarterKit/README.md +++ b/RxExample/RxDataSources/README.md @@ -1,5 +1,5 @@ -RxSwift: DataSource Starter Kit -=============================== +RxSwift: DataSources +==================== This directory contains example implementations of reactive data sources. diff --git a/RxExample/RxExample.xcodeproj/project.pbxproj b/RxExample/RxExample.xcodeproj/project.pbxproj index 7f126050..3d28be97 100644 --- a/RxExample/RxExample.xcodeproj/project.pbxproj +++ b/RxExample/RxExample.xcodeproj/project.pbxproj @@ -324,34 +324,6 @@ C89634081B95BE50002AE38C /* RxBlocking.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C8A468EF1B8A8BD000BF917B /* RxBlocking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C89634091B95BE50002AE38C /* RxCocoa.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C8A468ED1B8A8BCC00BF917B /* RxCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C896340A1B95BE51002AE38C /* RxSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C8A468EB1B8A8BC900BF917B /* RxSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C8984C311C36A579001E4272 /* Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C211C36A579001E4272 /* Changeset.swift */; }; - C8984C321C36A579001E4272 /* Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C211C36A579001E4272 /* Changeset.swift */; }; - C8984C331C36A579001E4272 /* RxCollectionViewSectionedAnimatedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C231C36A579001E4272 /* RxCollectionViewSectionedAnimatedDataSource.swift */; }; - C8984C341C36A579001E4272 /* RxCollectionViewSectionedAnimatedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C231C36A579001E4272 /* RxCollectionViewSectionedAnimatedDataSource.swift */; }; - C8984C351C36A579001E4272 /* RxCollectionViewSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C241C36A579001E4272 /* RxCollectionViewSectionedDataSource.swift */; }; - C8984C361C36A579001E4272 /* RxCollectionViewSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C241C36A579001E4272 /* RxCollectionViewSectionedDataSource.swift */; }; - C8984C371C36A579001E4272 /* RxCollectionViewSectionedReloadDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C251C36A579001E4272 /* RxCollectionViewSectionedReloadDataSource.swift */; }; - C8984C381C36A579001E4272 /* RxCollectionViewSectionedReloadDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C251C36A579001E4272 /* RxCollectionViewSectionedReloadDataSource.swift */; }; - C8984C391C36A579001E4272 /* RxTableViewSectionedAnimatedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C261C36A579001E4272 /* RxTableViewSectionedAnimatedDataSource.swift */; }; - C8984C3A1C36A579001E4272 /* RxTableViewSectionedAnimatedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C261C36A579001E4272 /* RxTableViewSectionedAnimatedDataSource.swift */; }; - C8984C3B1C36A579001E4272 /* RxTableViewSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C271C36A579001E4272 /* RxTableViewSectionedDataSource.swift */; }; - C8984C3C1C36A579001E4272 /* RxTableViewSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C271C36A579001E4272 /* RxTableViewSectionedDataSource.swift */; }; - C8984C3D1C36A579001E4272 /* RxTableViewSectionedReloadDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C281C36A579001E4272 /* RxTableViewSectionedReloadDataSource.swift */; }; - C8984C3E1C36A579001E4272 /* RxTableViewSectionedReloadDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C281C36A579001E4272 /* RxTableViewSectionedReloadDataSource.swift */; }; - C8984C3F1C36A579001E4272 /* Differentiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C291C36A579001E4272 /* Differentiator.swift */; }; - C8984C401C36A579001E4272 /* Differentiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C291C36A579001E4272 /* Differentiator.swift */; }; - C8984C411C36A579001E4272 /* ObservableConvertibleType+Differentiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C2A1C36A579001E4272 /* ObservableConvertibleType+Differentiator.swift */; }; - C8984C421C36A579001E4272 /* ObservableConvertibleType+Differentiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C2A1C36A579001E4272 /* ObservableConvertibleType+Differentiator.swift */; }; - C8984C451C36A579001E4272 /* RxDataSourceStarterKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C2C1C36A579001E4272 /* RxDataSourceStarterKit.swift */; }; - C8984C461C36A579001E4272 /* RxDataSourceStarterKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C2C1C36A579001E4272 /* RxDataSourceStarterKit.swift */; }; - C8984C471C36A579001E4272 /* SectionedViewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C2D1C36A579001E4272 /* SectionedViewType.swift */; }; - C8984C481C36A579001E4272 /* SectionedViewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C2D1C36A579001E4272 /* SectionedViewType.swift */; }; - C8984C491C36A579001E4272 /* SectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C2E1C36A579001E4272 /* SectionModel.swift */; }; - C8984C4A1C36A579001E4272 /* SectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C2E1C36A579001E4272 /* SectionModel.swift */; }; - C8984C4B1C36A579001E4272 /* SectionModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C2F1C36A579001E4272 /* SectionModelType.swift */; }; - C8984C4C1C36A579001E4272 /* SectionModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C2F1C36A579001E4272 /* SectionModelType.swift */; }; - C8984C4D1C36A579001E4272 /* UISectionedViewType+RxAnimatedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C301C36A579001E4272 /* UISectionedViewType+RxAnimatedDataSource.swift */; }; - C8984C4E1C36A579001E4272 /* UISectionedViewType+RxAnimatedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984C301C36A579001E4272 /* UISectionedViewType+RxAnimatedDataSource.swift */; }; C8984CD11C36BC3E001E4272 /* NumberCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984CCE1C36BC3E001E4272 /* NumberCell.swift */; }; C8984CD21C36BC3E001E4272 /* NumberCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984CCE1C36BC3E001E4272 /* NumberCell.swift */; }; C8984CD31C36BC3E001E4272 /* NumberSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8984CCF1C36BC3E001E4272 /* NumberSectionView.swift */; }; @@ -373,6 +345,50 @@ C8A468F11B8A8C2600BF917B /* RxBlocking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8A468EF1B8A8BD000BF917B /* RxBlocking.framework */; }; C8A468F21B8A8C2600BF917B /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8A468ED1B8A8BCC00BF917B /* RxCocoa.framework */; }; C8A468F31B8A8C2600BF917B /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8A468EB1B8A8BC900BF917B /* RxSwift.framework */; }; + C8B290BF1C959D2900E923D0 /* AnimatableSectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290A71C959D2900E923D0 /* AnimatableSectionModel.swift */; }; + C8B290C01C959D2900E923D0 /* AnimatableSectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290A71C959D2900E923D0 /* AnimatableSectionModel.swift */; }; + C8B290C11C959D2900E923D0 /* AnimatableSectionModelType+ItemPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290A81C959D2900E923D0 /* AnimatableSectionModelType+ItemPath.swift */; }; + C8B290C21C959D2900E923D0 /* AnimatableSectionModelType+ItemPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290A81C959D2900E923D0 /* AnimatableSectionModelType+ItemPath.swift */; }; + C8B290C31C959D2900E923D0 /* AnimatableSectionModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290A91C959D2900E923D0 /* AnimatableSectionModelType.swift */; }; + C8B290C41C959D2900E923D0 /* AnimatableSectionModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290A91C959D2900E923D0 /* AnimatableSectionModelType.swift */; }; + C8B290C51C959D2900E923D0 /* AnimationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290AA1C959D2900E923D0 /* AnimationConfiguration.swift */; }; + C8B290C61C959D2900E923D0 /* AnimationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290AA1C959D2900E923D0 /* AnimationConfiguration.swift */; }; + C8B290C71C959D2900E923D0 /* Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290AB1C959D2900E923D0 /* Changeset.swift */; }; + C8B290C81C959D2900E923D0 /* Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290AB1C959D2900E923D0 /* Changeset.swift */; }; + C8B290C91C959D2900E923D0 /* CollectionViewSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290AC1C959D2900E923D0 /* CollectionViewSectionedDataSource.swift */; }; + C8B290CA1C959D2900E923D0 /* CollectionViewSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290AC1C959D2900E923D0 /* CollectionViewSectionedDataSource.swift */; }; + C8B290CB1C959D2900E923D0 /* DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290AD1C959D2900E923D0 /* DataSources.swift */; }; + C8B290CC1C959D2900E923D0 /* DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290AD1C959D2900E923D0 /* DataSources.swift */; }; + C8B290CD1C959D2900E923D0 /* Differentiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290AE1C959D2900E923D0 /* Differentiator.swift */; }; + C8B290CE1C959D2900E923D0 /* Differentiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290AE1C959D2900E923D0 /* Differentiator.swift */; }; + C8B290CF1C959D2900E923D0 /* IdentifiableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290AF1C959D2900E923D0 /* IdentifiableType.swift */; }; + C8B290D01C959D2900E923D0 /* IdentifiableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290AF1C959D2900E923D0 /* IdentifiableType.swift */; }; + C8B290D11C959D2900E923D0 /* IdentifiableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B01C959D2900E923D0 /* IdentifiableValue.swift */; }; + C8B290D21C959D2900E923D0 /* IdentifiableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B01C959D2900E923D0 /* IdentifiableValue.swift */; }; + C8B290D31C959D2900E923D0 /* ItemPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B11C959D2900E923D0 /* ItemPath.swift */; }; + C8B290D41C959D2900E923D0 /* ItemPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B11C959D2900E923D0 /* ItemPath.swift */; }; + C8B290D51C959D2900E923D0 /* Optional+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B21C959D2900E923D0 /* Optional+Extensions.swift */; }; + C8B290D61C959D2900E923D0 /* Optional+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B21C959D2900E923D0 /* Optional+Extensions.swift */; }; + C8B290D71C959D2900E923D0 /* SectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B31C959D2900E923D0 /* SectionModel.swift */; }; + C8B290D81C959D2900E923D0 /* SectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B31C959D2900E923D0 /* SectionModel.swift */; }; + C8B290D91C959D2900E923D0 /* SectionModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B41C959D2900E923D0 /* SectionModelType.swift */; }; + C8B290DA1C959D2900E923D0 /* SectionModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B41C959D2900E923D0 /* SectionModelType.swift */; }; + C8B290DB1C959D2900E923D0 /* TableViewSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B51C959D2900E923D0 /* TableViewSectionedDataSource.swift */; }; + C8B290DC1C959D2900E923D0 /* TableViewSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B51C959D2900E923D0 /* TableViewSectionedDataSource.swift */; }; + C8B290DD1C959D2900E923D0 /* UI+SectionedViewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B61C959D2900E923D0 /* UI+SectionedViewType.swift */; }; + C8B290DE1C959D2900E923D0 /* UI+SectionedViewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B61C959D2900E923D0 /* UI+SectionedViewType.swift */; }; + C8B290DF1C959D2900E923D0 /* ObservableConvertibleType+Differentiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B81C959D2900E923D0 /* ObservableConvertibleType+Differentiator.swift */; }; + C8B290E01C959D2900E923D0 /* ObservableConvertibleType+Differentiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B81C959D2900E923D0 /* ObservableConvertibleType+Differentiator.swift */; }; + C8B290E11C959D2900E923D0 /* RxCollectionViewSectionedAnimatedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B91C959D2900E923D0 /* RxCollectionViewSectionedAnimatedDataSource.swift */; }; + C8B290E21C959D2900E923D0 /* RxCollectionViewSectionedAnimatedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290B91C959D2900E923D0 /* RxCollectionViewSectionedAnimatedDataSource.swift */; }; + C8B290E31C959D2900E923D0 /* RxCollectionViewSectionedReloadDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290BA1C959D2900E923D0 /* RxCollectionViewSectionedReloadDataSource.swift */; }; + C8B290E41C959D2900E923D0 /* RxCollectionViewSectionedReloadDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290BA1C959D2900E923D0 /* RxCollectionViewSectionedReloadDataSource.swift */; }; + C8B290E51C959D2900E923D0 /* RxTableViewSectionedAnimatedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290BB1C959D2900E923D0 /* RxTableViewSectionedAnimatedDataSource.swift */; }; + C8B290E61C959D2900E923D0 /* RxTableViewSectionedAnimatedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290BB1C959D2900E923D0 /* RxTableViewSectionedAnimatedDataSource.swift */; }; + C8B290E71C959D2900E923D0 /* RxTableViewSectionedReloadDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290BC1C959D2900E923D0 /* RxTableViewSectionedReloadDataSource.swift */; }; + C8B290E81C959D2900E923D0 /* RxTableViewSectionedReloadDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290BC1C959D2900E923D0 /* RxTableViewSectionedReloadDataSource.swift */; }; + C8B290E91C959D2900E923D0 /* UISectionedViewType+RxAnimatedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290BD1C959D2900E923D0 /* UISectionedViewType+RxAnimatedDataSource.swift */; }; + C8B290EA1C959D2900E923D0 /* UISectionedViewType+RxAnimatedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B290BD1C959D2900E923D0 /* UISectionedViewType+RxAnimatedDataSource.swift */; }; C8BCD3CE1C14756F005F1280 /* ShareReplay1WhileConnected.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8BCD3CD1C14756F005F1280 /* ShareReplay1WhileConnected.swift */; }; C8BCD3DF1C1480E9005F1280 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8BCD3DE1C1480E9005F1280 /* Operators.swift */; }; C8BCD3E01C1480E9005F1280 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8BCD3DE1C1480E9005F1280 /* Operators.swift */; }; @@ -865,21 +881,6 @@ C89465581BC6C2BC0055219D /* UITextField+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+Rx.swift"; sourceTree = ""; }; C89465591BC6C2BC0055219D /* UITextView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextView+Rx.swift"; sourceTree = ""; }; C89465601BC6C2BC0055219D /* RxCocoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RxCocoa.h; sourceTree = ""; }; - C8984C211C36A579001E4272 /* Changeset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Changeset.swift; sourceTree = ""; }; - C8984C231C36A579001E4272 /* RxCollectionViewSectionedAnimatedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxCollectionViewSectionedAnimatedDataSource.swift; sourceTree = ""; }; - C8984C241C36A579001E4272 /* RxCollectionViewSectionedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxCollectionViewSectionedDataSource.swift; sourceTree = ""; }; - C8984C251C36A579001E4272 /* RxCollectionViewSectionedReloadDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxCollectionViewSectionedReloadDataSource.swift; sourceTree = ""; }; - C8984C261C36A579001E4272 /* RxTableViewSectionedAnimatedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTableViewSectionedAnimatedDataSource.swift; sourceTree = ""; }; - C8984C271C36A579001E4272 /* RxTableViewSectionedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTableViewSectionedDataSource.swift; sourceTree = ""; }; - C8984C281C36A579001E4272 /* RxTableViewSectionedReloadDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTableViewSectionedReloadDataSource.swift; sourceTree = ""; }; - C8984C291C36A579001E4272 /* Differentiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Differentiator.swift; sourceTree = ""; }; - C8984C2A1C36A579001E4272 /* ObservableConvertibleType+Differentiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObservableConvertibleType+Differentiator.swift"; sourceTree = ""; }; - C8984C2B1C36A579001E4272 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - C8984C2C1C36A579001E4272 /* RxDataSourceStarterKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxDataSourceStarterKit.swift; sourceTree = ""; }; - C8984C2D1C36A579001E4272 /* SectionedViewType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionedViewType.swift; sourceTree = ""; }; - C8984C2E1C36A579001E4272 /* SectionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionModel.swift; sourceTree = ""; }; - C8984C2F1C36A579001E4272 /* SectionModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionModelType.swift; sourceTree = ""; }; - C8984C301C36A579001E4272 /* UISectionedViewType+RxAnimatedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UISectionedViewType+RxAnimatedDataSource.swift"; sourceTree = ""; }; C8984CCE1C36BC3E001E4272 /* NumberCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberCell.swift; sourceTree = ""; }; C8984CCF1C36BC3E001E4272 /* NumberSectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberSectionView.swift; sourceTree = ""; }; C8984CD01C36BC3E001E4272 /* PartialUpdatesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PartialUpdatesViewController.swift; sourceTree = ""; }; @@ -897,6 +898,29 @@ C8A468EF1B8A8BD000BF917B /* RxBlocking.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = RxBlocking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C8B145041BD2E45200267DCE /* ImmediateScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImmediateScheduler.swift; sourceTree = ""; }; C8B145051BD2E45200267DCE /* ConcurrentMainScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConcurrentMainScheduler.swift; sourceTree = ""; }; + C8B290A71C959D2900E923D0 /* AnimatableSectionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatableSectionModel.swift; sourceTree = ""; }; + C8B290A81C959D2900E923D0 /* AnimatableSectionModelType+ItemPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AnimatableSectionModelType+ItemPath.swift"; sourceTree = ""; }; + C8B290A91C959D2900E923D0 /* AnimatableSectionModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatableSectionModelType.swift; sourceTree = ""; }; + C8B290AA1C959D2900E923D0 /* AnimationConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationConfiguration.swift; sourceTree = ""; }; + C8B290AB1C959D2900E923D0 /* Changeset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Changeset.swift; sourceTree = ""; }; + C8B290AC1C959D2900E923D0 /* CollectionViewSectionedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewSectionedDataSource.swift; sourceTree = ""; }; + C8B290AD1C959D2900E923D0 /* DataSources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataSources.swift; sourceTree = ""; }; + C8B290AE1C959D2900E923D0 /* Differentiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Differentiator.swift; sourceTree = ""; }; + C8B290AF1C959D2900E923D0 /* IdentifiableType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentifiableType.swift; sourceTree = ""; }; + C8B290B01C959D2900E923D0 /* IdentifiableValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentifiableValue.swift; sourceTree = ""; }; + C8B290B11C959D2900E923D0 /* ItemPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemPath.swift; sourceTree = ""; }; + C8B290B21C959D2900E923D0 /* Optional+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Optional+Extensions.swift"; sourceTree = ""; }; + C8B290B31C959D2900E923D0 /* SectionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionModel.swift; sourceTree = ""; }; + C8B290B41C959D2900E923D0 /* SectionModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionModelType.swift; sourceTree = ""; }; + C8B290B51C959D2900E923D0 /* TableViewSectionedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewSectionedDataSource.swift; sourceTree = ""; }; + C8B290B61C959D2900E923D0 /* UI+SectionedViewType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UI+SectionedViewType.swift"; sourceTree = ""; }; + C8B290B81C959D2900E923D0 /* ObservableConvertibleType+Differentiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObservableConvertibleType+Differentiator.swift"; sourceTree = ""; }; + C8B290B91C959D2900E923D0 /* RxCollectionViewSectionedAnimatedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxCollectionViewSectionedAnimatedDataSource.swift; sourceTree = ""; }; + C8B290BA1C959D2900E923D0 /* RxCollectionViewSectionedReloadDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxCollectionViewSectionedReloadDataSource.swift; sourceTree = ""; }; + C8B290BB1C959D2900E923D0 /* RxTableViewSectionedAnimatedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTableViewSectionedAnimatedDataSource.swift; sourceTree = ""; }; + C8B290BC1C959D2900E923D0 /* RxTableViewSectionedReloadDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTableViewSectionedReloadDataSource.swift; sourceTree = ""; }; + C8B290BD1C959D2900E923D0 /* UISectionedViewType+RxAnimatedDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UISectionedViewType+RxAnimatedDataSource.swift"; sourceTree = ""; }; + C8B290BE1C959D2900E923D0 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; C8BCD3CD1C14756F005F1280 /* ShareReplay1WhileConnected.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareReplay1WhileConnected.swift; sourceTree = ""; }; C8BCD3DE1C1480E9005F1280 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = ""; }; C8BCD3E21C14820B005F1280 /* IntroductionExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntroductionExampleViewController.swift; sourceTree = ""; }; @@ -1081,7 +1105,7 @@ C8A468EF1B8A8BD000BF917B /* RxBlocking.framework */, C8A468ED1B8A8BCC00BF917B /* RxCocoa.framework */, C8A468EB1B8A8BC900BF917B /* RxSwift.framework */, - C8984C201C36A579001E4272 /* RxDataSourceStarterKit */, + C8B290A51C959D2900E923D0 /* RxDataSources */, C836EB911B8A7A3700AB941D /* NoModule */, C83366DF1AD0293800C668A7 /* RxExample */, C849EF621C3190360048AC4A /* RxExample-iOSTests */, @@ -1665,36 +1689,6 @@ path = Proxies; sourceTree = ""; }; - C8984C201C36A579001E4272 /* RxDataSourceStarterKit */ = { - isa = PBXGroup; - children = ( - C8984C211C36A579001E4272 /* Changeset.swift */, - C8984C221C36A579001E4272 /* DataSources */, - C8984C291C36A579001E4272 /* Differentiator.swift */, - C8984C2A1C36A579001E4272 /* ObservableConvertibleType+Differentiator.swift */, - C8984C2B1C36A579001E4272 /* README.md */, - C8984C2C1C36A579001E4272 /* RxDataSourceStarterKit.swift */, - C8984C2D1C36A579001E4272 /* SectionedViewType.swift */, - C8984C2E1C36A579001E4272 /* SectionModel.swift */, - C8984C2F1C36A579001E4272 /* SectionModelType.swift */, - C8984C301C36A579001E4272 /* UISectionedViewType+RxAnimatedDataSource.swift */, - ); - path = RxDataSourceStarterKit; - sourceTree = ""; - }; - C8984C221C36A579001E4272 /* DataSources */ = { - isa = PBXGroup; - children = ( - C8984C231C36A579001E4272 /* RxCollectionViewSectionedAnimatedDataSource.swift */, - C8984C241C36A579001E4272 /* RxCollectionViewSectionedDataSource.swift */, - C8984C251C36A579001E4272 /* RxCollectionViewSectionedReloadDataSource.swift */, - C8984C261C36A579001E4272 /* RxTableViewSectionedAnimatedDataSource.swift */, - C8984C271C36A579001E4272 /* RxTableViewSectionedDataSource.swift */, - C8984C281C36A579001E4272 /* RxTableViewSectionedReloadDataSource.swift */, - ); - path = DataSources; - sourceTree = ""; - }; C8984CCD1C36BC3E001E4272 /* TableViewPartialUpdates */ = { isa = PBXGroup; children = ( @@ -1716,6 +1710,52 @@ path = Mocks; sourceTree = ""; }; + C8B290A51C959D2900E923D0 /* RxDataSources */ = { + isa = PBXGroup; + children = ( + C8B290A61C959D2900E923D0 /* DataSources */, + C8B290B71C959D2900E923D0 /* DataSources+Rx */, + C8B290BE1C959D2900E923D0 /* README.md */, + ); + path = RxDataSources; + sourceTree = ""; + }; + C8B290A61C959D2900E923D0 /* DataSources */ = { + isa = PBXGroup; + children = ( + C8B290A71C959D2900E923D0 /* AnimatableSectionModel.swift */, + C8B290A81C959D2900E923D0 /* AnimatableSectionModelType+ItemPath.swift */, + C8B290A91C959D2900E923D0 /* AnimatableSectionModelType.swift */, + C8B290AA1C959D2900E923D0 /* AnimationConfiguration.swift */, + C8B290AB1C959D2900E923D0 /* Changeset.swift */, + C8B290AC1C959D2900E923D0 /* CollectionViewSectionedDataSource.swift */, + C8B290AD1C959D2900E923D0 /* DataSources.swift */, + C8B290AE1C959D2900E923D0 /* Differentiator.swift */, + C8B290AF1C959D2900E923D0 /* IdentifiableType.swift */, + C8B290B01C959D2900E923D0 /* IdentifiableValue.swift */, + C8B290B11C959D2900E923D0 /* ItemPath.swift */, + C8B290B21C959D2900E923D0 /* Optional+Extensions.swift */, + C8B290B31C959D2900E923D0 /* SectionModel.swift */, + C8B290B41C959D2900E923D0 /* SectionModelType.swift */, + C8B290B51C959D2900E923D0 /* TableViewSectionedDataSource.swift */, + C8B290B61C959D2900E923D0 /* UI+SectionedViewType.swift */, + ); + path = DataSources; + sourceTree = ""; + }; + C8B290B71C959D2900E923D0 /* DataSources+Rx */ = { + isa = PBXGroup; + children = ( + C8B290B81C959D2900E923D0 /* ObservableConvertibleType+Differentiator.swift */, + C8B290B91C959D2900E923D0 /* RxCollectionViewSectionedAnimatedDataSource.swift */, + C8B290BA1C959D2900E923D0 /* RxCollectionViewSectionedReloadDataSource.swift */, + C8B290BB1C959D2900E923D0 /* RxTableViewSectionedAnimatedDataSource.swift */, + C8B290BC1C959D2900E923D0 /* RxTableViewSectionedReloadDataSource.swift */, + C8B290BD1C959D2900E923D0 /* UISectionedViewType+RxAnimatedDataSource.swift */, + ); + path = "DataSources+Rx"; + sourceTree = ""; + }; C8BCD3E11C14820B005F1280 /* OSX simple example */ = { isa = PBXGroup; children = ( @@ -2097,6 +2137,7 @@ files = ( 0744CDD51C4DB5F000720FD2 /* GeolocationService.swift in Sources */, C83D73DE1C1DBC2A003DC470 /* AnonymousInvocable.swift in Sources */, + C8B290D21C959D2900E923D0 /* IdentifiableValue.swift in Sources */, C84015751C34353D009D2E77 /* DispatchQueueSchedulerQOS.swift in Sources */, C84CC58B1BDD486300E06A64 /* LockOwnerType.swift in Sources */, C89465971BC6C2BC0055219D /* UIScrollView+Rx.swift in Sources */, @@ -2105,11 +2146,11 @@ C89464D41BC6C2B00055219D /* ObserveOn.swift in Sources */, C894659B1BC6C2BC0055219D /* UIStepper+Rx.swift in Sources */, 8479BC711C3BCB9800FB8B54 /* ImagePickerController.swift in Sources */, - C8984C321C36A579001E4272 /* Changeset.swift in Sources */, C864BADA1C3332F10083833C /* RandomUserAPI.swift in Sources */, C89464F61BC6C2B00055219D /* AnonymousObserver.swift in Sources */, C89464C81BC6C2B00055219D /* DistinctUntilChanged.swift in Sources */, C860ECAC1C42EACD00A664B3 /* SectionedViewDataSourceType.swift in Sources */, + C8B290E21C959D2900E923D0 /* RxCollectionViewSectionedAnimatedDataSource.swift in Sources */, C89464CF1BC6C2B00055219D /* Just.swift in Sources */, C8F6A1381BEF9DF6007DF367 /* RetryWhen.swift in Sources */, C89465921BC6C2BC0055219D /* UIControl+Rx.swift in Sources */, @@ -2138,12 +2179,11 @@ C89464DA1BC6C2B00055219D /* Repeat.swift in Sources */, C89465651BC6C2BC0055219D /* CLLocationManager+Rx.swift in Sources */, C80DDED81BCE9046006A1832 /* Driver.swift in Sources */, - C8984C401C36A579001E4272 /* Differentiator.swift in Sources */, C84015881C343595009D2E77 /* Platform.Darwin.swift in Sources */, C80DDED71BCE9046006A1832 /* Driver+Subscription.swift in Sources */, C8F6A1321BEF9DA3007DF367 /* RecursiveScheduler.swift in Sources */, 07A5C3DC1B70B703001EFE5C /* CalculatorViewController.swift in Sources */, - C8984C4C1C36A579001E4272 /* SectionModelType.swift in Sources */, + C8B290CC1C959D2900E923D0 /* DataSources.swift in Sources */, C89465961BC6C2BC0055219D /* UILabel+Rx.swift in Sources */, C822B1E01C14CEAA0088A01A /* BindingExtensions.swift in Sources */, C8BCD3CE1C14756F005F1280 /* ShareReplay1WhileConnected.swift in Sources */, @@ -2157,22 +2197,23 @@ C864BADE1C3332F10083833C /* TableViewWithEditingCommandsViewController.swift in Sources */, B1604CC41BE5B8CE002E1279 /* DownloadableImage.swift in Sources */, C80DDED61BCE9046006A1832 /* Driver+Operators.swift in Sources */, - C8984C361C36A579001E4272 /* RxCollectionViewSectionedDataSource.swift in Sources */, C843A0941C1CE58700CBA4BD /* UINavigationController+Extensions.swift in Sources */, C89464B81BC6C2B00055219D /* Observable.swift in Sources */, C894659E1BC6C2BC0055219D /* UITextField+Rx.swift in Sources */, C89464D11BC6C2B00055219D /* Merge.swift in Sources */, C849EF811C3193B10048AC4A /* GitHubSignupViewController1.swift in Sources */, C89464A71BC6C2B00055219D /* BinaryDisposable.swift in Sources */, - C8984C461C36A579001E4272 /* RxDataSourceStarterKit.swift in Sources */, C8297E361B6CF905000589EA /* RootViewController.swift in Sources */, C89464BB1BC6C2B00055219D /* AnonymousObservable.swift in Sources */, C89465991BC6C2BC0055219D /* UISegmentedControl+Rx.swift in Sources */, B1B7C3D01BE006870076934E /* TakeLast.swift in Sources */, C849EF9E1C31A8750048AC4A /* String+URL.swift in Sources */, + C8B290CA1C959D2900E923D0 /* CollectionViewSectionedDataSource.swift in Sources */, C8C4B4CC1C17728200828BD5 /* MessageSentObserver.swift in Sources */, + C8B290C01C959D2900E923D0 /* AnimatableSectionModel.swift in Sources */, C8297E391B6CF905000589EA /* CollectionViewImageCell.swift in Sources */, C894649E1BC6C2B00055219D /* Cancelable.swift in Sources */, + C8B290DC1C959D2900E923D0 /* TableViewSectionedDataSource.swift in Sources */, C89464E01BC6C2B00055219D /* SubscribeOn.swift in Sources */, C89465801BC6C2BC0055219D /* RxTableViewReactiveArrayDataSource.swift in Sources */, C89464FA1BC6C2B00055219D /* ObserverType.swift in Sources */, @@ -2181,6 +2222,7 @@ C89464DF1BC6C2B00055219D /* StartWith.swift in Sources */, C89465881BC6C2BC0055219D /* RxScrollViewDelegateProxy.swift in Sources */, C8984CD21C36BC3E001E4272 /* NumberCell.swift in Sources */, + C8B290C61C959D2900E923D0 /* AnimationConfiguration.swift in Sources */, C89464B71BC6C2B00055219D /* Observable+Extensions.swift in Sources */, C8F8C4A11C277F5A0047640B /* Operation.swift in Sources */, C89464A01BC6C2B00055219D /* Lock.swift in Sources */, @@ -2189,9 +2231,11 @@ C89464C91BC6C2B00055219D /* Do.swift in Sources */, C89464A41BC6C2B00055219D /* Queue.swift in Sources */, C89464B91BC6C2B00055219D /* ObservableConvertibleType.swift in Sources */, + C8B290CE1C959D2900E923D0 /* Differentiator.swift in Sources */, C894657C1BC6C2BC0055219D /* RxCocoa.swift in Sources */, C894658F1BC6C2BC0055219D /* UIBarButtonItem+Rx.swift in Sources */, C89464D61BC6C2B00055219D /* Producer.swift in Sources */, + C8B290D01C959D2900E923D0 /* IdentifiableType.swift in Sources */, C8BCD4041C14BFCA005F1280 /* UIView+Rx.swift in Sources */, C822B1E81C14E7250088A01A /* SimpleTableViewExampleSectionedViewController.swift in Sources */, C894658A1BC6C2BC0055219D /* RxTableViewDataSourceProxy.swift in Sources */, @@ -2199,13 +2243,11 @@ C89465721BC6C2BC0055219D /* ControlTarget.swift in Sources */, C89464EC1BC6C2B00055219D /* Observable+Binding.swift in Sources */, C83D73E01C1DBC2A003DC470 /* InvocableType.swift in Sources */, - C8984C381C36A579001E4272 /* RxCollectionViewSectionedReloadDataSource.swift in Sources */, C8297E3A1B6CF905000589EA /* WikipediaSearchViewController.swift in Sources */, C89464F21BC6C2B00055219D /* Observable+StandardSequenceOperators.swift in Sources */, C89464CC1BC6C2B00055219D /* Filter.swift in Sources */, C864BAE01C3332F10083833C /* UIImageView+Extensions.swift in Sources */, C80DDED31BCE9046006A1832 /* ControlProperty+Driver.swift in Sources */, - C8984C341C36A579001E4272 /* RxCollectionViewSectionedAnimatedDataSource.swift in Sources */, C89464C11BC6C2B00055219D /* CombineLatest+CollectionType.swift in Sources */, C89465671BC6C2BC0055219D /* ControlEvent.swift in Sources */, C89464A61BC6C2B00055219D /* AnonymousDisposable.swift in Sources */, @@ -2228,18 +2270,18 @@ C89464F11BC6C2B00055219D /* Observable+Single.swift in Sources */, C89464BA1BC6C2B00055219D /* Amb.swift in Sources */, C894650A1BC6C2B00055219D /* SubjectType.swift in Sources */, - C8984C4A1C36A579001E4272 /* SectionModel.swift in Sources */, C89464B11BC6C2B00055219D /* SingleAssignmentDisposable.swift in Sources */, C89464AA1BC6C2B00055219D /* DisposeBase.swift in Sources */, C89465871BC6C2BC0055219D /* RxCollectionViewDelegateProxy.swift in Sources */, D2245A191BD5654C00E7146F /* WithLatestFrom.swift in Sources */, C809E97E1BE697100058D948 /* UIImage+Extensions.swift in Sources */, + C8B290C21C959D2900E923D0 /* AnimatableSectionModelType+ItemPath.swift in Sources */, C89464E61BC6C2B00055219D /* Timer.swift in Sources */, C83D73DF1C1DBC2A003DC470 /* InvocableScheduledItem.swift in Sources */, C822B1E41C14E4810088A01A /* SimpleTableViewExampleViewController.swift in Sources */, - C8984C421C36A579001E4272 /* ObservableConvertibleType+Differentiator.swift in Sources */, C83974131BF77406004F02CC /* KVORepresentable+CoreGraphics.swift in Sources */, C8297E401B6CF905000589EA /* ImageService.swift in Sources */, + C8B290EA1C959D2900E923D0 /* UISectionedViewType+RxAnimatedDataSource.swift in Sources */, C89464AD1BC6C2B00055219D /* NopDisposable.swift in Sources */, C84CC5901BDD486300E06A64 /* AsyncLock.swift in Sources */, C8F6A1311BEF9DA3007DF367 /* OperationQueueScheduler.swift in Sources */, @@ -2248,6 +2290,7 @@ C8F3FFF51C6FD62E00E60EEC /* UIBindingObserver.swift in Sources */, C89465091BC6C2B00055219D /* ReplaySubject.swift in Sources */, C8BCD3E01C1480E9005F1280 /* Operators.swift in Sources */, + C8B290DE1C959D2900E923D0 /* UI+SectionedViewType.swift in Sources */, C84CC58E1BDD486300E06A64 /* SynchronizedSubscribeType.swift in Sources */, 0744CDEE1C4DB78600720FD2 /* GeolocationViewController.swift in Sources */, 8479BC6E1C3BC99C00FB8B54 /* UIImagePickerController+Rx.swift in Sources */, @@ -2257,6 +2300,7 @@ C89465821BC6C2BC0055219D /* RxCollectionViewDataSourceType.swift in Sources */, C8297E421B6CF905000589EA /* WikipediaSearchResult.swift in Sources */, C89465701BC6C2BC0055219D /* Logging.swift in Sources */, + C8B290E81C959D2900E923D0 /* RxTableViewSectionedReloadDataSource.swift in Sources */, C8F8C49E1C277F4F0047640B /* CalculatorAction.swift in Sources */, C89464A31BC6C2B00055219D /* InfiniteSequence.swift in Sources */, C89465611BC6C2BC0055219D /* _RX.m in Sources */, @@ -2274,6 +2318,7 @@ C843A08F1C1CE39900CBA4BD /* GitHubSearchRepositoriesAPI.swift in Sources */, C83974231BF77413004F02CC /* NSObject+Rx+KVORepresentable.swift in Sources */, C8297E461B6CF905000589EA /* Example.swift in Sources */, + C8B290DA1C959D2900E923D0 /* SectionModelType.swift in Sources */, C89465081BC6C2B00055219D /* PublishSubject.swift in Sources */, C89464FC1BC6C2B00055219D /* RxMutableBox.swift in Sources */, C809E97B1BE6841C0058D948 /* Wireframe.swift in Sources */, @@ -2291,7 +2336,7 @@ B1604CCB1BE5BC45002E1279 /* UIImageView+DownloadableImage.swift in Sources */, C8297E471B6CF905000589EA /* ViewController.swift in Sources */, C89464E41BC6C2B00055219D /* TakeWhile.swift in Sources */, - C8984C481C36A579001E4272 /* SectionedViewType.swift in Sources */, + C8B290C81C959D2900E923D0 /* Changeset.swift in Sources */, C89464F71BC6C2B00055219D /* ObserverBase.swift in Sources */, C89465951BC6C2BC0055219D /* UIImageView+Rx.swift in Sources */, C89464E31BC6C2B00055219D /* TakeUntil.swift in Sources */, @@ -2309,16 +2354,18 @@ C89465621BC6C2BC0055219D /* _RXDelegateProxy.m in Sources */, C89464D31BC6C2B00055219D /* Never.swift in Sources */, C8F6A1351BEF9DA3007DF367 /* SerialDispatchQueueScheduler.swift in Sources */, - C8984C3A1C36A579001E4272 /* RxTableViewSectionedAnimatedDataSource.swift in Sources */, C89465931BC6C2BC0055219D /* UIDatePicker+Rx.swift in Sources */, C8CCB8D41C2D5FBA000EDACC /* String+Rx.swift in Sources */, + C8B290E41C959D2900E923D0 /* RxCollectionViewSectionedReloadDataSource.swift in Sources */, C84CC58F1BDD486300E06A64 /* SynchronizedUnsubscribeType.swift in Sources */, C89CDB711BCC45E5002063D9 /* ShareReplay1.swift in Sources */, C89464BD1BC6C2B00055219D /* Buffer.swift in Sources */, 84C225AB1C340474008724EC /* RxTextStorageDelegateProxy.swift in Sources */, C89464B31BC6C2B00055219D /* Errors.swift in Sources */, C864BAE21C3332F10083833C /* User.swift in Sources */, + C8B290D41C959D2900E923D0 /* ItemPath.swift in Sources */, C8F6A1301BEF9DA3007DF367 /* MainScheduler.swift in Sources */, + C8B290D61C959D2900E923D0 /* Optional+Extensions.swift in Sources */, C89464C51BC6C2B00055219D /* Debug.swift in Sources */, C89464AB1BC6C2B00055219D /* NAryDisposable.swift in Sources */, C89465631BC6C2BC0055219D /* _RXKVOObserver.m in Sources */, @@ -2332,9 +2379,9 @@ C89464CA1BC6C2B00055219D /* Empty.swift in Sources */, C803973B1BD3E17D009D8B26 /* ActivityIndicator.swift in Sources */, C89464C61BC6C2B00055219D /* Deferred.swift in Sources */, + C8B290E01C959D2900E923D0 /* ObservableConvertibleType+Differentiator.swift in Sources */, C8984CD41C36BC3E001E4272 /* NumberSectionView.swift in Sources */, CB883B611BE3AC72000AC2EE /* AddRef.swift in Sources */, - C8984C4E1C36A579001E4272 /* UISectionedViewType+RxAnimatedDataSource.swift in Sources */, C8BCD4021C14BFB7005F1280 /* NSLayoutConstraint+Rx.swift in Sources */, D2AF91981BD3D95900A008C1 /* Using.swift in Sources */, C8297E521B6CF905000589EA /* Dependencies.swift in Sources */, @@ -2347,11 +2394,13 @@ C8297E531B6CF905000589EA /* WikipediaAPI.swift in Sources */, C89465071BC6C2B00055219D /* BehaviorSubject.swift in Sources */, C89465911BC6C2BC0055219D /* UICollectionView+Rx.swift in Sources */, + C8B290C41C959D2900E923D0 /* AnimatableSectionModelType.swift in Sources */, C89464D01BC6C2B00055219D /* Map.swift in Sources */, C8297E541B6CF905000589EA /* AppDelegate.swift in Sources */, C894659D1BC6C2BC0055219D /* UITableView+Rx.swift in Sources */, C8F6A12C1BEF9DA3007DF367 /* ConcurrentMainScheduler.swift in Sources */, C8F8C48B1C277F460047640B /* CalculatorState.swift in Sources */, + C8B290E61C959D2900E923D0 /* RxTableViewSectionedAnimatedDataSource.swift in Sources */, C894657F1BC6C2BC0055219D /* RxCollectionViewReactiveArrayDataSource.swift in Sources */, 84C225A81C3402E5008724EC /* UITextView+Rx.swift in Sources */, 8479BC501C3BC98F00FB8B54 /* RxImagePickerDelegateProxy.swift in Sources */, @@ -2360,7 +2409,6 @@ C8297E571B6CF905000589EA /* Randomizer.swift in Sources */, C89464C31BC6C2B00055219D /* Concat.swift in Sources */, C894657A1BC6C2BC0055219D /* NSURLSession+Rx.swift in Sources */, - C8984C3C1C36A579001E4272 /* RxTableViewSectionedDataSource.swift in Sources */, C89464F41BC6C2B00055219D /* ObservableType.swift in Sources */, C89464CE1BC6C2B00055219D /* Generate.swift in Sources */, C89465711BC6C2BC0055219D /* Observable+Bind.swift in Sources */, @@ -2372,12 +2420,12 @@ C89464B41BC6C2B00055219D /* Event.swift in Sources */, C89465691BC6C2BC0055219D /* ControlProperty.swift in Sources */, C89465061BC6C2B00055219D /* SchedulerType.swift in Sources */, - C8984C3E1C36A579001E4272 /* RxTableViewSectionedReloadDataSource.swift in Sources */, C84015891C343595009D2E77 /* Platform.Linux.swift in Sources */, C8C4B4BF1C17724A00828BD5 /* _RXObjCRuntime.m in Sources */, C83D73E11C1DBC2A003DC470 /* ScheduledItem.swift in Sources */, C849EF871C3195180048AC4A /* GitHubSignupViewController2.swift in Sources */, C894658C1BC6C2BC0055219D /* RxTextViewDelegateProxy.swift in Sources */, + C8B290D81C959D2900E923D0 /* SectionModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2385,75 +2433,83 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C8B290C51C959D2900E923D0 /* AnimationConfiguration.swift in Sources */, 0744CDD41C4DB5F000720FD2 /* GeolocationService.swift in Sources */, B1604CC91BE5BBFA002E1279 /* UIImageView+DownloadableImage.swift in Sources */, C86E2F3E1AE5A0CA00C31024 /* SearchResultViewModel.swift in Sources */, C83367241AD029AE00C668A7 /* HtmlParsing.swift in Sources */, - C8984C3F1C36A579001E4272 /* Differentiator.swift in Sources */, - C8984C4D1C36A579001E4272 /* UISectionedViewType+RxAnimatedDataSource.swift in Sources */, C8F8C48A1C277F460047640B /* CalculatorState.swift in Sources */, - C8984C411C36A579001E4272 /* ObservableConvertibleType+Differentiator.swift in Sources */, C843A08E1C1CE39900CBA4BD /* GitHubSearchRepositoriesAPI.swift in Sources */, C849EF801C3193B10048AC4A /* GitHubSignupViewController1.swift in Sources */, C8DF92E51B0B32DA009BCF9A /* RootViewController.swift in Sources */, - C8984C371C36A579001E4272 /* RxCollectionViewSectionedReloadDataSource.swift in Sources */, - C8984C4B1C36A579001E4272 /* SectionModelType.swift in Sources */, C822B1DC1C14CD1C0088A01A /* DefaultImplementations.swift in Sources */, + C8B290CB1C959D2900E923D0 /* DataSources.swift in Sources */, C8C46DA81B47F7110020D71E /* CollectionViewImageCell.swift in Sources */, C8984CD31C36BC3E001E4272 /* NumberSectionView.swift in Sources */, C822B1E31C14E4810088A01A /* SimpleTableViewExampleViewController.swift in Sources */, C8C46DAC1B47F7110020D71E /* WikipediaSearchViewController.swift in Sources */, + C8B290C11C959D2900E923D0 /* AnimatableSectionModelType+ItemPath.swift in Sources */, 07A5C3DB1B70B703001EFE5C /* CalculatorViewController.swift in Sources */, C849EF861C3195180048AC4A /* GitHubSignupViewController2.swift in Sources */, C8F8C49D1C277F4F0047640B /* CalculatorAction.swift in Sources */, + C8B290D51C959D2900E923D0 /* Optional+Extensions.swift in Sources */, + C8B290D91C959D2900E923D0 /* SectionModelType.swift in Sources */, C864BAD71C3332F10083833C /* DetailViewController.swift in Sources */, + C8B290C31C959D2900E923D0 /* AnimatableSectionModelType.swift in Sources */, C822B1DF1C14CEAA0088A01A /* BindingExtensions.swift in Sources */, C864BAD91C3332F10083833C /* RandomUserAPI.swift in Sources */, + C8B290E51C959D2900E923D0 /* RxTableViewSectionedAnimatedDataSource.swift in Sources */, C864BADF1C3332F10083833C /* UIImageView+Extensions.swift in Sources */, C8BCD3DF1C1480E9005F1280 /* Operators.swift in Sources */, C843A0901C1CE39900CBA4BD /* GitHubSearchRepositoriesViewController.swift in Sources */, C803973A1BD3E17D009D8B26 /* ActivityIndicator.swift in Sources */, C849EF821C3193B10048AC4A /* GithubSignupViewModel1.swift in Sources */, C864BADD1C3332F10083833C /* TableViewWithEditingCommandsViewController.swift in Sources */, - C8984C391C36A579001E4272 /* RxTableViewSectionedAnimatedDataSource.swift in Sources */, C8F8C4A01C277F5A0047640B /* Operation.swift in Sources */, C822B1D91C14CBEA0088A01A /* Protocols.swift in Sources */, - C8984C471C36A579001E4272 /* SectionedViewType.swift in Sources */, C8BCD3E61C14A95E005F1280 /* NumbersViewController.swift in Sources */, + C8B290D71C959D2900E923D0 /* SectionModel.swift in Sources */, C849EF881C3195180048AC4A /* GithubSignupViewModel2.swift in Sources */, + C8B290BF1C959D2900E923D0 /* AnimatableSectionModel.swift in Sources */, C809E97A1BE6841C0058D948 /* Wireframe.swift in Sources */, - C8984C351C36A579001E4272 /* RxCollectionViewSectionedDataSource.swift in Sources */, + C8B290C71C959D2900E923D0 /* Changeset.swift in Sources */, C843A0931C1CE58700CBA4BD /* UINavigationController+Extensions.swift in Sources */, - C8984C3B1C36A579001E4272 /* RxTableViewSectionedDataSource.swift in Sources */, C864BADB1C3332F10083833C /* String+extensions.swift in Sources */, C822B1E71C14E7250088A01A /* SimpleTableViewExampleSectionedViewController.swift in Sources */, - C8984C491C36A579001E4272 /* SectionModel.swift in Sources */, C83367251AD029AE00C668A7 /* ImageService.swift in Sources */, C86E2F471AE5A0CA00C31024 /* WikipediaSearchResult.swift in Sources */, C8984CD11C36BC3E001E4272 /* NumberCell.swift in Sources */, + C8B290DD1C959D2900E923D0 /* UI+SectionedViewType.swift in Sources */, C8A2A2C81B4049E300F11F09 /* PseudoRandomGenerator.swift in Sources */, + C8B290E31C959D2900E923D0 /* RxCollectionViewSectionedReloadDataSource.swift in Sources */, + C8B290CD1C959D2900E923D0 /* Differentiator.swift in Sources */, C8D132151C42B54B00B59FFF /* UIImagePickerController+RxCreate.swift in Sources */, C8984CD51C36BC3E001E4272 /* PartialUpdatesViewController.swift in Sources */, 8479BC721C3BDAD400FB8B54 /* ImagePickerController.swift in Sources */, C864BAE11C3332F10083833C /* User.swift in Sources */, 0744CDED1C4DB78600720FD2 /* GeolocationViewController.swift in Sources */, - C8984C331C36A579001E4272 /* RxCollectionViewSectionedAnimatedDataSource.swift in Sources */, + C8B290D31C959D2900E923D0 /* ItemPath.swift in Sources */, + C8B290DB1C959D2900E923D0 /* TableViewSectionedDataSource.swift in Sources */, C83367231AD029AE00C668A7 /* Example.swift in Sources */, + C8B290D11C959D2900E923D0 /* IdentifiableValue.swift in Sources */, + C8B290E11C959D2900E923D0 /* RxCollectionViewSectionedAnimatedDataSource.swift in Sources */, + C8B290E91C959D2900E923D0 /* UISectionedViewType+RxAnimatedDataSource.swift in Sources */, C890A65D1AEC084100AFF7E6 /* ViewController.swift in Sources */, C8C46DAA1B47F7110020D71E /* WikipediaSearchCell.swift in Sources */, + C8B290C91C959D2900E923D0 /* CollectionViewSectionedDataSource.swift in Sources */, B1604CB51BE49F8D002E1279 /* DownloadableImage.swift in Sources */, 075F13101B4E9D5A000D7861 /* APIWrappersViewController.swift in Sources */, B18F3BE21BDB2E8F000AAC79 /* ReachabilityService.swift in Sources */, B18F3BBC1BD92EC8000AAC79 /* Reachability.swift in Sources */, - C8984C311C36A579001E4272 /* Changeset.swift in Sources */, 07E3C2331B03605B0010338D /* Dependencies.swift in Sources */, C849EF9C1C31A8750048AC4A /* String+URL.swift in Sources */, C86E2F451AE5A0CA00C31024 /* WikipediaAPI.swift in Sources */, - C8984C451C36A579001E4272 /* RxDataSourceStarterKit.swift in Sources */, - C8984C3D1C36A579001E4272 /* RxTableViewSectionedReloadDataSource.swift in Sources */, C8DF92CD1B0B2F84009BCF9A /* AppDelegate.swift in Sources */, C86E2F461AE5A0CA00C31024 /* WikipediaPage.swift in Sources */, + C8B290E71C959D2900E923D0 /* RxTableViewSectionedReloadDataSource.swift in Sources */, C809E97D1BE697100058D948 /* UIImage+Extensions.swift in Sources */, + C8B290DF1C959D2900E923D0 /* ObservableConvertibleType+Differentiator.swift in Sources */, + C8B290CF1C959D2900E923D0 /* IdentifiableType.swift in Sources */, C8A2A2CB1B404A1200F11F09 /* Randomizer.swift in Sources */, C8BCD3EA1C14B02A005F1280 /* SimpleValidationViewController.swift in Sources */, ); diff --git a/RxExample/RxExample/Examples/GitHubSearchRepositories/GitHubSearchRepositoriesViewController.swift b/RxExample/RxExample/Examples/GitHubSearchRepositories/GitHubSearchRepositoriesViewController.swift index 52c2e610..49946f71 100644 --- a/RxExample/RxExample/Examples/GitHubSearchRepositories/GitHubSearchRepositoriesViewController.swift +++ b/RxExample/RxExample/Examples/GitHubSearchRepositories/GitHubSearchRepositoriesViewController.swift @@ -30,14 +30,14 @@ class GitHubSearchRepositoriesViewController: ViewController, UITableViewDelegat let tableView = self.tableView let searchBar = self.searchBar - dataSource.cellFactory = { (tv, ip, repository: Repository) in + dataSource.configureCell = { (_, tv, ip, repository: Repository) in let cell = tv.dequeueReusableCellWithIdentifier("Cell")! cell.textLabel?.text = repository.name cell.detailTextLabel?.text = repository.url return cell } - dataSource.titleForHeaderInSection = { [unowned dataSource] sectionIndex in + dataSource.titleForHeaderInSection = { dataSource, sectionIndex in let section = dataSource.sectionAtIndex(sectionIndex) return section.items.count > 0 ? "Repositories (\(section.items.count))" : "No repositories found" } diff --git a/RxExample/RxExample/Examples/SimpleTableViewExampleSectioned/SimpleTableViewExampleSectionedViewController.swift b/RxExample/RxExample/Examples/SimpleTableViewExampleSectioned/SimpleTableViewExampleSectionedViewController.swift index 35f61497..f9e64f3b 100644 --- a/RxExample/RxExample/Examples/SimpleTableViewExampleSectioned/SimpleTableViewExampleSectionedViewController.swift +++ b/RxExample/RxExample/Examples/SimpleTableViewExampleSectioned/SimpleTableViewExampleSectionedViewController.swift @@ -43,7 +43,7 @@ class SimpleTableViewExampleSectionedViewController ]) ]) - dataSource.cellFactory = { (tv, indexPath, element) in + dataSource.configureCell = { (_, tv, indexPath, element) in let cell = tv.dequeueReusableCellWithIdentifier("Cell")! cell.textLabel?.text = "\(element) @ row \(indexPath.row)" return cell diff --git a/RxExample/RxExample/Examples/TableViewPartialUpdates/PartialUpdatesViewController.swift b/RxExample/RxExample/Examples/TableViewPartialUpdates/PartialUpdatesViewController.swift index c88676d0..9c31829d 100644 --- a/RxExample/RxExample/Examples/TableViewPartialUpdates/PartialUpdatesViewController.swift +++ b/RxExample/RxExample/Examples/TableViewPartialUpdates/PartialUpdatesViewController.swift @@ -28,7 +28,7 @@ class PartialUpdatesViewController : ViewController { var timer: NSTimer? = nil - static let initialValue: [HashableSectionModel] = [ + static let initialValue: [AnimatableSectionModel] = [ NumberSection(model: "section 1", items: [1, 2, 3]), NumberSection(model: "section 2", items: [4, 5, 6]), NumberSection(model: "section 3", items: [7, 8, 9]), @@ -42,7 +42,7 @@ class PartialUpdatesViewController : ViewController { ] - static let firstChange: [HashableSectionModel]? = nil + static let firstChange: [AnimatableSectionModel]? = nil var generator = Randomizer(rng: PseudoRandomGenerator(4, 3), sections: initialValue) @@ -63,10 +63,10 @@ class PartialUpdatesViewController : ViewController { let nSections = 10 let nItems = 100 - var sections = [HashableSectionModel]() + var sections = [AnimatableSectionModel]() for i in 0 ..< nSections { - sections.append(HashableSectionModel(model: "Section \(i + 1)", items: Array(i * nItems ..< (i + 1) * nItems))) + sections.append(AnimatableSectionModel(model: "Section \(i + 1)", items: Array(i * nItems ..< (i + 1) * nItems))) } generator = Randomizer(rng: PseudoRandomGenerator(4, 3), sections: sections) @@ -136,7 +136,7 @@ class PartialUpdatesViewController : ViewController { } func skinTableViewDataSource(dataSource: RxTableViewSectionedDataSource) { - dataSource.cellFactory = { (tv, ip, i) in + dataSource.configureCell = { (_, tv, ip, i) in let cell = tv.dequeueReusableCellWithIdentifier("Cell") ?? UITableViewCell(style:.Default, reuseIdentifier: "Cell") @@ -145,13 +145,13 @@ class PartialUpdatesViewController : ViewController { return cell } - dataSource.titleForHeaderInSection = { [unowned dataSource] (section: Int) -> String in + dataSource.titleForHeaderInSection = { (ds, section: Int) -> String in return dataSource.sectionAtIndex(section).model } } - func skinCollectionViewDataSource(dataSource: RxCollectionViewSectionedDataSource) { - dataSource.cellFactory = { (cv, ip, i) in + func skinCollectionViewDataSource(dataSource: CollectionViewSectionedDataSource) { + dataSource.cellFactory = { (_, cv, ip, i) in let cell = cv.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: ip) as! NumberCell cell.value!.text = "\(i)" @@ -159,7 +159,7 @@ class PartialUpdatesViewController : ViewController { return cell } - dataSource.supplementaryViewFactory = { [unowned dataSource] (cv, kind, ip) in + dataSource.supplementaryViewFactory = { (dataSource, cv, kind, ip) in let section = cv.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "Section", forIndexPath: ip) as! NumberSectionView section.value!.text = "\(dataSource.sectionAtIndex(ip.section).model)" diff --git a/RxExample/RxExample/Examples/TableViewWithEditingCommands/TableViewWithEditingCommandsViewController.swift b/RxExample/RxExample/Examples/TableViewWithEditingCommands/TableViewWithEditingCommandsViewController.swift index 6d63515f..0e03ab16 100644 --- a/RxExample/RxExample/Examples/TableViewWithEditingCommands/TableViewWithEditingCommandsViewController.swift +++ b/RxExample/RxExample/Examples/TableViewWithEditingCommands/TableViewWithEditingCommandsViewController.swift @@ -152,13 +152,13 @@ class TableViewWithEditingCommandsViewController: ViewController, UITableViewDel static func configureDataSource() -> RxTableViewSectionedReloadDataSource> { let dataSource = RxTableViewSectionedReloadDataSource>() - dataSource.cellFactory = { (tv, ip, user: User) in + dataSource.configureCell = { (_, tv, ip, user: User) in let cell = tv.dequeueReusableCellWithIdentifier("Cell")! cell.textLabel?.text = user.firstName + " " + user.lastName return cell } - dataSource.titleForHeaderInSection = { [unowned dataSource] sectionIndex in + dataSource.titleForHeaderInSection = { dataSource, sectionIndex in return dataSource.sectionAtIndex(sectionIndex).model } diff --git a/RxExample/RxExample/Services/Randomizer.swift b/RxExample/RxExample/Services/Randomizer.swift index a14dea4d..24cb5282 100644 --- a/RxExample/RxExample/Services/Randomizer.swift +++ b/RxExample/RxExample/Services/Randomizer.swift @@ -8,7 +8,7 @@ import Foundation -typealias NumberSection = HashableSectionModel +typealias NumberSection = AnimatableSectionModel let insertItems = true let deleteItems = true @@ -74,7 +74,7 @@ class Randomizer { if rng.get_random() % 2 == 0 { let itemIndex = rng.get_random() % (itemCount + 1) if insertItems { - sections[sectionIndex].items.insert(unusedValue, atIndex: itemIndex) + sections[sectionIndex].items.insert(IdentitifiableValue(value: unusedValue), atIndex: itemIndex) } else { nextUnusedItems.append(unusedValue) @@ -83,14 +83,14 @@ class Randomizer { // update else { if itemCount == 0 { - sections[sectionIndex].items.insert(unusedValue, atIndex: 0) + sections[sectionIndex].items.insert(IdentitifiableValue(value: unusedValue), atIndex: 0) continue } let itemIndex = rng.get_random() % itemCount if reloadItems { - nextUnusedItems.append(sections[sectionIndex].items.removeAtIndex(itemIndex)) - sections[sectionIndex].items.insert(unusedValue, atIndex: itemIndex) + nextUnusedItems.append(sections[sectionIndex].items.removeAtIndex(itemIndex).value) + sections[sectionIndex].items.insert(IdentitifiableValue(value: unusedValue), atIndex: itemIndex) } else { @@ -151,7 +151,7 @@ class Randomizer { let sourceItemIndex = rng.get_random() % sectionItemCount if deleteItems { - nextUnusedItems.append(sections[sourceSectionIndex].items.removeAtIndex(sourceItemIndex)) + nextUnusedItems.append(sections[sourceSectionIndex].items.removeAtIndex(sourceItemIndex).value) } } @@ -188,7 +188,7 @@ class Randomizer { let section = sections.removeAtIndex(sectionIndex) for item in section.items { - nextUnusedItems.append(item) + nextUnusedItems.append(item.value) } nextUnusedSections.append(section.model)