diff --git a/AutoCat.xcodeproj/project.pbxproj b/AutoCat.xcodeproj/project.pbxproj index 4d459c7..7f95513 100644 --- a/AutoCat.xcodeproj/project.pbxproj +++ b/AutoCat.xcodeproj/project.pbxproj @@ -9,8 +9,6 @@ /* Begin PBXBuildFile section */ 7A000AA224C2EEDE001F5B00 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A000AA124C2EEDE001F5B00 /* Location.swift */; }; 7A051611241412CA00FC55AC /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = 7A051610241412CA00FC55AC /* SwiftDate */; }; - 7A0516162414EC1200FC55AC /* Differentiator in Frameworks */ = {isa = PBXBuildFile; productRef = 7A0516152414EC1200FC55AC /* Differentiator */; }; - 7A0516182414EC1200FC55AC /* RxDataSources in Frameworks */ = {isa = PBXBuildFile; productRef = 7A0516172414EC1200FC55AC /* RxDataSources */; }; 7A05161A2414FF0900FC55AC /* DateSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0516192414FF0900FC55AC /* DateSection.swift */; }; 7A1090E824A394F100B4F0B2 /* AudioRecordCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1090E724A394F100B4F0B2 /* AudioRecordCell.swift */; }; 7A1090EA24A3A26300B4F0B2 /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1090E924A3A26300B4F0B2 /* AudioPlayer.swift */; }; @@ -80,6 +78,8 @@ 7A96AE33246C095700297C33 /* Base64FS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE32246C095700297C33 /* Base64FS.swift */; }; 7A9FEEC82529AB23001CA50E /* RxRealmDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */; }; 7AABDE1D2532F3EB0041AFC6 /* PKHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABDE1C2532F3EB0041AFC6 /* PKHUD */; }; + 7AABDE23253327F10041AFC6 /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABDE22253327F10041AFC6 /* DifferenceKit */; }; + 7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */; }; 7AAE6AD324CDDF950023860B /* VehicleEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAE6AD224CDDF950023860B /* VehicleEvent.swift */; }; 7AB562BA249C9E9B00473D53 /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB562B9249C9E9B00473D53 /* Region.swift */; }; 7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8B2435C38700258F61 /* CustomTextField.swift */; }; @@ -168,6 +168,7 @@ 7A96AE30246B2FE400297C33 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 7A96AE32246C095700297C33 /* Base64FS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base64FS.swift; sourceTree = ""; }; 7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxRealmDataSource.swift; sourceTree = ""; }; + 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxSectionedDataSource.swift; sourceTree = ""; }; 7AAE6AD224CDDF950023860B /* VehicleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleEvent.swift; sourceTree = ""; }; 7AB562B9249C9E9B00473D53 /* Region.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Region.swift; sourceTree = ""; }; 7AB67E8B2435C38700258F61 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = ""; }; @@ -195,16 +196,15 @@ files = ( 7A813DBE2506A57100CC93B9 /* AuthenticationServices.framework in Frameworks */, 7AF58D342402A91C00CE01A0 /* Kingfisher in Frameworks */, - 7A0516162414EC1200FC55AC /* Differentiator in Frameworks */, 7A813DC12508C4D900CC93B9 /* ExceptionCatcher in Frameworks */, 7A96AE2F246B2BCD00297C33 /* WebKit.framework in Frameworks */, 7A11472823FEA1F400B424AF /* RealmSwift in Frameworks */, 7A11472123FEA18700B424AF /* RxCocoa in Frameworks */, - 7A0516182414EC1200FC55AC /* RxDataSources in Frameworks */, 7A96AE2A246AFD6200297C33 /* Eureka in Frameworks */, 7A051611241412CA00FC55AC /* SwiftDate in Frameworks */, 7A11472323FEA18700B424AF /* RxBlocking in Frameworks */, 7AABDE1D2532F3EB0041AFC6 /* PKHUD in Frameworks */, + 7AABDE23253327F10041AFC6 /* DifferenceKit in Frameworks */, 7A530B8B240181F500CBFE6E /* RxRealm in Frameworks */, 7A11471F23FEA18700B424AF /* RxRelay in Frameworks */, 7AF58D2F24029C5200CE01A0 /* MagazineLayout in Frameworks */, @@ -296,6 +296,7 @@ 7A1090E924A3A26300B4F0B2 /* AudioPlayer.swift */, 7A000AA124C2EEDE001F5B00 /* Location.swift */, 7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */, + 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */, ); path = Utils; sourceTree = ""; @@ -436,11 +437,10 @@ 7AF58D2E24029C5200CE01A0 /* MagazineLayout */, 7AF58D332402A91C00CE01A0 /* Kingfisher */, 7A051610241412CA00FC55AC /* SwiftDate */, - 7A0516152414EC1200FC55AC /* Differentiator */, - 7A0516172414EC1200FC55AC /* RxDataSources */, 7A96AE29246AFD6200297C33 /* Eureka */, 7A813DC02508C4D900CC93B9 /* ExceptionCatcher */, 7AABDE1C2532F3EB0041AFC6 /* PKHUD */, + 7AABDE22253327F10041AFC6 /* DifferenceKit */, ); productName = AutoCat; productReference = 7A1146FD23FDE7E500B424AF /* AutoCat.app */; @@ -477,10 +477,10 @@ 7AF58D2D24029C5200CE01A0 /* XCRemoteSwiftPackageReference "MagazineLayout" */, 7AF58D322402A91C00CE01A0 /* XCRemoteSwiftPackageReference "Kingfisher" */, 7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */, - 7A0516142414EC1200FC55AC /* XCRemoteSwiftPackageReference "RxDataSources" */, 7A96AE28246AFD6200297C33 /* XCRemoteSwiftPackageReference "Eureka" */, 7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */, 7AABDE1B2532F3EB0041AFC6 /* XCRemoteSwiftPackageReference "PKHUD" */, + 7AABDE21253327F10041AFC6 /* XCRemoteSwiftPackageReference "DifferenceKit" */, ); productRefGroup = 7A1146FE23FDE7E500B424AF /* Products */; projectDirPath = ""; @@ -523,6 +523,7 @@ 7A6DD90824329144009DE740 /* CenterTextLayer.swift in Sources */, 7A813DC32508EE4F00CC93B9 /* EventCell.swift in Sources */, 7A3F07AD2436350B00E59687 /* SearchController.swift in Sources */, + 7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */, 7AB562BA249C9E9B00473D53 /* Region.swift in Sources */, 7A659B5924A2B1BA0043A0F2 /* AudioRecord.swift in Sources */, 7A6DD90C24335A6D009DE740 /* FlagLayer.swift in Sources */, @@ -727,7 +728,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_TEAM = 46DTTB8X4S; INFOPLIST_FILE = AutoCat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -749,7 +750,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 43; + CURRENT_PROJECT_VERSION = 44; DEVELOPMENT_TEAM = 46DTTB8X4S; INFOPLIST_FILE = AutoCat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -797,14 +798,6 @@ minimumVersion = 6.1.0; }; }; - 7A0516142414EC1200FC55AC /* XCRemoteSwiftPackageReference "RxDataSources" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/RxSwiftCommunity/RxDataSources"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 4.0.1; - }; - }; 7A11471B23FEA18700B424AF /* XCRemoteSwiftPackageReference "RxSwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ReactiveX/RxSwift.git"; @@ -853,6 +846,14 @@ minimumVersion = 5.4.0; }; }; + 7AABDE21253327F10041AFC6 /* XCRemoteSwiftPackageReference "DifferenceKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/ra1028/DifferenceKit.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.5; + }; + }; 7AF58D2D24029C5200CE01A0 /* XCRemoteSwiftPackageReference "MagazineLayout" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/airbnb/MagazineLayout"; @@ -877,16 +878,6 @@ package = 7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */; productName = SwiftDate; }; - 7A0516152414EC1200FC55AC /* Differentiator */ = { - isa = XCSwiftPackageProductDependency; - package = 7A0516142414EC1200FC55AC /* XCRemoteSwiftPackageReference "RxDataSources" */; - productName = Differentiator; - }; - 7A0516172414EC1200FC55AC /* RxDataSources */ = { - isa = XCSwiftPackageProductDependency; - package = 7A0516142414EC1200FC55AC /* XCRemoteSwiftPackageReference "RxDataSources" */; - productName = RxDataSources; - }; 7A11471C23FEA18700B424AF /* RxSwift */ = { isa = XCSwiftPackageProductDependency; package = 7A11471B23FEA18700B424AF /* XCRemoteSwiftPackageReference "RxSwift" */; @@ -937,6 +928,11 @@ package = 7AABDE1B2532F3EB0041AFC6 /* XCRemoteSwiftPackageReference "PKHUD" */; productName = PKHUD; }; + 7AABDE22253327F10041AFC6 /* DifferenceKit */ = { + isa = XCSwiftPackageProductDependency; + package = 7AABDE21253327F10041AFC6 /* XCRemoteSwiftPackageReference "DifferenceKit" */; + productName = DifferenceKit; + }; 7AF58D2E24029C5200CE01A0 /* MagazineLayout */ = { isa = XCSwiftPackageProductDependency; package = 7AF58D2D24029C5200CE01A0 /* XCRemoteSwiftPackageReference "MagazineLayout" */; diff --git a/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f1cb54a..a5409b2 100644 --- a/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { "object": { "pins": [ + { + "package": "DifferenceKit", + "repositoryURL": "https://github.com/ra1028/DifferenceKit.git", + "state": { + "branch": null, + "revision": "14c66681e12a38b81045f44c6c29724a0d4b0e72", + "version": "1.1.5" + } + }, { "package": "Eureka", "repositoryURL": "https://github.com/xmartlabs/Eureka", @@ -64,15 +73,6 @@ "version": "6.1.3" } }, - { - "package": "RxDataSources", - "repositoryURL": "https://github.com/RxSwiftCommunity/RxDataSources", - "state": { - "branch": null, - "revision": "a18cee01c9ee55f04d0c7eb15c77a96c3648c88e", - "version": "4.0.1" - } - }, { "package": "RxRealm", "repositoryURL": "https://github.com/RxSwiftCommunity/RxRealm", diff --git a/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 0aab7da..9b245e6 100644 --- a/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -8,7 +8,7 @@ BreakpointExtensionID = "Xcode.Breakpoint.SwiftErrorBreakpoint"> @@ -17,7 +17,7 @@ BreakpointExtensionID = "Xcode.Breakpoint.ExceptionBreakpoint"> orderHint 0 + DifferenceKit (Playground) 1.xcscheme + + isShown + + orderHint + 14 + + DifferenceKit (Playground) 2.xcscheme + + isShown + + orderHint + 15 + + DifferenceKit (Playground).xcscheme + + isShown + + orderHint + 11 + Eureka (Playground) 1.xcscheme isShown @@ -35,14 +56,14 @@ isShown orderHint - 11 + 12 GettingStarted (Playground) 2.xcscheme isShown orderHint - 12 + 13 GettingStarted (Playground) 3.xcscheme diff --git a/AutoCat/Controllers/SearchController.swift b/AutoCat/Controllers/SearchController.swift index 2e6b643..f86b78c 100644 --- a/AutoCat/Controllers/SearchController.swift +++ b/AutoCat/Controllers/SearchController.swift @@ -1,5 +1,4 @@ import UIKit -import RxDataSources import RxSwift import RxCocoa import RealmSwift @@ -13,7 +12,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe let bag = DisposeBag() let searchController = UISearchController(searchResultsController: nil) var refreshControl = UIRefreshControl() - var datasource: RxTableViewSectionedAnimatedDataSource>! + var datasource: RxSectionedDataSource! var filterRelay = BehaviorRelay(value: Filter()) var filter = Filter() @@ -33,23 +32,8 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe self.refreshControl.addTarget(self, action: #selector(self.refresh(_:)), for: .valueChanged) self.tableView.addSubview(self.refreshControl) - self.datasource = RxTableViewSectionedAnimatedDataSource>(configureCell: { dataSource, tableView, indexPath, item in - if let cell = tableView.dequeueReusableCell(withIdentifier: "VehicleCell", for: indexPath) as? VehicleCell { - cell.configure(with: item) - return cell - } else { - return UITableViewCell() - } - }) - self.datasource.canEditRowAtIndexPath = { _, _ in true } - - self.datasource.titleForHeaderInSection = { dataSourse, index in - return dataSourse.sectionModels[index].header - } - - self.tableView.rx.modelSelected(Vehicle.self) - .subscribe(onNext: self.updateDetailController(with:)) - .disposed(by: self.bag) + self.datasource = RxSectionedDataSource(table: self.tableView) + self.tableView.delegate = self DispatchQueue.main.async { self.filterRelay @@ -62,12 +46,9 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe self.showMapButton.isEnabled = $0.count > 0 self.refreshControl.endRefreshing() }) - .map { $0.groupedByDate() } - .bind(to: self.tableView.rx.items(dataSource: self.datasource)) + .bind(to: self.datasource.data) .disposed(by: self.bag) } - - self.tableView.rx.setDelegate(self).disposed(by: self.bag) } // FIXME: Code duplication @@ -135,7 +116,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe // MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - let vehicle = self.datasource[indexPath] + let vehicle = self.datasource.item(at: indexPath) let updateAction = UIContextualAction(style: .normal, title: "Update") { action, view, completion in self.update(vehicle: vehicle, at: indexPath) completion(true) @@ -149,7 +130,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe } func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - let vehicle = self.datasource[indexPath] + let vehicle = self.datasource.item(at: indexPath) return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in let update = UIAction(title: "Update", image: UIImage(systemName: "arrow.2.circlepath")) { action in @@ -171,10 +152,15 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe } } } - self.datasource[indexPath] = newVehicle + self.datasource.set(item: newVehicle, at: indexPath) self.updateDetailController(with: newVehicle) } onError: { err in HUD.show(error: err) }.disposed(by: self.bag) } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let vehicle = self.datasource.item(at: indexPath) + self.updateDetailController(with: vehicle) + } } diff --git a/AutoCat/Extensions/Dated.swift b/AutoCat/Extensions/Dated.swift index 7f5827f..e43567e 100644 --- a/AutoCat/Extensions/Dated.swift +++ b/AutoCat/Extensions/Dated.swift @@ -1,6 +1,6 @@ import Foundation -import RxDataSources import SwiftDate +import DifferenceKit protocol Dated { var date: Date { get } @@ -18,7 +18,7 @@ extension AudioRecord: Dated { } } -extension RandomAccessCollection where Element: Dated & IdentifiableType & Equatable { +extension RandomAccessCollection where Element: Dated & Equatable & Differentiable { func groupedByDate() -> [DateSection] { let now = Date() let monthStart = now.dateAtStartOf(.month) diff --git a/AutoCat/Models/AudioRecord.swift b/AutoCat/Models/AudioRecord.swift index 1129610..f0123df 100644 --- a/AutoCat/Models/AudioRecord.swift +++ b/AutoCat/Models/AudioRecord.swift @@ -1,8 +1,8 @@ import Foundation import RealmSwift -import RxDataSources +import DifferenceKit -class AudioRecord: Object, IdentifiableType { +class AudioRecord: Object, Identifiable, Differentiable { @objc dynamic var path: String = "" @objc dynamic var number: String? @@ -12,13 +12,19 @@ class AudioRecord: Object, IdentifiableType { @objc dynamic var event: VehicleEvent? var identifier: TimeInterval = 0 - var identity: TimeInterval { + var id: TimeInterval { if self.identifier == 0 { self.identifier = self.addedDate } return self.identifier } + var differenceIdentifier: TimeInterval { id } + + func isContentEqual(to source: AudioRecord) -> Bool { + return self == source + } + init(path: String, number: String?, raw: String, duration: TimeInterval, event: VehicleEvent?) { self.path = path self.number = number @@ -36,7 +42,7 @@ class AudioRecord: Object, IdentifiableType { } override class func ignoredProperties() -> [String] { - return ["identity", "identifier"] + return ["id", "identifier", "differenceIdentifier"] } func getAddedDate() -> TimeInterval { diff --git a/AutoCat/Models/DateSection.swift b/AutoCat/Models/DateSection.swift index 0ba4c7e..ce9f96d 100644 --- a/AutoCat/Models/DateSection.swift +++ b/AutoCat/Models/DateSection.swift @@ -1,20 +1,22 @@ import Foundation -import RxDataSources import SwiftDate +import DifferenceKit -struct DateSection: AnimatableSectionModelType, Equatable where T: IdentifiableType, T: Equatable { - +struct DateSection: Differentiable, DifferentiableSection where T: Differentiable { + var timestamp: Double = 0 var header: String - var items: [T] + var elements: [T] - var identity: String { - return header + init(source: DateSection, elements: C) where C : Swift.Collection, C.Element == Self.Collection.Element { + self.timestamp = source.timestamp + self.header = source.header + self.elements = Array(elements) } init(original: DateSection, items: [T]) { self = original - self.items = items + self.elements = items } init(timestamp: Double, items: [T]) { @@ -45,6 +47,16 @@ struct DateSection: AnimatableSectionModelType, Equatable where T: Identifiab } self.timestamp = timestamp - self.items = items + self.elements = items + } + + // MARK: - Differentiable + + var differenceIdentifier: String { + return header + } + + func isContentEqual(to source: DateSection) -> Bool { + return self.differenceIdentifier == source.differenceIdentifier } } diff --git a/AutoCat/Models/Vehicle.swift b/AutoCat/Models/Vehicle.swift index 295e036..9392aa6 100644 --- a/AutoCat/Models/Vehicle.swift +++ b/AutoCat/Models/Vehicle.swift @@ -1,6 +1,6 @@ import Foundation import RealmSwift -import RxDataSources +import DifferenceKit class VehicleName: Object, Decodable { @objc dynamic var original: String? @@ -66,7 +66,7 @@ class VehicleOwnershipPeriod: Object, Decodable { @objc dynamic var to: Int64 } -class Vehicle: Object, Decodable, IdentifiableType { +class Vehicle: Object, Decodable, Identifiable, Differentiable { @objc dynamic var brand: VehicleBrand? @objc dynamic var model: VehicleModel? @objc dynamic var color: String? @@ -89,13 +89,19 @@ class Vehicle: Object, Decodable, IdentifiableType { let events = List() var identifier: String = "" - var identity: String { + var id: String { if self.identifier.isEmpty { self.identifier = self.number } return self.identifier } + var differenceIdentifier: String { id } + + func isContentEqual(to source: Vehicle) -> Bool { + return self == source + } + enum CodingKeys: String, CodingKey { case brand case model @@ -183,7 +189,7 @@ class Vehicle: Object, Decodable, IdentifiableType { } override class func ignoredProperties() -> [String] { - return ["identity", "identifier"] + return ["id", "identifier", "differenceIdentifier"] } var unrecognized: Bool { diff --git a/AutoCat/Utils/RxRealmDataSource.swift b/AutoCat/Utils/RxRealmDataSource.swift index 4b58955..e9f45c0 100644 --- a/AutoCat/Utils/RxRealmDataSource.swift +++ b/AutoCat/Utils/RxRealmDataSource.swift @@ -1,8 +1,8 @@ import UIKit import RealmSwift -import RxDataSources +import DifferenceKit -class RealmSectionedDataSource: NSObject, UITableViewDataSource where Item: Object & IdentifiableType & Dated, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item { +class RealmSectionedDataSource: NSObject, UITableViewDataSource where Item: Object & Identifiable & Dated & Differentiable, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item { private var tv: UITableView private var data: Results @@ -24,28 +24,20 @@ class RealmSectionedDataSource: NSObject, UITableViewDataSource where self.tv.reloadData() case .update(_, let deletions, let insertions, let modifications): print("====== Deletes: \(deletions.count), Inserts: \(insertions.count), Mods: \(modifications.count)") - let oldSectionsCount = self.sections.count + + let newSections = self.data.groupedByDate() + let changeset = StagedChangeset(source: self.sections, target: newSections, section: 0) + self.tv.beginUpdates() - let delPaths = deletions.map(self.indexPath) self.tv.deleteRows(at: deletions.map(self.indexPath), with: .automatic) - if let delPath = delPaths.first, self.sections[delPath.section].items.count == 1 { - self.tv.deleteSections(IndexSet(integer: delPath.section), with: .automatic) - } self.sections = self.data.groupedByDate() - let insPaths = insertions.map(self.indexPath) - if let insPath = insPaths.first, self.sections[insPath.section].items.count == 1 { - self.tv.insertSections(IndexSet(integer: insPath.section), with: .automatic) + self.tv.insertRows(at: insertions.map(self.indexPath), with: .automatic) + self.tv.reloadRows(at: modifications.map(self.indexPath), with: .automatic) + + if let firstChangeset = changeset.first { + self.tv.deleteSections(IndexSet(firstChangeset.elementDeleted.map { $0.element }), with: .automatic) + self.tv.insertSections(IndexSet(firstChangeset.elementInserted.map { $0.element }), with: .automatic) } - self.tv.insertRows(at: insPaths, with: .automatic) - let modPaths = modifications.map(self.indexPath) - if let modPath = modPaths.first { - if oldSectionsCount > self.sections.count { - self.tv.deleteSections(IndexSet(integer: modPath.section), with: .automatic) - } else if oldSectionsCount < self.sections.count { - self.tv.insertSections(IndexSet(integer: modPath.section), with: .automatic) - } - } - self.tv.reloadRows(at: modPaths, with: .automatic) self.tv.endUpdates() case .error(let err): print("Realm observer error: \(err)") @@ -58,12 +50,12 @@ class RealmSectionedDataSource: NSObject, UITableViewDataSource where func indexPath(by index: Int) -> IndexPath { var sectionStartIndex = 0 for (i, section) in self.sections.enumerated() { - if index < sectionStartIndex + section.items.count { + if index < sectionStartIndex + section.elements.count { print("Index \(index) -> (\(i), \(index - sectionStartIndex))") return IndexPath(row: index - sectionStartIndex, section: i) } - sectionStartIndex += section.items.count + sectionStartIndex += section.elements.count } return IndexPath(row: 0, section: 0) @@ -76,7 +68,7 @@ class RealmSectionedDataSource: NSObject, UITableViewDataSource where } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return self.sections[section].items.count + return self.sections[section].elements.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -84,7 +76,7 @@ class RealmSectionedDataSource: NSObject, UITableViewDataSource where return UITableViewCell() } - let item = self.sections[indexPath.section].items[indexPath.row] + let item = self.sections[indexPath.section].elements[indexPath.row] cell.configure(with: item) return cell } @@ -96,6 +88,6 @@ class RealmSectionedDataSource: NSObject, UITableViewDataSource where // MARK: - Public func item(at indexPath: IndexPath) -> Item { - return self.sections[indexPath.section].items[indexPath.row] + return self.sections[indexPath.section].elements[indexPath.row] } } diff --git a/AutoCat/Utils/RxSectionedDataSource.swift b/AutoCat/Utils/RxSectionedDataSource.swift new file mode 100644 index 0000000..722e0ac --- /dev/null +++ b/AutoCat/Utils/RxSectionedDataSource.swift @@ -0,0 +1,61 @@ +import UIKit +import DifferenceKit +import RxSwift +import RxCocoa + +class RxSectionedDataSource: NSObject, UITableViewDataSource where Item: Dated & Equatable & Differentiable, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item { + private var tv: UITableView + private var cellIdentifier: String + private var sections: [DateSection] = [] + + init(table: UITableView, cellIdentifier: String = String(describing: Cell.self)) { + self.tv = table + self.cellIdentifier = cellIdentifier + super.init() + self.tv.dataSource = self + } + + // MARK: - UITableViewDataSource + + func numberOfSections(in tableView: UITableView) -> Int { + return self.sections.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.sections[section].elements.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath) as? Cell else { + return UITableViewCell() + } + + let item = self.sections[indexPath.section].elements[indexPath.row] + cell.configure(with: item) + return cell + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return self.sections[section].header + } + + // MARK: - Public + + func item(at indexPath: IndexPath) -> Item { + return self.sections[indexPath.section].elements[indexPath.row] + } + + func set(item: Item, at indexPath: IndexPath) { + self.sections[indexPath.section].elements[indexPath.row] = item + } + + var data: Binder<[Item]> { + return Binder(self) { datasource, data in + let newSections = data.groupedByDate() + let changeset = StagedChangeset(source: self.sections, target: newSections) + self.tv.reload(using: changeset, with: .automatic) { newSects in + self.sections = newSects + } + } + } +}