mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-18 06:57:15 +01:00
Allow custom views to consume ListSnapshot diffable data
This commit is contained in:
@@ -141,6 +141,13 @@
|
||||
B51260941E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */; };
|
||||
B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */; };
|
||||
B51260961E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */; };
|
||||
B514EF0E23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; };
|
||||
B514EF0F23A8DB180093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; };
|
||||
B514EF1023A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; };
|
||||
B514EF1123A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; };
|
||||
B514EF1223A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; };
|
||||
B514EF1323A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; };
|
||||
B514EF1423A8DB1E0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; };
|
||||
B51B5C2B22D43931009FA3BA /* String+KeyPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51B5C2A22D43931009FA3BA /* String+KeyPaths.swift */; };
|
||||
B51B5C2D22D43E38009FA3BA /* KeyPath+KeyPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51B5C2C22D43E38009FA3BA /* KeyPath+KeyPaths.swift */; };
|
||||
B51FE5AB1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */; };
|
||||
@@ -668,6 +675,7 @@
|
||||
B5D33A041E96012400C880DE /* Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D33A001E96012400C880DE /* Relationship.swift */; };
|
||||
B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; };
|
||||
B5D39A0219FD00C9000E91BB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0119FD00C9000E91BB /* Foundation.framework */; };
|
||||
B5D4A6B723A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; };
|
||||
B5D7A5B61CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; };
|
||||
B5D7A5B81CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; };
|
||||
B5D7A5B91CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; };
|
||||
@@ -906,6 +914,7 @@
|
||||
B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStoreObject+Convenience.swift"; sourceTree = "<group>"; };
|
||||
B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSEntityDescription+DynamicModel.swift"; sourceTree = "<group>"; };
|
||||
B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Internals.EntityIdentifier.swift; sourceTree = "<group>"; };
|
||||
B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.Target.swift; sourceTree = "<group>"; };
|
||||
B51B5C2A22D43931009FA3BA /* String+KeyPaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+KeyPaths.swift"; sourceTree = "<group>"; };
|
||||
B51B5C2C22D43E38009FA3BA /* KeyPath+KeyPaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyPath+KeyPaths.swift"; sourceTree = "<group>"; };
|
||||
B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+CustomDebugStringConvertible.swift"; sourceTree = "<group>"; };
|
||||
@@ -1042,6 +1051,7 @@
|
||||
B5D33A001E96012400C880DE /* Relationship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Relationship.swift; sourceTree = "<group>"; };
|
||||
B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
|
||||
B5D39A0119FD00C9000E91BB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.BaseAdapter.swift; sourceTree = "<group>"; };
|
||||
B5D7A5B51CA3BF8F005C752B /* CSInto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSInto.swift; sourceTree = "<group>"; };
|
||||
B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataStack+DataSources.swift"; sourceTree = "<group>"; };
|
||||
B5D8CA7A2346EC550055D7D1 /* ListPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListPublisherTests.swift; sourceTree = "<group>"; };
|
||||
@@ -1607,6 +1617,8 @@
|
||||
B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */,
|
||||
B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */,
|
||||
B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */,
|
||||
B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */,
|
||||
B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */,
|
||||
B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */,
|
||||
B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */,
|
||||
B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionView-AppKit.swift */,
|
||||
@@ -2089,6 +2101,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */,
|
||||
B5DE5230230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */,
|
||||
B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */,
|
||||
B5ECDBF91CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */,
|
||||
@@ -2126,7 +2139,6 @@
|
||||
B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */,
|
||||
B5DE522B230BD7CC00A22534 /* Internals.swift in Sources */,
|
||||
B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */,
|
||||
B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */,
|
||||
B5ECDC1D1CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */,
|
||||
B5C976E31C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */,
|
||||
B53FBA121CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
|
||||
@@ -2224,6 +2236,7 @@
|
||||
B5BF7FBC234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
|
||||
B5215CA41FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
|
||||
B5D33A011E96012400C880DE /* Relationship.swift in Sources */,
|
||||
B514EF0E23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift in Sources */,
|
||||
B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */,
|
||||
B56923C91EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */,
|
||||
B56923E41EB827F5007C4DC9 /* CustomSchemaMappingProvider.swift in Sources */,
|
||||
@@ -2240,6 +2253,7 @@
|
||||
B512607F1E97A18000402229 /* CoreStoreObject+Convenience.swift in Sources */,
|
||||
B5E84EF81AFF846E0064E85B /* CoreStore+Transaction.swift in Sources */,
|
||||
B5E84F301AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift in Sources */,
|
||||
B5D4A6B723A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift in Sources */,
|
||||
B5831B7A1F34ACBA00A9F647 /* Transformable.swift in Sources */,
|
||||
B5BF7FB2234C97910070E741 /* DiffableDataSource.swift in Sources */,
|
||||
B546F9691C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
|
||||
@@ -2387,6 +2401,7 @@
|
||||
B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
|
||||
B5DBE2D31C991B3E00B5CEFA /* CSDataStack.swift in Sources */,
|
||||
82BA18B41C4BBD3900A0916E /* BaseDataTransaction+Importing.swift in Sources */,
|
||||
B514EF1223A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */,
|
||||
B53FBA1A1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */,
|
||||
82BA18CA1C4BBD5900A0916E /* MigrationResult.swift in Sources */,
|
||||
B5519A5A1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */,
|
||||
@@ -2435,6 +2450,7 @@
|
||||
B5215CAA1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
|
||||
B50E175823517DE4004F033C /* Differentiable.swift in Sources */,
|
||||
82BA18BA1C4BBD4A00A0916E /* Select.swift in Sources */,
|
||||
B514EF0F23A8DB180093DBA4 /* DiffableDataSource.Target.swift in Sources */,
|
||||
B52F744B1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
|
||||
B5E2222C1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */,
|
||||
@@ -2538,9 +2554,12 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5BF7FB5234C97910070E741 /* DiffableDataSource.swift in Sources */,
|
||||
B5220E1E1D13080D009BC71E /* CSListMonitor.swift in Sources */,
|
||||
B5DBE2D01C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
|
||||
B5AA37F4235C28EE00FFD4B9 /* DiffableDataSource.CollectionView-AppKit.swift in Sources */,
|
||||
B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */,
|
||||
B5635D172356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */,
|
||||
B5DBE2D01C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
|
||||
B5CA2B0B1F7E5ACA004B1936 /* WhereClauseType.swift in Sources */,
|
||||
B56923F81EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
|
||||
B52DD1BE1BE1F94300949AFE /* Progress+Convenience.swift in Sources */,
|
||||
@@ -2581,7 +2600,6 @@
|
||||
B50564D62350CC3100482308 /* PropertyProtocol.swift in Sources */,
|
||||
B5831B781F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */,
|
||||
B52DD1981BE1F92500949AFE /* CoreStore+Setup.swift in Sources */,
|
||||
B5BF7FB5234C97910070E741 /* DiffableDataSource.swift in Sources */,
|
||||
B5D339F41E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
|
||||
B5220E241D13085E009BC71E /* NSFetchedResultsController+Convenience.swift in Sources */,
|
||||
B559CD471CAA8B6300E4D58B /* CSSetupResult.swift in Sources */,
|
||||
@@ -2609,6 +2627,7 @@
|
||||
B53FBA1C1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */,
|
||||
B5F8496F234898240029D57B /* ListSnapshot.swift in Sources */,
|
||||
B5831F452212700500D8604C /* Where.Expression.swift in Sources */,
|
||||
B514EF1423A8DB1E0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */,
|
||||
B51260961E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
|
||||
B5ECDBE31CA6BB2B00C7F112 /* CSBaseDataTransaction+Querying.swift in Sources */,
|
||||
B5ECDC031CA80CBA00C7F112 /* CSWhere.swift in Sources */,
|
||||
@@ -2657,6 +2676,7 @@
|
||||
B5220E1A1D130791009BC71E /* Internals.CoreStoreFetchedResultsController.swift in Sources */,
|
||||
B5215CAC1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
|
||||
B53FBA0F1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
|
||||
B514EF1123A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */,
|
||||
B50E175A23517DE4004F033C /* Differentiable.swift in Sources */,
|
||||
B52F744D1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B52DD19A1BE1F92800949AFE /* CoreStore+Logging.swift in Sources */,
|
||||
@@ -2698,7 +2718,6 @@
|
||||
B52F74401E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
|
||||
18166886232B9ED20097C275 /* KeyPath+KeyPaths.swift in Sources */,
|
||||
B5E8A72321C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
|
||||
B5AA37F4235C28EE00FFD4B9 /* DiffableDataSource.CollectionView-AppKit.swift in Sources */,
|
||||
B50E175F2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
|
||||
B5474D182227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
|
||||
B501322E2346A9B100FC238B /* ListPublisher.swift in Sources */,
|
||||
@@ -2716,7 +2735,6 @@
|
||||
B5D339EA1E9493A500C880DE /* Entity.swift in Sources */,
|
||||
B5BF7FC9234D7E460070E741 /* ObjectSnapshot.swift in Sources */,
|
||||
B52DD1AA1BE1F93500949AFE /* TypeErasedClauses.swift in Sources */,
|
||||
B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */,
|
||||
B53FBA021CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
|
||||
B51FE5AF1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */,
|
||||
);
|
||||
@@ -2835,6 +2853,7 @@
|
||||
B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
|
||||
B53FBA011CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
|
||||
B5DBE2D41C991B3E00B5CEFA /* CSDataStack.swift in Sources */,
|
||||
B514EF1323A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */,
|
||||
B50392FA1C47963F009900CA /* NSManagedObject+Transaction.swift in Sources */,
|
||||
B53FBA1B1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */,
|
||||
B5519A5B1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */,
|
||||
@@ -2883,6 +2902,7 @@
|
||||
B56321A21BD65216006C9394 /* ListObserver.swift in Sources */,
|
||||
B50E175923517DE4004F033C /* Differentiable.swift in Sources */,
|
||||
B5215CAB1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
|
||||
B514EF1023A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */,
|
||||
B563218A1BD65216006C9394 /* SynchronousDataTransaction.swift in Sources */,
|
||||
B52F744C1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B5AEFAB71C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
|
||||
|
||||
@@ -51,7 +51,7 @@ final class CollectionViewDemoViewController: UICollectionViewController {
|
||||
]
|
||||
self.filterBarButton = filterBarButton
|
||||
|
||||
self.dataSource = DiffableDataSource.CollectionView<Palette>(
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Palette>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: ColorsDemo.stack,
|
||||
cellProvider: { (collectionView, indexPath, palette) in
|
||||
@@ -118,7 +118,7 @@ final class CollectionViewDemoViewController: UICollectionViewController {
|
||||
// MARK: Private
|
||||
|
||||
private var filterBarButton: UIBarButtonItem?
|
||||
private var dataSource: DiffableDataSource.CollectionView<Palette>?
|
||||
private var dataSource: DiffableDataSource.CollectionViewAdapter<Palette>?
|
||||
|
||||
deinit {
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ final class ListObserverDemoViewController: UITableViewController {
|
||||
|
||||
// MARK: - EditableDataSource
|
||||
|
||||
final class EditableDataSource: DiffableDataSource.TableView<Palette> {
|
||||
final class EditableDataSource: DiffableDataSource.TableViewAdapter<Palette> {
|
||||
|
||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
|
||||
@@ -130,7 +130,7 @@ final class ListObserverDemoViewController: UITableViewController {
|
||||
// MARK: Private
|
||||
|
||||
private var filterBarButton: UIBarButtonItem?
|
||||
private var dataSource: DiffableDataSource.TableView<Palette>?
|
||||
private var dataSource: DiffableDataSource.TableViewAdapter<Palette>?
|
||||
|
||||
deinit {
|
||||
|
||||
|
||||
@@ -1506,9 +1506,9 @@ Note that the owner instance will not be retained. You may call `ListPublisher.r
|
||||
|
||||
The `ListSnapshot` returned from the `ListPublisher.snapshot` property returns a full-copy `struct` of all sections and `NSManagedObject` items in the list. This is ideal for managing states as they are thread-safe and are not affected by further changes to the result set. `ListPublisher` automatically updates its `snapshot` value to the latest state of the fetch.
|
||||
|
||||
Unlike `ListMonitor`s (See [`ListMonitor` examples](#observe-detailed-list-changes) below), a `ListPublisher` does not track detailed inserts, deletes, and moves. In return, a `ListPublisher` is a lot more lightweight and are designed to work well with `DiffableDataSource.TableView`s and `DiffableDataSource.CollectionView`s:
|
||||
Unlike `ListMonitor`s (See [`ListMonitor` examples](#observe-detailed-list-changes) below), a `ListPublisher` does not track detailed inserts, deletes, and moves. In return, a `ListPublisher` is a lot more lightweight and are designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s:
|
||||
```swift
|
||||
self.dataSource = DiffableDataSource.CollectionView<Person>(
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (collectionView, indexPath, person) in
|
||||
|
||||
197
Sources/DiffableDataSource.BaseAdapter.swift
Normal file
197
Sources/DiffableDataSource.BaseAdapter.swift
Normal file
@@ -0,0 +1,197 @@
|
||||
//
|
||||
// DiffableDataSource.BaseAdapter.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2018 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: - BaseAdapter
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.BaseAdapter` serves as a superclass for consumers of `ListPublisher` and `ListSnapshot` diffable data.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
|
||||
tableView: self.tableView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (tableView, indexPath, person) in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
|
||||
cell.setPerson(person)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
```
|
||||
The dataSource can then apply changes from a `ListPublisher` as shown:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
|
||||
*/
|
||||
open class BaseAdapter<O: DynamicObject, T: Target>: NSObject {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
The object type represented by this dataSource
|
||||
*/
|
||||
public typealias ObjectType = O
|
||||
|
||||
/**
|
||||
The target to be updated by this dataSource
|
||||
*/
|
||||
public let target: T
|
||||
|
||||
/**
|
||||
The `DataStack` where object fetches are performed
|
||||
*/
|
||||
public let dataStack: DataStack
|
||||
|
||||
/**
|
||||
Initializes the `DiffableDataSource.BaseAdapter` object. This instance needs to be held on (retained) for as long as the target's lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.TableViewAdapterAdapter<Person>(
|
||||
tableView: self.tableView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (tableView, indexPath, person) in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
|
||||
cell.setPerson(person)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
```
|
||||
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableViewAdapter`.
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
|
||||
*/
|
||||
public init(target: T, dataStack: DataStack) {
|
||||
|
||||
self.target = target
|
||||
self.dataStack = dataStack
|
||||
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
|
||||
}
|
||||
|
||||
/**
|
||||
Reloads the `UITableView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
If the `defaultRowAnimation` is configured to, animations are also applied accordingly.
|
||||
|
||||
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
|
||||
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
|
||||
*/
|
||||
open func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true, completion: @escaping () -> Void = {}) {
|
||||
|
||||
let diffableSnapshot = snapshot.diffableSnapshot
|
||||
self.dispatcher.apply(
|
||||
diffableSnapshot,
|
||||
target: self.target,
|
||||
animatingDifferences: animatingDifferences,
|
||||
performUpdates: { target, changeset, setSections in
|
||||
|
||||
target.reload(
|
||||
using: changeset,
|
||||
animated: animatingDifferences,
|
||||
setData: setSections
|
||||
)
|
||||
},
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of sections
|
||||
|
||||
- parameter indexPath: the `IndexPath` to search for
|
||||
- returns: the number of sections
|
||||
*/
|
||||
public func numberOfSections() -> Int {
|
||||
|
||||
return self.dispatcher.numberOfSections()
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of items at the specified section, or `nil` if the section is not found
|
||||
|
||||
- parameter section: the section index to search for
|
||||
- returns: the number of items at the specified section, or `nil` if the section is not found
|
||||
*/
|
||||
public func numberOfItems(inSection section: Int) -> Int? {
|
||||
|
||||
return self.dispatcher.numberOfItems(inSection: section)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the section identifier at the specified index, or `nil` if not found
|
||||
|
||||
- parameter section: the section index to search for
|
||||
- returns: the section identifier at the specified indec, or `nil` if not found
|
||||
*/
|
||||
public func sectionID(for section: Int) -> String? {
|
||||
|
||||
return self.dispatcher.sectionIdentifier(inSection: section)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
|
||||
- parameter indexPath: the `IndexPath` to search for
|
||||
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
*/
|
||||
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
return self.dispatcher.itemIdentifier(for: indexPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
|
||||
- parameter itemID: the object identifier to search for
|
||||
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
*/
|
||||
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
|
||||
|
||||
return self.dispatcher.indexPath(for: itemID)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let dispatcher: Internals.DiffableDataUIDispatcher<O>
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// DiffableDataSource.CollectionView-AppKit.swift
|
||||
// DiffableDataSource.CollectionViewAdapter-AppKit.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2018 John Rommel Estropia
|
||||
@@ -36,10 +36,10 @@ extension DiffableDataSource {
|
||||
// MARK: - CollectionView
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.CollectionView` serves as a `NSCollectionViewDataSource` that handles `ListPublisher` snapshots for a `NSCollectionView`. Subclasses of `DiffableDataSource.CollectionView` may override some `NSCollectionViewDataSource` methods as needed.
|
||||
The `DiffableDataSource.CollectionView` instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
|
||||
The `DiffableDataSource.CollectionViewAdapter` serves as a `NSCollectionViewDataSource` that handles `ListPublisher` snapshots for a `NSCollectionView`. Subclasses of `DiffableDataSource.CollectionViewAdapter` may override some `NSCollectionViewDataSource` methods as needed.
|
||||
The `DiffableDataSource.CollectionViewAdapter` instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.CollectionView<Person>(
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
itemProvider: { (collectionView, indexPath, person) in
|
||||
@@ -58,22 +58,17 @@ extension DiffableDataSource {
|
||||
)
|
||||
}
|
||||
```
|
||||
`DiffableDataSource.CollectionView` fully handles the reload animations.
|
||||
`DiffableDataSource.CollectionViewAdapter` fully handles the reload animations.
|
||||
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
|
||||
*/
|
||||
open class CollectionView<O: DynamicObject>: NSObject, NSCollectionViewDataSource {
|
||||
open class CollectionViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultCollectionViewTarget<NSCollectionView>>, NSCollectionViewDataSource {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
The object type represented by this dataSource
|
||||
*/
|
||||
public typealias ObjectType = O
|
||||
|
||||
/**
|
||||
Initializes the `DiffableDataSource.CollectionView`. This instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
|
||||
Initializes the `DiffableDataSource.CollectionViewAdapter`. This instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.CollectionView<Person>(
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
itemProvider: { (collectionView, indexPath, person) in
|
||||
@@ -83,78 +78,20 @@ extension DiffableDataSource {
|
||||
}
|
||||
)
|
||||
```
|
||||
- parameter collectionView: the `NSCollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionView`.
|
||||
- parameter collectionView: the `NSCollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionViewAdapter`.
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
|
||||
*/
|
||||
@nonobjc
|
||||
public init(collectionView: NSCollectionView, dataStack: DataStack, itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?, supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }) {
|
||||
|
||||
self.collectionView = collectionView
|
||||
self.itemProvider = itemProvider
|
||||
self.supplementaryViewProvider = supplementaryViewProvider
|
||||
self.dataStack = dataStack
|
||||
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
|
||||
|
||||
super.init()
|
||||
super.init(target: .init(collectionView), dataStack: dataStack)
|
||||
|
||||
collectionView.dataSource = self
|
||||
}
|
||||
|
||||
/**
|
||||
Reloads the `NSCollectionView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
|
||||
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
|
||||
*/
|
||||
public func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true) {
|
||||
|
||||
let diffableSnapshot = snapshot.diffableSnapshot
|
||||
self.dispatcher.apply(
|
||||
diffableSnapshot as! Internals.DiffableDataSourceSnapshot,
|
||||
view: self.collectionView,
|
||||
animatingDifferences: animatingDifferences,
|
||||
performUpdates: { collectionView, changeset, setSections in
|
||||
|
||||
collectionView.reload(
|
||||
using: changeset,
|
||||
setData: setSections
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
|
||||
- parameter indexPath: the `IndexPath` to search for
|
||||
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
*/
|
||||
@nonobjc
|
||||
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
return self.dispatcher.itemIdentifier(for: indexPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
|
||||
- parameter itemID: the object identifier to search for
|
||||
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
*/
|
||||
@nonobjc
|
||||
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
|
||||
|
||||
return self.dispatcher.indexPath(for: itemID)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSCollectionViewDataSource
|
||||
@@ -162,19 +99,19 @@ extension DiffableDataSource {
|
||||
@objc
|
||||
public dynamic func numberOfSections(in collectionView: NSCollectionView) -> Int {
|
||||
|
||||
return self.dispatcher.numberOfSections()
|
||||
return self.numberOfSections()
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
|
||||
return self.dispatcher.numberOfItems(inSection: section)
|
||||
return self.numberOfItems(inSection: section) ?? 0
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
|
||||
|
||||
guard let objectID = self.dispatcher.itemIdentifier(for: indexPath) else {
|
||||
guard let objectID = self.itemID(for: indexPath) else {
|
||||
|
||||
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
|
||||
}
|
||||
@@ -202,94 +139,93 @@ extension DiffableDataSource {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private weak var collectionView: NSCollectionView?
|
||||
|
||||
private let dataStack: DataStack
|
||||
private let itemProvider: (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?
|
||||
private let supplementaryViewProvider: (NSCollectionView, String, IndexPath) -> NSView?
|
||||
private let dispatcher: Internals.DiffableDataUIDispatcher<O>
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DefaultCollectionViewTarget
|
||||
|
||||
public struct DefaultCollectionViewTarget<T: NSCollectionView>: Target {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public typealias Base = T
|
||||
|
||||
public internal(set) weak var base: Base?
|
||||
|
||||
public init(_ base: Base) {
|
||||
|
||||
self.base = base
|
||||
}
|
||||
|
||||
|
||||
// MARK: DiffableDataSource.Target:
|
||||
|
||||
public var shouldSuspendBatchUpdates: Bool {
|
||||
|
||||
return self.base?.window == nil
|
||||
}
|
||||
|
||||
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.deleteSections(indices)
|
||||
}
|
||||
|
||||
public func insertSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.insertSections(indices)
|
||||
}
|
||||
|
||||
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.reloadSections(indices)
|
||||
}
|
||||
|
||||
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
||||
|
||||
self.base?.moveSection(index, toSection: newIndex)
|
||||
}
|
||||
|
||||
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.deleteItems(at: Set(indexPaths))
|
||||
}
|
||||
|
||||
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.insertItems(at: Set(indexPaths))
|
||||
}
|
||||
|
||||
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.reloadItems(at: Set(indexPaths))
|
||||
}
|
||||
|
||||
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
||||
|
||||
self.base?.moveItem(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
|
||||
|
||||
self.base?.animator().performBatchUpdates(updates, completionHandler: nil)
|
||||
}
|
||||
|
||||
public func reloadData() {
|
||||
|
||||
self.base?.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSCollectionView
|
||||
// MARK: - Deprecated
|
||||
|
||||
extension NSCollectionView {
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
@nonobjc
|
||||
fileprivate func reload<C, O>(
|
||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void
|
||||
) {
|
||||
|
||||
if case .none = window, let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
for changeset in stagedChangeset {
|
||||
|
||||
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
self.animator().performBatchUpdates(
|
||||
{
|
||||
setData(changeset.data)
|
||||
|
||||
if !changeset.sectionDeleted.isEmpty {
|
||||
|
||||
self.deleteSections(IndexSet(changeset.sectionDeleted))
|
||||
}
|
||||
if !changeset.sectionInserted.isEmpty {
|
||||
|
||||
self.insertSections(IndexSet(changeset.sectionInserted))
|
||||
}
|
||||
if !changeset.sectionUpdated.isEmpty {
|
||||
|
||||
self.reloadSections(IndexSet(changeset.sectionUpdated))
|
||||
}
|
||||
for (source, target) in changeset.sectionMoved {
|
||||
|
||||
self.moveSection(source, toSection: target)
|
||||
}
|
||||
if !changeset.elementDeleted.isEmpty {
|
||||
|
||||
self.deleteItems(
|
||||
at: Set(changeset.elementDeleted.map { IndexPath(item: $0.element, section: $0.section) })
|
||||
)
|
||||
}
|
||||
if !changeset.elementInserted.isEmpty {
|
||||
|
||||
self.insertItems(
|
||||
at: Set(changeset.elementInserted.map { IndexPath(item: $0.element, section: $0.section) })
|
||||
)
|
||||
}
|
||||
if !changeset.elementUpdated.isEmpty {
|
||||
|
||||
self.reloadItems(
|
||||
at: Set(changeset.elementUpdated.map { IndexPath(item: $0.element, section: $0.section) })
|
||||
)
|
||||
}
|
||||
for (source, target) in changeset.elementMoved {
|
||||
|
||||
self.moveItem(
|
||||
at: IndexPath(item: source.element, section: source.section),
|
||||
to: IndexPath(item: target.element, section: target.section)
|
||||
)
|
||||
}
|
||||
},
|
||||
completionHandler: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
@available(*, deprecated, renamed: "CollectionViewAdapter")
|
||||
public typealias CollectionView = CollectionViewAdapter
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// DiffableDataSource.CollectionView-UIKit.swift
|
||||
// DiffableDataSource.CollectionViewAdapter-UIKit.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2018 John Rommel Estropia
|
||||
@@ -36,10 +36,10 @@ extension DiffableDataSource {
|
||||
// MARK: - CollectionView
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.CollectionView` serves as a `UICollectionViewDataSource` that handles `ListPublisher` snapshots for a `UICollectionView`. Subclasses of `DiffableDataSource.CollectionView` may override some `UICollectionViewDataSource` methods as needed.
|
||||
The `DiffableDataSource.CollectionView` instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
|
||||
The `DiffableDataSource.CollectionViewAdapter` serves as a `UICollectionViewDataSource` that handles `ListPublisher` snapshots for a `UICollectionView`. Subclasses of `DiffableDataSource.CollectionViewAdapter` may override some `UICollectionViewDataSource` methods as needed.
|
||||
The `DiffableDataSource.CollectionViewAdapter` instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.CollectionView<Person>(
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (collectionView, indexPath, person) in
|
||||
@@ -58,22 +58,17 @@ extension DiffableDataSource {
|
||||
)
|
||||
}
|
||||
```
|
||||
`DiffableDataSource.CollectionView` fully handles the reload animations.
|
||||
`DiffableDataSource.CollectionViewAdapter` fully handles the reload animations.
|
||||
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
|
||||
*/
|
||||
open class CollectionView<O: DynamicObject>: NSObject, UICollectionViewDataSource {
|
||||
open class CollectionViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultCollectionViewTarget<UICollectionView>>, UICollectionViewDataSource {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
The object type represented by this dataSource
|
||||
*/
|
||||
public typealias ObjectType = O
|
||||
|
||||
/**
|
||||
Initializes the `DiffableDataSource.CollectionView`. This instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
|
||||
Initializes the `DiffableDataSource.CollectionViewAdapter`. This instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.CollectionView<Person>(
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (collectionView, indexPath, person) in
|
||||
@@ -83,79 +78,20 @@ extension DiffableDataSource {
|
||||
}
|
||||
)
|
||||
```
|
||||
- parameter collectionView: the `UICollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionView`.
|
||||
- parameter collectionView: the `UICollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionViewAdapter`.
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter cellProvider: a closure that configures and returns the `UICollectionViewCell` for the object
|
||||
- parameter supplementaryViewProvider: an optional closure for providing `UICollectionReusableView` supplementary views. If not set, defaults to returning `nil`
|
||||
*/
|
||||
@nonobjc
|
||||
public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) {
|
||||
|
||||
self.collectionView = collectionView
|
||||
self.cellProvider = cellProvider
|
||||
self.supplementaryViewProvider = supplementaryViewProvider
|
||||
self.dataStack = dataStack
|
||||
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
|
||||
|
||||
super.init()
|
||||
super.init(target: .init(collectionView), dataStack: dataStack)
|
||||
|
||||
collectionView.dataSource = self
|
||||
}
|
||||
|
||||
/**
|
||||
Reloads the `UICollectionView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
|
||||
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
|
||||
*/
|
||||
public func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true) {
|
||||
|
||||
let diffableSnapshot = snapshot.diffableSnapshot
|
||||
self.dispatcher.apply(
|
||||
diffableSnapshot,
|
||||
view: self.collectionView,
|
||||
animatingDifferences: animatingDifferences,
|
||||
performUpdates: { collectionView, changeset, setSections in
|
||||
|
||||
collectionView.reload(
|
||||
using: changeset,
|
||||
setData: setSections
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
|
||||
- parameter indexPath: the `IndexPath` to search for
|
||||
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
*/
|
||||
@nonobjc
|
||||
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
return self.dispatcher.itemIdentifier(for: indexPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
|
||||
- parameter itemID: the object identifier to search for
|
||||
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
*/
|
||||
@nonobjc
|
||||
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
|
||||
|
||||
return self.dispatcher.indexPath(for: itemID)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UICollectionViewDataSource
|
||||
@@ -163,19 +99,19 @@ extension DiffableDataSource {
|
||||
@objc
|
||||
public dynamic func numberOfSections(in collectionView: UICollectionView) -> Int {
|
||||
|
||||
return self.dispatcher.numberOfSections()
|
||||
return self.numberOfSections()
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
|
||||
return self.dispatcher.numberOfItems(inSection: section)
|
||||
return self.numberOfItems(inSection: section) ?? 0
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
|
||||
guard let objectID = self.dispatcher.itemIdentifier(for: indexPath) else {
|
||||
guard let objectID = self.itemID(for: indexPath) else {
|
||||
|
||||
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
|
||||
}
|
||||
@@ -203,95 +139,93 @@ extension DiffableDataSource {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private weak var collectionView: UICollectionView?
|
||||
|
||||
private let dataStack: DataStack
|
||||
private let cellProvider: (UICollectionView, IndexPath, O) -> UICollectionViewCell?
|
||||
private let supplementaryViewProvider: (UICollectionView, String, IndexPath) -> UICollectionReusableView?
|
||||
private let dispatcher: Internals.DiffableDataUIDispatcher<O>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UICollectionView
|
||||
// MARK: - DefaultCollectionViewTarget
|
||||
|
||||
extension UICollectionView {
|
||||
public struct DefaultCollectionViewTarget<T: UICollectionView>: Target {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
@nonobjc
|
||||
fileprivate func reload<C, O>(
|
||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void
|
||||
) {
|
||||
// MARK: Public
|
||||
|
||||
if case .none = window, let data = stagedChangeset.last?.data {
|
||||
public typealias Base = T
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
public internal(set) weak var base: Base?
|
||||
|
||||
public init(_ base: Base) {
|
||||
|
||||
self.base = base
|
||||
}
|
||||
for changeset in stagedChangeset {
|
||||
|
||||
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
self.performBatchUpdates(
|
||||
{
|
||||
setData(changeset.data)
|
||||
// MARK: DiffableDataSource.Target
|
||||
|
||||
if !changeset.sectionDeleted.isEmpty {
|
||||
public var shouldSuspendBatchUpdates: Bool {
|
||||
|
||||
self.deleteSections(IndexSet(changeset.sectionDeleted))
|
||||
}
|
||||
if !changeset.sectionInserted.isEmpty {
|
||||
return self.base?.window == nil
|
||||
}
|
||||
|
||||
self.insertSections(IndexSet(changeset.sectionInserted))
|
||||
}
|
||||
if !changeset.sectionUpdated.isEmpty {
|
||||
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.reloadSections(IndexSet(changeset.sectionUpdated))
|
||||
}
|
||||
for (source, target) in changeset.sectionMoved {
|
||||
self.base?.deleteSections(indices)
|
||||
}
|
||||
|
||||
self.moveSection(source, toSection: target)
|
||||
}
|
||||
if !changeset.elementDeleted.isEmpty {
|
||||
public func insertSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.deleteItems(
|
||||
at: changeset.elementDeleted.map { IndexPath(item: $0.element, section: $0.section) }
|
||||
)
|
||||
}
|
||||
if !changeset.elementInserted.isEmpty {
|
||||
self.base?.insertSections(indices)
|
||||
}
|
||||
|
||||
self.insertItems(
|
||||
at: changeset.elementInserted.map { IndexPath(item: $0.element, section: $0.section) }
|
||||
)
|
||||
}
|
||||
if !changeset.elementUpdated.isEmpty {
|
||||
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.reloadItems(
|
||||
at: changeset.elementUpdated.map { IndexPath(item: $0.element, section: $0.section) }
|
||||
)
|
||||
}
|
||||
for (source, target) in changeset.elementMoved {
|
||||
self.base?.reloadSections(indices)
|
||||
}
|
||||
|
||||
self.moveItem(
|
||||
at: IndexPath(item: source.element, section: source.section),
|
||||
to: IndexPath(item: target.element, section: target.section)
|
||||
)
|
||||
}
|
||||
},
|
||||
completion: nil
|
||||
)
|
||||
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
||||
|
||||
self.base?.moveSection(index, toSection: newIndex)
|
||||
}
|
||||
|
||||
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.deleteItems(at: indexPaths)
|
||||
}
|
||||
|
||||
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.insertItems(at: indexPaths)
|
||||
}
|
||||
|
||||
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.reloadItems(at: indexPaths)
|
||||
}
|
||||
|
||||
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
||||
|
||||
self.base?.moveItem(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
|
||||
|
||||
self.base?.performBatchUpdates(updates, completion: nil)
|
||||
}
|
||||
|
||||
public func reloadData() {
|
||||
|
||||
self.base?.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Deprecated
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
@available(*, deprecated, renamed: "CollectionViewAdapter")
|
||||
public typealias CollectionView = CollectionViewAdapter
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// DiffableDataSource.TableView-UIKit.swift
|
||||
// DiffableDataSource.TableViewAdapter-UIKit.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2018 John Rommel Estropia
|
||||
@@ -33,13 +33,13 @@ import CoreData
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: - TableView
|
||||
// MARK: - TableViewAdapter
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.TableView` serves as a `UITableViewDataSource` that handles `ListPublisher` snapshots for a `UITableView`. Subclasses of `DiffableDataSource.TableView` may override some `UITableViewDataSource` methods as needed.
|
||||
The `DiffableDataSource.TableView` instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
|
||||
The `DiffableDataSource.TableViewAdapterAdapter` serves as a `UITableViewDataSource` that handles `ListPublisher` snapshots for a `UITableView`. Subclasses of `DiffableDataSource.TableViewAdapter` may override some `UITableViewDataSource` methods as needed.
|
||||
The `DiffableDataSource.TableViewAdapterAdapter` instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.TableView<Person>(
|
||||
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
|
||||
tableView: self.tableView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (tableView, indexPath, person) in
|
||||
@@ -58,31 +58,17 @@ extension DiffableDataSource {
|
||||
)
|
||||
}
|
||||
```
|
||||
`DiffableDataSource.TableView` fully handles the reload animations. To turn change the default animation, set the `defaultRowAnimation`.
|
||||
`DiffableDataSource.TableViewAdapter` fully handles the reload animations.
|
||||
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
|
||||
*/
|
||||
open class TableView<O: DynamicObject>: NSObject, UITableViewDataSource {
|
||||
|
||||
// MARK: Open
|
||||
open class TableViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultTableViewTarget<UITableView>>, UITableViewDataSource {
|
||||
|
||||
// MARK: Publi
|
||||
|
||||
/**
|
||||
The animation style for row changes
|
||||
*/
|
||||
@nonobjc
|
||||
open var defaultRowAnimation: UITableView.RowAnimation = .automatic
|
||||
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
The object type represented by this dataSource
|
||||
*/
|
||||
public typealias ObjectType = O
|
||||
|
||||
/**
|
||||
Initializes the `DiffableDataSource.TableView`. This instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
|
||||
Initializes the `DiffableDataSource.TableViewAdapter`. This instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.TableView<Person>(
|
||||
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
|
||||
tableView: self.tableView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (tableView, indexPath, person) in
|
||||
@@ -92,78 +78,24 @@ extension DiffableDataSource {
|
||||
}
|
||||
)
|
||||
```
|
||||
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableView`.
|
||||
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableViewAdapter`.
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
|
||||
*/
|
||||
@nonobjc
|
||||
public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?) {
|
||||
|
||||
self.tableView = tableView
|
||||
self.cellProvider = cellProvider
|
||||
self.dataStack = dataStack
|
||||
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
|
||||
|
||||
super.init()
|
||||
super.init(target: .init(tableView), dataStack: dataStack)
|
||||
|
||||
tableView.dataSource = self
|
||||
}
|
||||
|
||||
/**
|
||||
Reloads the `UITableView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
If the `defaultRowAnimation` is configured to, animations are also applied accordingly.
|
||||
|
||||
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
|
||||
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
|
||||
*/
|
||||
@nonobjc
|
||||
public func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true) {
|
||||
|
||||
self.dispatcher.apply(
|
||||
snapshot.diffableSnapshot,
|
||||
view: self.tableView,
|
||||
animatingDifferences: animatingDifferences,
|
||||
performUpdates: { tableView, changeset, setSections in
|
||||
/**
|
||||
The target `UITableView`
|
||||
*/
|
||||
public var tableView: UITableView? {
|
||||
|
||||
tableView.reload(
|
||||
using: changeset,
|
||||
with: self.defaultRowAnimation,
|
||||
setData: setSections
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
|
||||
- parameter indexPath: the `IndexPath` to search for
|
||||
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
*/
|
||||
@nonobjc
|
||||
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
return self.dispatcher.itemIdentifier(for: indexPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
|
||||
- parameter itemID: the object identifier to search for
|
||||
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
*/
|
||||
@nonobjc
|
||||
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
|
||||
|
||||
return self.dispatcher.indexPath(for: itemID)
|
||||
return self.target.base
|
||||
}
|
||||
|
||||
|
||||
@@ -172,19 +104,19 @@ extension DiffableDataSource {
|
||||
@objc
|
||||
public dynamic func numberOfSections(in tableView: UITableView) -> Int {
|
||||
|
||||
return self.dispatcher.numberOfSections()
|
||||
return self.numberOfSections()
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return self.dispatcher.numberOfItems(inSection: section)
|
||||
return self.numberOfItems(inSection: section) ?? 0
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
|
||||
return self.dispatcher.sectionIdentifier(inSection: section)
|
||||
return self.sectionID(for: section)
|
||||
}
|
||||
|
||||
@objc
|
||||
@@ -196,7 +128,7 @@ extension DiffableDataSource {
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
guard let objectID = self.dispatcher.itemIdentifier(for: indexPath) else {
|
||||
guard let objectID = self.itemID(for: indexPath) else {
|
||||
|
||||
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
|
||||
}
|
||||
@@ -229,132 +161,106 @@ extension DiffableDataSource {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@nonobjc
|
||||
private weak var tableView: UITableView?
|
||||
|
||||
@nonobjc
|
||||
private let dataStack: DataStack
|
||||
|
||||
@nonobjc
|
||||
private let cellProvider: (UITableView, IndexPath, O) -> UITableViewCell?
|
||||
|
||||
@nonobjc
|
||||
private let dispatcher: Internals.DiffableDataUIDispatcher<O>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UITableView
|
||||
// MARK: - DefaultTableViewTarget
|
||||
|
||||
extension UITableView {
|
||||
public struct DefaultTableViewTarget<T: UITableView>: Target {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
@nonobjc
|
||||
fileprivate func reload<C, O>(
|
||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||
with animation: @autoclosure () -> RowAnimation,
|
||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void
|
||||
) {
|
||||
|
||||
self.reload(
|
||||
using: stagedChangeset,
|
||||
deleteSectionsAnimation: animation(),
|
||||
insertSectionsAnimation: animation(),
|
||||
reloadSectionsAnimation: animation(),
|
||||
deleteRowsAnimation: animation(),
|
||||
insertRowsAnimation: animation(),
|
||||
reloadRowsAnimation: animation(),
|
||||
interrupt: interrupt,
|
||||
setData: setData
|
||||
)
|
||||
}
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
@nonobjc
|
||||
fileprivate func reload<C, O>(
|
||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||
deleteSectionsAnimation: @autoclosure () -> RowAnimation,
|
||||
insertSectionsAnimation: @autoclosure () -> RowAnimation,
|
||||
reloadSectionsAnimation: @autoclosure () -> RowAnimation,
|
||||
deleteRowsAnimation: @autoclosure () -> RowAnimation,
|
||||
insertRowsAnimation: @autoclosure () -> RowAnimation,
|
||||
reloadRowsAnimation: @autoclosure () -> RowAnimation,
|
||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void
|
||||
) {
|
||||
|
||||
if case .none = window, let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
// MARK: Public
|
||||
|
||||
public typealias Base = T
|
||||
|
||||
public internal(set) weak var base: Base?
|
||||
|
||||
public init(_ base: Base) {
|
||||
|
||||
self.base = base
|
||||
}
|
||||
for changeset in stagedChangeset {
|
||||
|
||||
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
|
||||
|
||||
// MARK: DiffableDataSource.Target
|
||||
|
||||
public var shouldSuspendBatchUpdates: Bool {
|
||||
|
||||
return self.base?.window == nil
|
||||
}
|
||||
|
||||
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.deleteSections(indices, with: .automatic)
|
||||
}
|
||||
|
||||
public func insertSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.insertSections(indices, with: .automatic)
|
||||
}
|
||||
|
||||
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.reloadSections(indices, with: .automatic)
|
||||
}
|
||||
|
||||
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
||||
|
||||
self.base?.moveSection(index, toSection: newIndex)
|
||||
}
|
||||
|
||||
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.deleteRows(at: indexPaths, with: .automatic)
|
||||
}
|
||||
|
||||
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.insertRows(at: indexPaths, with: .automatic)
|
||||
}
|
||||
|
||||
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.reloadRows(at: indexPaths, with: .automatic)
|
||||
}
|
||||
|
||||
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
||||
|
||||
self.base?.moveRow(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
|
||||
|
||||
guard let base = self.base else {
|
||||
|
||||
return
|
||||
}
|
||||
self.cs_performBatchUpdates {
|
||||
|
||||
setData(changeset.data)
|
||||
if #available(iOS 11.0, tvOS 11.0, *) {
|
||||
|
||||
if !changeset.sectionDeleted.isEmpty {
|
||||
|
||||
self.deleteSections(IndexSet(changeset.sectionDeleted), with: deleteSectionsAnimation())
|
||||
}
|
||||
if !changeset.sectionInserted.isEmpty {
|
||||
|
||||
self.insertSections(IndexSet(changeset.sectionInserted), with: insertSectionsAnimation())
|
||||
}
|
||||
if !changeset.sectionUpdated.isEmpty {
|
||||
|
||||
self.reloadSections(IndexSet(changeset.sectionUpdated), with: reloadSectionsAnimation())
|
||||
}
|
||||
for (source, target) in changeset.sectionMoved {
|
||||
|
||||
self.moveSection(source, toSection: target)
|
||||
}
|
||||
if !changeset.elementDeleted.isEmpty {
|
||||
|
||||
self.deleteRows(at: changeset.elementDeleted.map { IndexPath(row: $0.element, section: $0.section) }, with: deleteRowsAnimation())
|
||||
}
|
||||
if !changeset.elementInserted.isEmpty {
|
||||
|
||||
self.insertRows(at: changeset.elementInserted.map { IndexPath(row: $0.element, section: $0.section) }, with: insertRowsAnimation())
|
||||
}
|
||||
if !changeset.elementUpdated.isEmpty {
|
||||
|
||||
self.reloadRows(at: changeset.elementUpdated.map { IndexPath(row: $0.element, section: $0.section) }, with: reloadRowsAnimation())
|
||||
}
|
||||
for (source, target) in changeset.elementMoved {
|
||||
|
||||
self.moveRow(at: IndexPath(row: source.element, section: source.section), to: IndexPath(row: target.element, section: target.section))
|
||||
}
|
||||
base.performBatchUpdates(updates)
|
||||
}
|
||||
else {
|
||||
|
||||
base.beginUpdates()
|
||||
updates()
|
||||
base.endUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
private func cs_performBatchUpdates(_ updates: () -> Void) {
|
||||
|
||||
if #available(iOS 11.0, tvOS 11.0, *) {
|
||||
|
||||
self.performBatchUpdates(updates)
|
||||
}
|
||||
else {
|
||||
|
||||
self.beginUpdates()
|
||||
updates()
|
||||
self.endUpdates()
|
||||
public func reloadData() {
|
||||
|
||||
self.base?.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Deprecated
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
@available(*, deprecated, renamed: "TableViewAdapter")
|
||||
public typealias TableView = TableViewAdapter
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
211
Sources/DiffableDataSource.Target.swift
Normal file
211
Sources/DiffableDataSource.Target.swift
Normal file
@@ -0,0 +1,211 @@
|
||||
//
|
||||
// DiffableDataSource.Target.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2018 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - DiffableDataSourc
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: - Target
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.Target` protocol allows custom views to consume `ListSnapshot` diffable data similar to how `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` reloads data for their corresponding views.
|
||||
*/
|
||||
public typealias Target = DiffableDataSourceTarget
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DiffableDataSource.Target
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.Target` protocol allows custom views to consume `ListSnapshot` diffable data similar to how `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` reloads data for their corresponding views.
|
||||
*/
|
||||
public protocol DiffableDataSourceTarget {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Whether `reloadData()` should be executed instead of `performBatchUpdates(updates:animated:)`.
|
||||
*/
|
||||
var shouldSuspendBatchUpdates: Bool { get }
|
||||
|
||||
/**
|
||||
Deletes one or more sections.
|
||||
*/
|
||||
func deleteSections(at indices: IndexSet, animated: Bool)
|
||||
|
||||
/**
|
||||
Inserts one or more sections
|
||||
*/
|
||||
func insertSections(at indices: IndexSet, animated: Bool)
|
||||
|
||||
/**
|
||||
Reloads the specified sections.
|
||||
*/
|
||||
func reloadSections(at indices: IndexSet, animated: Bool)
|
||||
|
||||
/**
|
||||
Moves a section to a new location.
|
||||
*/
|
||||
func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool)
|
||||
|
||||
/**
|
||||
Deletes the items specified by an array of index paths.
|
||||
*/
|
||||
func deleteItems(at indexPaths: [IndexPath], animated: Bool)
|
||||
|
||||
/**
|
||||
Inserts items at the locations identified by an array of index paths.
|
||||
*/
|
||||
func insertItems(at indexPaths: [IndexPath], animated: Bool)
|
||||
|
||||
/**
|
||||
Reloads the specified items.
|
||||
*/
|
||||
func reloadItems(at indexPaths: [IndexPath], animated: Bool)
|
||||
|
||||
/**
|
||||
Moves the item at a specified location to a destination location.
|
||||
*/
|
||||
func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool)
|
||||
|
||||
/**
|
||||
Animates multiple insert, delete, reload, and move operations as a group.
|
||||
*/
|
||||
func performBatchUpdates(updates: () -> Void, animated: Bool)
|
||||
|
||||
/**
|
||||
Reloads all sections and items.
|
||||
*/
|
||||
func reloadData()
|
||||
}
|
||||
|
||||
extension DiffableDataSource.Target {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func reload<C, O>(
|
||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||
animated: Bool,
|
||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void
|
||||
) {
|
||||
|
||||
if self.shouldSuspendBatchUpdates, let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
for changeset in stagedChangeset {
|
||||
|
||||
if let interrupt = interrupt,
|
||||
interrupt(changeset),
|
||||
let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
self.performBatchUpdates(
|
||||
updates: {
|
||||
|
||||
setData(changeset.data)
|
||||
|
||||
if !changeset.sectionDeleted.isEmpty {
|
||||
|
||||
self.deleteSections(
|
||||
at: IndexSet(changeset.sectionDeleted),
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
if !changeset.sectionInserted.isEmpty {
|
||||
|
||||
self.insertSections(
|
||||
at: IndexSet(changeset.sectionInserted),
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
if !changeset.sectionUpdated.isEmpty {
|
||||
|
||||
self.reloadSections(
|
||||
at: IndexSet(changeset.sectionUpdated),
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
for (source, target) in changeset.sectionMoved {
|
||||
|
||||
self.moveSection(
|
||||
at: source,
|
||||
to: target,
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
if !changeset.elementDeleted.isEmpty {
|
||||
|
||||
self.deleteItems(
|
||||
at: changeset.elementDeleted.map {
|
||||
|
||||
IndexPath(item: $0.element, section: $0.section)
|
||||
},
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
if !changeset.elementInserted.isEmpty {
|
||||
|
||||
self.insertItems(
|
||||
at: changeset.elementInserted.map {
|
||||
|
||||
IndexPath(item: $0.element, section: $0.section)
|
||||
},
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
if !changeset.elementUpdated.isEmpty {
|
||||
|
||||
self.reloadItems(
|
||||
at: changeset.elementUpdated.map {
|
||||
|
||||
IndexPath(item: $0.element, section: $0.section)
|
||||
},
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
for (source, target) in changeset.elementMoved {
|
||||
|
||||
self.moveItem(
|
||||
at: IndexPath(item: source.element, section: source.section),
|
||||
to: IndexPath(item: target.element, section: target.section),
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
},
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
/**
|
||||
Namespace for diffable data source types. See `DiffableDataSource.TableView` and `DiffableDataSource.CollectionView` for actual implementations
|
||||
Namespace for diffable data source types. See `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` for actual implementations
|
||||
*/
|
||||
public enum DiffableDataSource {}
|
||||
|
||||
|
||||
@@ -52,7 +52,17 @@ extension Internals {
|
||||
self.dataStack = dataStack
|
||||
}
|
||||
|
||||
func apply<View: AnyObject>(_ snapshot: DiffableDataSourceSnapshot, view: View?, animatingDifferences: Bool, performUpdates: @escaping (View, StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>, @escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void) -> Void) {
|
||||
func apply<Target: DiffableDataSource.Target>(
|
||||
_ snapshot: DiffableDataSourceSnapshot,
|
||||
target: Target?,
|
||||
animatingDifferences: Bool,
|
||||
performUpdates: @escaping (
|
||||
Target,
|
||||
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
|
||||
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
||||
) -> Void,
|
||||
completion: @escaping () -> Void
|
||||
) {
|
||||
|
||||
self.dispatcher.dispatch { [weak self] in
|
||||
|
||||
@@ -64,36 +74,42 @@ extension Internals {
|
||||
self.currentSnapshot = snapshot
|
||||
|
||||
let newSections = snapshot.sections
|
||||
guard let view = view else {
|
||||
guard let target = target else {
|
||||
|
||||
return self.sections = newSections
|
||||
self.sections = newSections
|
||||
return
|
||||
}
|
||||
|
||||
let performDiffingUpdates: () -> Void = {
|
||||
|
||||
let changeset = StagedChangeset(source: self.sections, target: newSections)
|
||||
performUpdates(view, changeset) { sections in
|
||||
performUpdates(target, changeset) { sections in
|
||||
|
||||
self.sections = sections
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(QuartzCore)
|
||||
|
||||
|
||||
CATransaction.begin()
|
||||
CATransaction.setCompletionBlock(completion)
|
||||
|
||||
if !animatingDifferences {
|
||||
|
||||
CATransaction.begin()
|
||||
|
||||
CATransaction.setDisableActions(true)
|
||||
|
||||
performDiffingUpdates()
|
||||
|
||||
CATransaction.commit()
|
||||
return
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
performDiffingUpdates()
|
||||
|
||||
CATransaction.commit()
|
||||
|
||||
|
||||
#else
|
||||
|
||||
performDiffingUpdates()
|
||||
completion()
|
||||
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,14 +120,23 @@ extension Internals {
|
||||
return snapshot
|
||||
}
|
||||
|
||||
func sectionIdentifier(inSection section: Int) -> String? {
|
||||
|
||||
guard self.sections.indices.contains(section) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return self.sections[section].differenceIdentifier
|
||||
}
|
||||
|
||||
func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
guard (0 ..< self.sections.endIndex) ~= indexPath.section else {
|
||||
|
||||
guard self.sections.indices.contains(indexPath.section) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let items = self.sections[indexPath.section].elements
|
||||
guard (0 ..< items.endIndex) ~= indexPath.item else {
|
||||
guard items.indices.contains(indexPath.item) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -138,15 +163,14 @@ extension Internals {
|
||||
return self.sections.count
|
||||
}
|
||||
|
||||
func numberOfItems(inSection section: Int) -> Int {
|
||||
|
||||
func numberOfItems(inSection section: Int) -> Int? {
|
||||
|
||||
guard self.sections.indices.contains(section) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return self.sections[section].elements.count
|
||||
}
|
||||
|
||||
func sectionIdentifier(inSection section: Int) -> String {
|
||||
|
||||
return self.sections[section].differenceIdentifier
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@@ -62,7 +62,7 @@ import SwiftUI
|
||||
.orderBy(.ascending(\.lastName))
|
||||
)
|
||||
```
|
||||
All access to the `ListPublisher` items should be done via its `snapshot` value, which is a `struct` of type `ListSnapshot<O>`. `ListSnapshot`s are also designed to work well with `DiffableDataSource.TableView`s and `DiffableDataSource.CollectionView`s. For detailed examples, refer to the documentation for `DiffableDataSource.TableView` and `DiffableDataSource.CollectionView`.
|
||||
All access to the `ListPublisher` items should be done via its `snapshot` value, which is a `struct` of type `ListSnapshot<O>`. `ListSnapshot`s are also designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s. For detailed examples, refer to the documentation for `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter`.
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
|
||||
@@ -37,7 +37,7 @@ import AppKit
|
||||
// MARK: - ListSnapshot
|
||||
|
||||
/**
|
||||
A `ListSnapshot` holds a stable list of `DynamicObject` identifiers. This is typically created by a `ListPublisher` and are designed to work well with `DiffableDataSource.TableView`s and `DiffableDataSource.CollectionView`s. For detailed examples, see the documentation on `DiffableDataSource.TableView` and `DiffableDataSource.CollectionView`.
|
||||
A `ListSnapshot` holds a stable list of `DynamicObject` identifiers. This is typically created by a `ListPublisher` and are designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s. For detailed examples, see the documentation on `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter`.
|
||||
|
||||
While the `ListSnapshot` stores only object identifiers, all accessors to its items return `ObjectPublisher`s, which are lazily created. For more details, see the documentation on `ListObject`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user