mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-11 22:30:34 +01:00
back-portable TableView DiffableDataSource
This commit is contained in:
@@ -105,6 +105,26 @@
|
||||
B509C7F51E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; };
|
||||
B509C7F61E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; };
|
||||
B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; };
|
||||
B50E174D23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
|
||||
B50E174E23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
|
||||
B50E174F23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
|
||||
B50E175023517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
|
||||
B50E175223517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */; };
|
||||
B50E175323517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */; };
|
||||
B50E175423517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */; };
|
||||
B50E175523517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */; };
|
||||
B50E175723517DE4004F033C /* Differentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175623517DE4004F033C /* Differentiable.swift */; };
|
||||
B50E175823517DE4004F033C /* Differentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175623517DE4004F033C /* Differentiable.swift */; };
|
||||
B50E175923517DE4004F033C /* Differentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175623517DE4004F033C /* Differentiable.swift */; };
|
||||
B50E175A23517DE4004F033C /* Differentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175623517DE4004F033C /* Differentiable.swift */; };
|
||||
B50E175C2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */; };
|
||||
B50E175D2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */; };
|
||||
B50E175E2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */; };
|
||||
B50E175F2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */; };
|
||||
B50E17612351FA66004F033C /* Internals.Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E17602351FA66004F033C /* Internals.Closure.swift */; };
|
||||
B50E17622351FA66004F033C /* Internals.Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E17602351FA66004F033C /* Internals.Closure.swift */; };
|
||||
B50E17632351FA66004F033C /* Internals.Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E17602351FA66004F033C /* Internals.Closure.swift */; };
|
||||
B50E17642351FA66004F033C /* Internals.Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E17602351FA66004F033C /* Internals.Closure.swift */; };
|
||||
B50EE14223473C92009B8C47 /* CoreStoreObject+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */; };
|
||||
B50EE14323473C96009B8C47 /* CoreStoreObject+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */; };
|
||||
B50EE14423473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */; };
|
||||
@@ -572,10 +592,10 @@
|
||||
B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView.swift */; };
|
||||
B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView.swift */; };
|
||||
B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView.swift */; };
|
||||
B5BF7FBC234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */; };
|
||||
B5BF7FBD234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */; };
|
||||
B5BF7FBE234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */; };
|
||||
B5BF7FBF234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */; };
|
||||
B5BF7FBC234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */; };
|
||||
B5BF7FBD234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */; };
|
||||
B5BF7FBE234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */; };
|
||||
B5BF7FBF234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */; };
|
||||
B5BF7FC1234D7B2E0070E741 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */; };
|
||||
B5BF7FC2234D7B2E0070E741 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */; };
|
||||
B5BF7FC3234D7B2E0070E741 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */; };
|
||||
@@ -878,6 +898,11 @@
|
||||
B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = "<group>"; };
|
||||
B50564D22350CC3100482308 /* PropertyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyProtocol.swift; sourceTree = "<group>"; };
|
||||
B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportableAttributeType.swift; sourceTree = "<group>"; };
|
||||
B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.StagedChangeset.swift; sourceTree = "<group>"; };
|
||||
B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.Changeset.swift; sourceTree = "<group>"; };
|
||||
B50E175623517DE4004F033C /* Differentiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Differentiable.swift; sourceTree = "<group>"; };
|
||||
B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.DiffResult.swift; sourceTree = "<group>"; };
|
||||
B50E17602351FA66004F033C /* Internals.Closure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.Closure.swift; sourceTree = "<group>"; };
|
||||
B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreStoreObject+DataSources.swift"; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@@ -994,7 +1019,7 @@
|
||||
B5BF7FAC234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataSourceSnapshot.swift; sourceTree = "<group>"; };
|
||||
B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.swift; sourceTree = "<group>"; };
|
||||
B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.TableView.swift; sourceTree = "<group>"; };
|
||||
B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.FallbackDiffableDataUIDispatcher.swift; sourceTree = "<group>"; };
|
||||
B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.swift; sourceTree = "<group>"; };
|
||||
B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveObject.swift; sourceTree = "<group>"; };
|
||||
B5BF7FC5234D7E460070E741 /* ObjectSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectSnapshot.swift; sourceTree = "<group>"; };
|
||||
B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.LazyNonmutating.swift; sourceTree = "<group>"; };
|
||||
@@ -1316,6 +1341,7 @@
|
||||
B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */,
|
||||
B50564D22350CC3100482308 /* PropertyProtocol.swift */,
|
||||
B53D9E5823513712000F48FB /* DiffableDataSourceSnapshotProtocol.swift */,
|
||||
B50E175623517DE4004F033C /* Differentiable.swift */,
|
||||
);
|
||||
name = Protocols;
|
||||
sourceTree = "<group>";
|
||||
@@ -1714,13 +1740,17 @@
|
||||
B5474D142227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift */,
|
||||
B5BF7FAC234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift */,
|
||||
B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */,
|
||||
B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */,
|
||||
B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */,
|
||||
B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */,
|
||||
B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */,
|
||||
B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */,
|
||||
B501322F2346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift */,
|
||||
B54A6A541BA15F2A007870FD /* Internals.FetchedResultsControllerDelegate.swift */,
|
||||
B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */,
|
||||
B5FAD6AB1B51285300714891 /* Internals.MigrationManager.swift */,
|
||||
B5E84F2B1AFF849C0064E85B /* Internals.NotificationObserver.swift */,
|
||||
B5277676234F265F0056BE9F /* Internals.SharedNotificationObserver.swift */,
|
||||
B50E17602351FA66004F033C /* Internals.Closure.swift */,
|
||||
B5DE522A230BD7CC00A22534 /* Internals.swift */,
|
||||
B5E84F2D1AFF849C0064E85B /* Internals.WeakObject.swift */,
|
||||
B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */,
|
||||
@@ -2063,6 +2093,7 @@
|
||||
B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */,
|
||||
B5ECDBF91CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */,
|
||||
B5CA2B081F7E5ACA004B1936 /* WhereClauseType.swift in Sources */,
|
||||
B50E17612351FA66004F033C /* Internals.Closure.swift in Sources */,
|
||||
B5C976E71C6E3A5A00B1AF90 /* Internals.CoreStoreFetchedResultsController.swift in Sources */,
|
||||
B56923F51EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
|
||||
B5F1DA901B9AA991007C5CBB /* ImportableUniqueObject.swift in Sources */,
|
||||
@@ -2114,6 +2145,7 @@
|
||||
B5E84F0E1AFF847B0064E85B /* Tweak.swift in Sources */,
|
||||
B5E1B5931CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */,
|
||||
B5277677234F265F0056BE9F /* Internals.SharedNotificationObserver.swift in Sources */,
|
||||
B50E175C2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
|
||||
B5ECDC291CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */,
|
||||
B56923F01EB827F6007C4DC9 /* XcodeSchemaMappingProvider.swift in Sources */,
|
||||
B5F335582348D75D00FD649F /* LiveResult.swift in Sources */,
|
||||
@@ -2121,6 +2153,7 @@
|
||||
B546F9581C99B17400D5AC55 /* CSCoreStore+Setup.swift in Sources */,
|
||||
B5E84F361AFF85470064E85B /* NSManagedObjectContext+Setup.swift in Sources */,
|
||||
B5FAD6AE1B518DCB00714891 /* CoreStore+Migration.swift in Sources */,
|
||||
B50E175223517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
|
||||
B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */,
|
||||
B546F9731C9C553300D5AC55 /* SetupResult.swift in Sources */,
|
||||
B53CA9A21EF1EF1600E0F440 /* PartialObject.swift in Sources */,
|
||||
@@ -2160,6 +2193,7 @@
|
||||
B596BBB61DD5BC67001DCDD9 /* FetchableSource.swift in Sources */,
|
||||
B5E1B5A21CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */,
|
||||
B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */,
|
||||
B50E175723517DE4004F033C /* Differentiable.swift in Sources */,
|
||||
B53304AA230BA4F7007C2BD8 /* DynamicObjectMeta.swift in Sources */,
|
||||
B59AFF411C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift in Sources */,
|
||||
B5E84F231AFF84860064E85B /* ListMonitor.swift in Sources */,
|
||||
@@ -2177,6 +2211,7 @@
|
||||
B5FAD6A91B50A4B400714891 /* Progress+Convenience.swift in Sources */,
|
||||
B5E84EFC1AFF846E0064E85B /* SynchronousDataTransaction.swift in Sources */,
|
||||
B5215CA91FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
|
||||
B50E174D23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
|
||||
B5E222231CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */,
|
||||
B5E84F281AFF84920064E85B /* NSManagedObject+Convenience.swift in Sources */,
|
||||
B52F744A1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
@@ -2185,7 +2220,7 @@
|
||||
B5E84F391AFF85470064E85B /* NSManagedObjectContext+Querying.swift in Sources */,
|
||||
B56923E81EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
|
||||
B53B275F1EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
|
||||
B5BF7FBC234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */,
|
||||
B5BF7FBC234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
|
||||
B5215CA41FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
|
||||
B5D33A011E96012400C880DE /* Relationship.swift in Sources */,
|
||||
B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */,
|
||||
@@ -2288,7 +2323,7 @@
|
||||
82BA18B21C4BBD3900A0916E /* ImportableObject.swift in Sources */,
|
||||
82BA18AE1C4BBD3100A0916E /* DataStack+Transaction.swift in Sources */,
|
||||
82BA18AB1C4BBD3100A0916E /* AsynchronousDataTransaction.swift in Sources */,
|
||||
B5BF7FBD234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */,
|
||||
B5BF7FBD234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
|
||||
B5D339D91E9489AB00C880DE /* CoreStoreObject.swift in Sources */,
|
||||
82BA18CE1C4BBD7100A0916E /* Internals.FetchedResultsControllerDelegate.swift in Sources */,
|
||||
B56923FB1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */,
|
||||
@@ -2307,6 +2342,7 @@
|
||||
B5A992201EA898720091A2E3 /* UserInfo.swift in Sources */,
|
||||
B5FE4DAD1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */,
|
||||
B5D339E31E948C3600C880DE /* Value.swift in Sources */,
|
||||
B50E175323517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
|
||||
82BA18C51C4BBD5300A0916E /* ListObserver.swift in Sources */,
|
||||
B53FBA0D1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
|
||||
82BA18C21C4BBD5300A0916E /* ObjectMonitor.swift in Sources */,
|
||||
@@ -2324,6 +2360,7 @@
|
||||
B5E1B5AA1CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */,
|
||||
B5D339F21E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
|
||||
B5E1B59F1CAA2568007FD580 /* CSDataStack+Observing.swift in Sources */,
|
||||
B50E17622351FA66004F033C /* Internals.Closure.swift in Sources */,
|
||||
B5ECDC251CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */,
|
||||
B549F6741E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
|
||||
B509C7F51E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
|
||||
@@ -2396,11 +2433,13 @@
|
||||
B5E222251CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */,
|
||||
82BA18C41C4BBD5300A0916E /* ListMonitor.swift in Sources */,
|
||||
B5215CAA1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
|
||||
B50E175823517DE4004F033C /* Differentiable.swift in Sources */,
|
||||
82BA18BA1C4BBD4A00A0916E /* Select.swift in Sources */,
|
||||
B52F744B1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
|
||||
B5E2222C1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */,
|
||||
82BA18A71C4BBD2900A0916E /* CoreStore+Logging.swift in Sources */,
|
||||
B50E174E23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
|
||||
82BA18D81C4BBD7100A0916E /* Internals.WeakObject.swift in Sources */,
|
||||
B56923E91EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
|
||||
B53B27601EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
|
||||
@@ -2436,6 +2475,7 @@
|
||||
B52F743E1E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
|
||||
18166884232B9ED00097C275 /* KeyPath+KeyPaths.swift in Sources */,
|
||||
B5E8A72121C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
|
||||
B50E175D2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
|
||||
B5474D162227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
|
||||
B501322B2346A9AE00FC238B /* LiveList.swift in Sources */,
|
||||
B56924001EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */,
|
||||
@@ -2507,7 +2547,7 @@
|
||||
B546F9761C9C553300D5AC55 /* SetupResult.swift in Sources */,
|
||||
B53FBA161CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
|
||||
B5ECDC271CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */,
|
||||
B5BF7FBF234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */,
|
||||
B5BF7FBF234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
|
||||
B5D339DB1E9489AB00C880DE /* CoreStoreObject.swift in Sources */,
|
||||
B52DD1951BE1F92500949AFE /* CoreStoreError.swift in Sources */,
|
||||
B56923FD1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */,
|
||||
@@ -2526,6 +2566,7 @@
|
||||
B5A992221EA898720091A2E3 /* UserInfo.swift in Sources */,
|
||||
B5D7A5BA1CA3BF8F005C752B /* CSInto.swift in Sources */,
|
||||
B5D339E51E948C3600C880DE /* Value.swift in Sources */,
|
||||
B50E175523517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
|
||||
B5A5F26A1CAEC50F004AB9AF /* CSSelect.swift in Sources */,
|
||||
B5220E1B1D13079B009BC71E /* CSCoreStore+Observing.swift in Sources */,
|
||||
B5FEC1911C9166E700532541 /* NSPersistentStore+Setup.swift in Sources */,
|
||||
@@ -2543,6 +2584,7 @@
|
||||
B5D339F41E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
|
||||
B5220E241D13085E009BC71E /* NSFetchedResultsController+Convenience.swift in Sources */,
|
||||
B559CD471CAA8B6300E4D58B /* CSSetupResult.swift in Sources */,
|
||||
B50E17642351FA66004F033C /* Internals.Closure.swift in Sources */,
|
||||
B5ECDBF01CA6BF2000C7F112 /* CSFrom.swift in Sources */,
|
||||
B549F6761E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
|
||||
B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
|
||||
@@ -2615,11 +2657,13 @@
|
||||
B5220E1A1D130791009BC71E /* Internals.CoreStoreFetchedResultsController.swift in Sources */,
|
||||
B5215CAC1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
|
||||
B53FBA0F1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
|
||||
B50E175A23517DE4004F033C /* Differentiable.swift in Sources */,
|
||||
B52F744D1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B52DD19A1BE1F92800949AFE /* CoreStore+Logging.swift in Sources */,
|
||||
B52DD1A71BE1F93200949AFE /* BaseDataTransaction+Querying.swift in Sources */,
|
||||
B546F96C1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
|
||||
B56923EB1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
|
||||
B50E175023517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
|
||||
B53B27621EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
|
||||
B5215CA71FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
|
||||
B5D33A041E96012400C880DE /* Relationship.swift in Sources */,
|
||||
@@ -2655,6 +2699,7 @@
|
||||
B52F74401E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
|
||||
18166886232B9ED20097C275 /* KeyPath+KeyPaths.swift in Sources */,
|
||||
B5E8A72321C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
|
||||
B50E175F2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
|
||||
B5474D182227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
|
||||
B501322E2346A9B100FC238B /* LiveList.swift in Sources */,
|
||||
B56924021EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */,
|
||||
@@ -2726,7 +2771,7 @@
|
||||
B56321AD1BD6521C006C9394 /* Internals.MigrationManager.swift in Sources */,
|
||||
B563219D1BD65216006C9394 /* DataStack+Observing.swift in Sources */,
|
||||
B56321961BD65216006C9394 /* From.swift in Sources */,
|
||||
B5BF7FBE234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */,
|
||||
B5BF7FBE234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
|
||||
B5D339DA1E9489AB00C880DE /* CoreStoreObject.swift in Sources */,
|
||||
B5ECDC021CA80CBA00C7F112 /* CSWhere.swift in Sources */,
|
||||
B56923FC1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */,
|
||||
@@ -2745,6 +2790,7 @@
|
||||
B5A992211EA898720091A2E3 /* UserInfo.swift in Sources */,
|
||||
B563218C1BD65216006C9394 /* DataStack+Transaction.swift in Sources */,
|
||||
B5D339E41E948C3600C880DE /* Value.swift in Sources */,
|
||||
B50E175423517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
|
||||
B53FBA0E1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
|
||||
B563219E1BD65216006C9394 /* CoreStore+Observing.swift in Sources */,
|
||||
B5D7A5B91CA3BF8F005C752B /* CSInto.swift in Sources */,
|
||||
@@ -2762,6 +2808,7 @@
|
||||
B5D339F31E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
|
||||
B5E1B5A01CAA2568007FD580 /* CSDataStack+Observing.swift in Sources */,
|
||||
B5ECDC261CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */,
|
||||
B50E17632351FA66004F033C /* Internals.Closure.swift in Sources */,
|
||||
B563217F1BD65216006C9394 /* CoreStore.swift in Sources */,
|
||||
B549F6751E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
|
||||
B509C7F61E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
|
||||
@@ -2834,11 +2881,13 @@
|
||||
B56321A51BD65216006C9394 /* MigrationChain.swift in Sources */,
|
||||
B5E222261CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */,
|
||||
B56321A21BD65216006C9394 /* ListObserver.swift in Sources */,
|
||||
B50E175923517DE4004F033C /* Differentiable.swift in Sources */,
|
||||
B5215CAB1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
|
||||
B563218A1BD65216006C9394 /* SynchronousDataTransaction.swift in Sources */,
|
||||
B52F744C1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B5AEFAB71C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
|
||||
B5E2222D1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */,
|
||||
B50E174F23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
|
||||
B563219F1BD65216006C9394 /* ObjectMonitor.swift in Sources */,
|
||||
B56321B61BD6521C006C9394 /* Internals.WeakObject.swift in Sources */,
|
||||
B56923EA1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
|
||||
@@ -2874,6 +2923,7 @@
|
||||
B52F743F1E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
|
||||
18166885232B9ED10097C275 /* KeyPath+KeyPaths.swift in Sources */,
|
||||
B5E8A72221C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
|
||||
B50E175E2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
|
||||
B5474D172227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
|
||||
B501322D2346A9B000FC238B /* LiveList.swift in Sources */,
|
||||
B56924011EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */,
|
||||
|
||||
@@ -510,18 +510,18 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="aI4-O3-OCi"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="w8K-eN-RvU">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="309"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="311.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="NhC-oM-bkd">
|
||||
<rect key="frame" x="16" y="69.5" width="343" height="77.5"/>
|
||||
<rect key="frame" x="16" y="69.5" width="343" height="80"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="TIX-qi-B34"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="250" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfe-Yq-3Xa">
|
||||
<rect key="frame" x="16" y="157" width="343" height="18"/>
|
||||
<rect key="frame" x="16" y="159.5" width="343" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="4h9-ha-EzR"/>
|
||||
</constraints>
|
||||
@@ -530,37 +530,37 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Hue" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sgg-Md-Nf3">
|
||||
<rect key="frame" x="16" y="195" width="74" height="18"/>
|
||||
<rect key="frame" x="16" y="197.5" width="74" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Saturation" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rry-vh-bRK">
|
||||
<rect key="frame" x="16" y="233" width="74" height="18"/>
|
||||
<rect key="frame" x="16" y="235.5" width="74" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Brightness" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vTa-ly-eyO">
|
||||
<rect key="frame" x="16" y="271" width="74" height="18"/>
|
||||
<rect key="frame" x="16" y="273.5" width="74" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="359" translatesAutoresizingMaskIntoConstraints="NO" id="YQ6-fq-3Wb">
|
||||
<rect key="frame" x="98" y="189" width="263" height="31"/>
|
||||
<rect key="frame" x="98" y="191.5" width="263" height="31"/>
|
||||
<connections>
|
||||
<action selector="hueSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="9Hy-3h-llE"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="xXz-78-tAd">
|
||||
<rect key="frame" x="98" y="227" width="263" height="31"/>
|
||||
<rect key="frame" x="98" y="229.5" width="263" height="31"/>
|
||||
<connections>
|
||||
<action selector="saturationSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="qtU-ua-ZTc"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="hpy-2d-eOP">
|
||||
<rect key="frame" x="98" y="265" width="263" height="31"/>
|
||||
<rect key="frame" x="98" y="267.5" width="263" height="31"/>
|
||||
<connections>
|
||||
<action selector="brightnessSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="F09-EP-2iD"/>
|
||||
</connections>
|
||||
@@ -637,34 +637,52 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="L5f-tW-lXf">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="311.5"/>
|
||||
<connections>
|
||||
<segue destination="5Fw-je-9gI" kind="embed" id="YcI-2Z-ijV"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6So-f3-4Gp">
|
||||
<rect key="frame" x="0.0" y="355.5" width="375" height="311.5"/>
|
||||
<connections>
|
||||
<segue destination="bLr-DM-i2t" kind="embed" id="OdS-gZ-CUA"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="cce-yT-4dn">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="623"/>
|
||||
<subviews>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="L5f-tW-lXf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="311.5"/>
|
||||
<connections>
|
||||
<segue destination="5Fw-je-9gI" kind="embed" id="YcI-2Z-ijV"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6So-f3-4Gp">
|
||||
<rect key="frame" x="0.0" y="311.5" width="375" height="311.5"/>
|
||||
<connections>
|
||||
<segue destination="bLr-DM-i2t" kind="embed" id="OdS-gZ-CUA"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="6So-f3-4Gp" firstAttribute="top" secondItem="L5f-tW-lXf" secondAttribute="bottom" id="3m8-tj-Nd4"/>
|
||||
<constraint firstAttribute="trailing" secondItem="6So-f3-4Gp" secondAttribute="trailing" id="4L8-wZ-F59"/>
|
||||
<constraint firstItem="L5f-tW-lXf" firstAttribute="height" secondItem="6So-f3-4Gp" secondAttribute="height" id="8XS-L3-hvN"/>
|
||||
<constraint firstAttribute="bottom" secondItem="6So-f3-4Gp" secondAttribute="bottom" id="8wL-zm-wnt"/>
|
||||
<constraint firstItem="L5f-tW-lXf" firstAttribute="leading" secondItem="6x3-vn-Egt" secondAttribute="leading" id="CbE-2f-7wk"/>
|
||||
<constraint firstAttribute="trailing" secondItem="L5f-tW-lXf" secondAttribute="trailing" id="dso-2g-fgA"/>
|
||||
<constraint firstItem="6So-f3-4Gp" firstAttribute="leading" secondItem="6x3-vn-Egt" secondAttribute="leading" id="eXM-D3-NLv"/>
|
||||
<constraint firstItem="L5f-tW-lXf" firstAttribute="top" secondItem="IML-3o-caw" secondAttribute="bottom" id="zJ5-sE-iJA"/>
|
||||
<constraint firstAttribute="trailing" secondItem="cce-yT-4dn" secondAttribute="trailing" id="DE1-u6-1mr"/>
|
||||
<constraint firstItem="LNL-mj-D7l" firstAttribute="top" secondItem="cce-yT-4dn" secondAttribute="bottom" id="WIZ-M3-bdr"/>
|
||||
<constraint firstItem="cce-yT-4dn" firstAttribute="leading" secondItem="6x3-vn-Egt" secondAttribute="leading" id="ZiS-j0-ANp"/>
|
||||
<constraint firstItem="cce-yT-4dn" firstAttribute="top" secondItem="IML-3o-caw" secondAttribute="bottom" id="zjs-CM-7r5"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<extendedEdge key="edgesForExtendedLayout" top="YES"/>
|
||||
<navigationItem key="navigationItem" title="Colors" id="7Gd-Ad-Bzu"/>
|
||||
<navigationItem key="navigationItem" title="Colors" id="7Gd-Ad-Bzu">
|
||||
<rightBarButtonItems>
|
||||
<barButtonItem title="▼" width="36" id="aPM-OC-EB8">
|
||||
<connections>
|
||||
<action selector="toggleBottomContainerView" destination="YOI-b7-Nxn" id="7fn-z6-pne"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem title="▲" width="36" id="6q3-Zk-A9p">
|
||||
<connections>
|
||||
<action selector="toggleTopContainerView" destination="YOI-b7-Nxn" id="APN-Kk-C3n"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</rightBarButtonItems>
|
||||
</navigationItem>
|
||||
<nil key="simulatedBottomBarMetrics"/>
|
||||
<connections>
|
||||
<outlet property="bottomContainerView" destination="6So-f3-4Gp" id="C7O-e2-k0l"/>
|
||||
<outlet property="topContainerView" destination="L5f-tW-lXf" id="DRV-mm-rBY"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="C9h-Ba-WoL" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
@@ -700,7 +718,7 @@
|
||||
<objects>
|
||||
<tableViewController id="3AE-ED-0oj" customClass="ListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="DAz-BE-6Ca">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="309"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="311.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="G3X-70-BCD" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
|
||||
@@ -885,11 +903,11 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="Q1w-Zn-fPm"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="kof-sr-Xm1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="309"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="311.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="250" text="SwiftUI is not supported on this device/OS version" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="M6C-fv-74B">
|
||||
<rect key="frame" x="16" y="124.5" width="343" height="60"/>
|
||||
<rect key="frame" x="16" y="126" width="343" height="60"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="25"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
|
||||
@@ -43,10 +43,10 @@ struct ColorsDemo {
|
||||
|
||||
didSet {
|
||||
|
||||
self.palettes.refetch(
|
||||
self.filter.whereClause(),
|
||||
OrderBy<Palette>(.ascending(\.hue))
|
||||
)
|
||||
// self.palettes.refetch(
|
||||
// self.filter.whereClause(),
|
||||
// OrderBy<Palette>(.ascending(\.hue))
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +73,9 @@ struct ColorsDemo {
|
||||
return dataStack
|
||||
}()
|
||||
|
||||
static let palettes: ListMonitor<Palette> = {
|
||||
static let palettes: LiveList<Palette> = {
|
||||
|
||||
return ColorsDemo.stack.monitorSectionedList(
|
||||
return ColorsDemo.stack.liveList(
|
||||
From<Palette>()
|
||||
.sectionBy(\.colorName)
|
||||
.orderBy(.ascending(\.hue))
|
||||
@@ -86,15 +86,7 @@ struct ColorsDemo {
|
||||
|
||||
// MARK: - ListObserverDemoViewController
|
||||
|
||||
class ListObserverDemoViewController: UITableViewController, ListSectionObserver {
|
||||
|
||||
// MARK: NSObject
|
||||
|
||||
deinit {
|
||||
|
||||
ColorsDemo.palettes.removeObserver(self)
|
||||
}
|
||||
|
||||
class ListObserverDemoViewController: UITableViewController {
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
@@ -132,10 +124,26 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
filterBarButton
|
||||
]
|
||||
self.filterBarButton = filterBarButton
|
||||
|
||||
ColorsDemo.palettes.addObserver(self)
|
||||
|
||||
self.setTable(enabled: !ColorsDemo.palettes.isPendingRefetch)
|
||||
|
||||
self.dataSource = DiffableDataSource.TableView<Palette>(
|
||||
tableView: self.tableView,
|
||||
dataStack: ColorsDemo.stack,
|
||||
cellProvider: { (tableView, indexPath, palette) in
|
||||
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PaletteTableViewCell") as! PaletteTableViewCell
|
||||
cell.colorView?.backgroundColor = palette.color
|
||||
cell.label?.text = palette.colorText
|
||||
return cell
|
||||
}
|
||||
)
|
||||
ColorsDemo.palettes.addObserver(self) { [weak self] (liveList, snapshot) in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.dataSource?.apply(snapshot, animatingDifferences: true)
|
||||
}
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
@@ -153,30 +161,6 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
}
|
||||
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
|
||||
return ColorsDemo.palettes.numberOfSections()
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return ColorsDemo.palettes.numberOfObjects(in: section)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PaletteTableViewCell") as! PaletteTableViewCell
|
||||
|
||||
let palette = ColorsDemo.palettes[indexPath]
|
||||
cell.colorView?.backgroundColor = palette.color
|
||||
cell.label?.text = palette.colorText
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
// MARK: UITableViewDelegate
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
@@ -185,7 +169,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
|
||||
self.performSegue(
|
||||
withIdentifier: "ObjectObserverDemoViewController",
|
||||
sender: ColorsDemo.palettes[indexPath]
|
||||
sender: ColorsDemo.palettes[indexPath: indexPath]?.object
|
||||
)
|
||||
}
|
||||
|
||||
@@ -194,11 +178,11 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
switch editingStyle {
|
||||
|
||||
case .delete:
|
||||
let palette = ColorsDemo.palettes[indexPath]
|
||||
let palette = ColorsDemo.palettes[indexPath: indexPath]
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
transaction.delete(palette)
|
||||
transaction.delete(palette?.object)
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
@@ -208,81 +192,16 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
|
||||
return ColorsDemo.palettes.sectionInfo(at: section).name
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListObserver
|
||||
|
||||
func listMonitorWillChange(_ monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.tableView.beginUpdates()
|
||||
}
|
||||
|
||||
func listMonitorDidChange(_ monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.tableView.endUpdates()
|
||||
}
|
||||
|
||||
func listMonitorWillRefetch(_ monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.setTable(enabled: false)
|
||||
}
|
||||
|
||||
func listMonitorDidRefetch(_ monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.filterBarButton?.title = ColorsDemo.filter.rawValue
|
||||
self.tableView.reloadData()
|
||||
self.setTable(enabled: true)
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListObjectObserver
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<Palette>, didInsertObject object: Palette, toIndexPath indexPath: IndexPath) {
|
||||
|
||||
self.tableView.insertRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<Palette>, didDeleteObject object: Palette, fromIndexPath indexPath: IndexPath) {
|
||||
|
||||
self.tableView.deleteRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: IndexPath) {
|
||||
|
||||
if let cell = self.tableView.cellForRow(at: indexPath) as? PaletteTableViewCell {
|
||||
|
||||
let palette = ColorsDemo.palettes[indexPath]
|
||||
cell.colorView?.backgroundColor = palette.color
|
||||
cell.label?.text = palette.colorText
|
||||
}
|
||||
}
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<Palette>, didMoveObject object: Palette, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
|
||||
|
||||
self.tableView.moveRow(at: fromIndexPath, to: toIndexPath)
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListSectionObserver
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<Palette>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
|
||||
|
||||
self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .automatic)
|
||||
}
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<Palette>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
|
||||
|
||||
self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var filterBarButton: UIBarButtonItem?
|
||||
private var dataSource: DiffableDataSource.TableView<Palette>?
|
||||
|
||||
deinit {
|
||||
|
||||
ColorsDemo.palettes.removeObserver(self)
|
||||
}
|
||||
|
||||
@IBAction private dynamic func resetBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
|
||||
@@ -313,8 +232,6 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
}
|
||||
|
||||
@IBAction private dynamic func shuffleBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
|
||||
self.setTable(enabled: false)
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
@@ -324,28 +241,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
palette.colorName .= nil
|
||||
}
|
||||
},
|
||||
completion: { _ in
|
||||
|
||||
self.setTable(enabled: true)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func setTable(enabled: Bool) {
|
||||
|
||||
tableView.isUserInteractionEnabled = enabled
|
||||
UIView.animate(
|
||||
withDuration: 0.2,
|
||||
delay: 0,
|
||||
options: .beginFromCurrentState,
|
||||
animations: { () -> Void in
|
||||
|
||||
if let tableView = self.tableView {
|
||||
|
||||
tableView.alpha = enabled ? 1.0 : 0.5
|
||||
}
|
||||
},
|
||||
completion: nil
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,4 +27,20 @@ class ObserversViewController: UIViewController {
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@IBOutlet private dynamic weak var topContainerView: UIView?
|
||||
@IBOutlet private dynamic weak var bottomContainerView: UIView?
|
||||
|
||||
@IBAction private dynamic func toggleTopContainerView() {
|
||||
|
||||
self.topContainerView?.isHidden.toggle()
|
||||
}
|
||||
|
||||
@IBAction private dynamic func toggleBottomContainerView() {
|
||||
|
||||
self.bottomContainerView?.isHidden.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import CoreData
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
extension DataStack {
|
||||
|
||||
public func liveList<D>(_ from: From<D>, _ fetchClauses: FetchClause...) -> LiveList<D> {
|
||||
|
||||
@@ -29,79 +29,324 @@ import UIKit
|
||||
import CoreData
|
||||
|
||||
|
||||
//// MARK: - DiffableDataSource
|
||||
//
|
||||
//extension DiffableDataSource {
|
||||
//
|
||||
// // MARK: - TableView
|
||||
//
|
||||
// public open class TableView<D: DynamicObject>: NSObject, UITableViewDataSource {
|
||||
//
|
||||
// // MARK: Public
|
||||
//
|
||||
// public typealias ObjectType = D
|
||||
//
|
||||
// public var defaultRowAnimation: UITableView.RowAnimation = .automatic
|
||||
//
|
||||
// public init(tableView: UITableView, cellProvider: @escaping (UITableView, IndexPath, ObjectType) -> UITableViewCell?) {
|
||||
//
|
||||
// self.tableView = tableView
|
||||
// self.cellProvider = cellProvider
|
||||
//
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: - TableView
|
||||
|
||||
open class TableView<O: DynamicObject>: NSObject, UITableViewDataSource {
|
||||
|
||||
// MARK: Open
|
||||
|
||||
@nonobjc
|
||||
open var defaultRowAnimation: UITableView.RowAnimation = .automatic
|
||||
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public typealias ObjectType = O
|
||||
|
||||
@nonobjc
|
||||
public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, ObjectType) -> UITableViewCell?) {
|
||||
|
||||
self.tableView = tableView
|
||||
self.cellProvider = cellProvider
|
||||
self.dataStack = dataStack
|
||||
|
||||
super.init()
|
||||
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// self.rawDataSource = UITableViewDiffableDataSource<String, D.ObjectID>(
|
||||
// self.rawDataSource = UITableViewDiffableDataSource<String, O.ObjectID>(
|
||||
// tableView: tableView,
|
||||
// cellProvider: { (tableView, indexPath, managedObjectID) -> UITableViewCell? in
|
||||
// cellProvider: { [weak self] (tableView, indexPath, objectID) -> UITableViewCell? in
|
||||
//
|
||||
// cellProvider(
|
||||
// guard let self = self else {
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
// guard let object = self.dataStack.fetchExisting(objectID) as O? else {
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
// return self.cellProvider(tableView, indexPath, object)
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// self.rawDataSource = nil
|
||||
|
||||
self.rawDataSource = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
|
||||
// }
|
||||
//
|
||||
// super.init()
|
||||
//
|
||||
// tableView.dataSource = self
|
||||
// }
|
||||
//
|
||||
// public func apply(_ snapshot: ListSnapshot<ObjectType>, animatingDifferences: Bool = true) {
|
||||
//
|
||||
// let dataSource = UITableViewDiffableDataSource<String, D>.
|
||||
|
||||
tableView.dataSource = self
|
||||
}
|
||||
|
||||
public func apply(_ snapshot: ListSnapshot<ObjectType>, animatingDifferences: Bool = true) {
|
||||
|
||||
let diffableSnapshot = snapshot.diffableSnapshot
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// self.rawDataSource! as! UITableViewDiffableDataSource<String, D>
|
||||
//
|
||||
// self.modernDataSource.apply(
|
||||
// diffableSnapshot as! NSDiffableDataSourceSnapshot<String, NSManagedObjectID>,
|
||||
// animatingDifferences: animatingDifferences,
|
||||
// completion: nil
|
||||
// )
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
|
||||
self.legacyDataSource.apply(
|
||||
diffableSnapshot as! Internals.DiffableDataSourceSnapshot,
|
||||
view: self.tableView,
|
||||
animatingDifferences: animatingDifferences,
|
||||
performUpdates: { tableView, changeset, setSections in
|
||||
|
||||
tableView.reload(
|
||||
using: changeset,
|
||||
with: self.defaultRowAnimation,
|
||||
setData: setSections
|
||||
)
|
||||
}
|
||||
)
|
||||
// }
|
||||
// core.apply(
|
||||
// snapshot,
|
||||
// view: tableView,
|
||||
// animatingDifferences: animatingDifferences,
|
||||
// performUpdates: { tableView, changeset, setSections in
|
||||
// tableView.reload(using: changeset, with: self.defaultRowAnimation, setData: setSections)
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
public func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// return self.modernDataSource.itemIdentifier(for: indexPath)
|
||||
// }
|
||||
// else {
|
||||
|
||||
return self.legacyDataSource.itemIdentifier(for: indexPath)
|
||||
// }
|
||||
}
|
||||
|
||||
public func indexPath(for itemIdentifier: O.ObjectID) -> IndexPath? {
|
||||
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// // MARK: Private
|
||||
// return self.modernDataSource.indexPath(for: itemIdentifier)
|
||||
// }
|
||||
// else {
|
||||
|
||||
return self.legacyDataSource.indexPath(for: itemIdentifier)
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
|
||||
@objc
|
||||
public dynamic func numberOfSections(in tableView: UITableView) -> Int {
|
||||
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// private weak var tableView: UITableView?
|
||||
// private let cellProvider: (UITableView, IndexPath, ObjectType) -> UITableViewCell?
|
||||
// private let rawDataSource: Any?
|
||||
// return self.modernDataSource.numberOfSections(in: tableView)
|
||||
// }
|
||||
// else {
|
||||
|
||||
return self.legacyDataSource.numberOfSections()
|
||||
// }
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// return self.modernDataSource.tableView(tableView, numberOfRowsInSection: section)
|
||||
// }
|
||||
// else {
|
||||
|
||||
return self.legacyDataSource.numberOfItems(inSection: section)
|
||||
// }
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// return self.modernDataSource.snapshot().sectionIdentifiers[section]
|
||||
// }
|
||||
// else {
|
||||
|
||||
return self.legacyDataSource.sectionIdentifier(inSection: section)
|
||||
// }
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// return self.modernDataSource.tableView(tableView, titleForFooterInSection: section)
|
||||
// }
|
||||
// else {
|
||||
|
||||
return nil
|
||||
// }
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// return self.modernDataSource.tableView(tableView, cellForRowAt: indexPath)
|
||||
// }
|
||||
// else {
|
||||
|
||||
guard let objectID = self.legacyDataSource.itemIdentifier(for: indexPath) else {
|
||||
|
||||
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
|
||||
}
|
||||
guard let object = self.dataStack.fetchExisting(objectID) as O? else {
|
||||
|
||||
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) has been deleted")
|
||||
}
|
||||
guard let cell = self.cellProvider(tableView, indexPath, object) else {
|
||||
|
||||
Internals.abort("\(Internals.typeName(UITableViewDataSource.self)) returned a `nil` cell for \(Internals.typeName(IndexPath.self)) \(indexPath)")
|
||||
}
|
||||
return cell
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private weak var tableView: UITableView?
|
||||
|
||||
private let dataStack: DataStack
|
||||
private let cellProvider: (UITableView, IndexPath, ObjectType) -> UITableViewCell?
|
||||
private var rawDataSource: Any!
|
||||
|
||||
// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
// private var diffableDataSource: UITableViewDiffableDataSource<String, D.ObjectID> {
|
||||
// private var modernDataSource: UITableViewDiffableDataSource<String, O.ObjectID> {
|
||||
//
|
||||
// return self.rawDataSource! as! UITableViewDiffableDataSource<String, D.ObjectID>
|
||||
// return self.rawDataSource as! UITableViewDiffableDataSource<String, O.ObjectID>
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
private var legacyDataSource: Internals.DiffableDataUIDispatcher<O> {
|
||||
|
||||
return self.rawDataSource as! Internals.DiffableDataUIDispatcher<O>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UITableView
|
||||
|
||||
extension UITableView {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
@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
|
||||
)
|
||||
}
|
||||
|
||||
@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
|
||||
}
|
||||
for changeset in stagedChangeset {
|
||||
|
||||
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
self.cs_performBatchUpdates {
|
||||
|
||||
setData(changeset.data)
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
private func cs_performBatchUpdates(_ updates: () -> Void) {
|
||||
|
||||
if #available(iOS 11.0, tvOS 11.0, *) {
|
||||
|
||||
self.performBatchUpdates(updates)
|
||||
}
|
||||
else {
|
||||
|
||||
self.beginUpdates()
|
||||
updates()
|
||||
self.endUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
62
Sources/Differentiable.swift
Normal file
62
Sources/Differentiable.swift
Normal file
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Differentiable.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: - Differentiable
|
||||
|
||||
@usableFromInline
|
||||
internal protocol Differentiable {
|
||||
|
||||
associatedtype DifferenceIdentifier: Hashable
|
||||
|
||||
var differenceIdentifier: DifferenceIdentifier { get }
|
||||
|
||||
func isContentEqual(to source: Self) -> Bool
|
||||
}
|
||||
|
||||
extension Differentiable where Self: AnyObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal var differenceIdentifier: ObjectIdentifier {
|
||||
|
||||
return .init(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DifferentiableSection
|
||||
|
||||
@usableFromInline
|
||||
internal protocol DifferentiableSection: Differentiable {
|
||||
|
||||
associatedtype Collection: Swift.Collection where Collection.Element: Differentiable
|
||||
|
||||
var elements: Collection { get }
|
||||
|
||||
init<S: Sequence>(source: Self, elements: S) where S.Element == Collection.Element
|
||||
}
|
||||
54
Sources/Internals.Closure.swift
Normal file
54
Sources/Internals.Closure.swift
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Internals.Closure.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: - Internals
|
||||
|
||||
extension Internals {
|
||||
|
||||
// MARK: - Closure
|
||||
|
||||
internal final class Closure<T, U> {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
internal init(_ closure: @escaping (T) -> U) {
|
||||
|
||||
self.closure = closure
|
||||
}
|
||||
|
||||
internal func invoke(with argument: T) -> U {
|
||||
|
||||
return self.closure(argument)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let closure: (T) -> U
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,18 @@ extension Internals {
|
||||
|
||||
self.structure = .init(sections: sections)
|
||||
}
|
||||
|
||||
var sections: [Section] {
|
||||
|
||||
get {
|
||||
|
||||
return self.structure.sections
|
||||
}
|
||||
set {
|
||||
|
||||
self.structure.sections = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: DiffableDataSourceSnapshotProtocol
|
||||
@@ -186,48 +198,66 @@ extension Internals {
|
||||
private var structure: BackingStructure
|
||||
|
||||
|
||||
// MARK: - ItemStateID
|
||||
// MARK: - Section
|
||||
|
||||
internal struct ItemStateID: Identifiable, Equatable {
|
||||
internal struct Section: DifferentiableSection, Equatable {
|
||||
|
||||
let stateTag: UUID
|
||||
var isReloaded: Bool
|
||||
|
||||
init(id: NSManagedObjectID, stateTag: UUID) {
|
||||
|
||||
self.id = id
|
||||
self.stateTag = stateTag
|
||||
init(differenceIdentifier: String, items: [Item] = [], isReloaded: Bool = false) {
|
||||
|
||||
self.differenceIdentifier = differenceIdentifier
|
||||
self.elements = items
|
||||
self.isReloaded = isReloaded
|
||||
}
|
||||
|
||||
func isContentEqual(to source: ItemStateID) -> Bool {
|
||||
// MARK: Differentiable
|
||||
|
||||
return self.id == source.id && self.stateTag == source.stateTag
|
||||
let differenceIdentifier: String
|
||||
|
||||
func isContentEqual(to source: Section) -> Bool {
|
||||
|
||||
return !self.isReloaded
|
||||
&& self.differenceIdentifier == source.differenceIdentifier
|
||||
}
|
||||
|
||||
|
||||
// MARK: DifferentiableSection
|
||||
|
||||
var elements: [Item] = []
|
||||
|
||||
// MARK: Identifiable
|
||||
init<S: Sequence>(source: Section, elements: S) where S.Element == Item {
|
||||
|
||||
let id: NSManagedObjectID
|
||||
self.init(
|
||||
differenceIdentifier: source.differenceIdentifier,
|
||||
items: Array(elements),
|
||||
isReloaded: source.isReloaded
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - SectionStateID
|
||||
// MARK: - Item
|
||||
|
||||
internal struct SectionStateID: Identifiable, Equatable {
|
||||
internal struct Item: Differentiable, Equatable {
|
||||
|
||||
let stateTag: UUID
|
||||
var isReloaded: Bool
|
||||
|
||||
init(id: String, stateTag: UUID) {
|
||||
self.id = id
|
||||
self.stateTag = stateTag
|
||||
init(differenceIdentifier: NSManagedObjectID, isReloaded: Bool = false) {
|
||||
|
||||
self.differenceIdentifier = differenceIdentifier
|
||||
self.isReloaded = isReloaded
|
||||
}
|
||||
|
||||
func isContentEqual(to source: SectionStateID) -> Bool {
|
||||
// MARK: Differentiable
|
||||
|
||||
return self.id == source.id && self.stateTag == source.stateTag
|
||||
let differenceIdentifier: NSManagedObjectID
|
||||
|
||||
func isContentEqual(to source: Item) -> Bool {
|
||||
|
||||
return !self.isReloaded
|
||||
&& self.differenceIdentifier == source.differenceIdentifier
|
||||
}
|
||||
|
||||
// MARK: Identifiable
|
||||
|
||||
let id: String
|
||||
}
|
||||
|
||||
|
||||
@@ -249,22 +279,22 @@ extension Internals {
|
||||
self.sections = sections.map {
|
||||
|
||||
Section(
|
||||
id: $0.name,
|
||||
differenceIdentifier: $0.name,
|
||||
items: $0.objects?
|
||||
.compactMap({ ($0 as? NSManagedObject)?.objectID })
|
||||
.map({ Item(id: $0) }) ?? []
|
||||
.map({ Item(differenceIdentifier: $0) }) ?? []
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var allSectionIDs: [String] {
|
||||
|
||||
return self.sections.map({ $0.id })
|
||||
return self.sections.map({ $0.differenceIdentifier })
|
||||
}
|
||||
|
||||
var allItemIDs: [NSManagedObjectID] {
|
||||
|
||||
return self.sections.lazy.flatMap({ $0.elements }).map({ $0.id })
|
||||
return self.sections.lazy.flatMap({ $0.elements }).map({ $0.differenceIdentifier })
|
||||
}
|
||||
|
||||
func items(in sectionID: String) -> [NSManagedObjectID] {
|
||||
@@ -273,12 +303,12 @@ extension Internals {
|
||||
|
||||
Internals.abort("Section \"\(sectionID)\" does not exist")
|
||||
}
|
||||
return self.sections[sectionIndex].elements.map({ $0.id })
|
||||
return self.sections[sectionIndex].elements.map({ $0.differenceIdentifier })
|
||||
}
|
||||
|
||||
func section(containing itemID: NSManagedObjectID) -> String? {
|
||||
|
||||
return self.itemPositionMap()[itemID]?.section.id
|
||||
return self.itemPositionMap()[itemID]?.section.differenceIdentifier
|
||||
}
|
||||
|
||||
mutating func append(itemIDs: [NSManagedObjectID], to sectionID: String?) {
|
||||
@@ -301,7 +331,7 @@ extension Internals {
|
||||
}
|
||||
index = section.index(before: section.endIndex)
|
||||
}
|
||||
let items = itemIDs.lazy.map({ Item(id: $0) })
|
||||
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
|
||||
self.sections[index].elements.append(contentsOf: items)
|
||||
}
|
||||
|
||||
@@ -311,7 +341,7 @@ extension Internals {
|
||||
|
||||
Internals.abort("Item \(beforeItemID) does not exist")
|
||||
}
|
||||
let items = itemIDs.lazy.map({ Item(id: $0) })
|
||||
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
|
||||
self.sections[itemPosition.sectionIndex].elements
|
||||
.insert(contentsOf: items, at: itemPosition.itemRelativeIndex)
|
||||
}
|
||||
@@ -324,7 +354,7 @@ extension Internals {
|
||||
}
|
||||
let itemIndex = self.sections[itemPosition.sectionIndex].elements
|
||||
.index(after: itemPosition.itemRelativeIndex)
|
||||
let items = itemIDs.lazy.map({ Item(id: $0) })
|
||||
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
|
||||
self.sections[itemPosition.sectionIndex].elements
|
||||
.insert(contentsOf: items, at: itemIndex)
|
||||
}
|
||||
@@ -406,7 +436,7 @@ extension Internals {
|
||||
|
||||
mutating func append(sectionIDs: [String]) {
|
||||
|
||||
let newSections = sectionIDs.lazy.map({ Section(id: $0) })
|
||||
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
|
||||
self.sections.append(contentsOf: newSections)
|
||||
}
|
||||
|
||||
@@ -416,7 +446,7 @@ extension Internals {
|
||||
|
||||
Internals.abort("Section \"\(beforeSectionID)\" does not exist")
|
||||
}
|
||||
let newSections = sectionIDs.lazy.map({ Section(id: $0) })
|
||||
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
|
||||
self.sections.insert(contentsOf: newSections, at: sectionIndex)
|
||||
}
|
||||
|
||||
@@ -427,7 +457,7 @@ extension Internals {
|
||||
Internals.abort("Section \"\(afterSectionID)\" does not exist")
|
||||
}
|
||||
let sectionIndex = self.sections.index(after: beforeIndex)
|
||||
let newSections = sectionIDs.lazy.map({ Section(id: $0) })
|
||||
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
|
||||
self.sections.insert(contentsOf: newSections, at: sectionIndex)
|
||||
}
|
||||
|
||||
@@ -481,11 +511,9 @@ extension Internals {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static let zeroUUID: UUID = .init(uuid: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
|
||||
|
||||
private func sectionIndex(of sectionID: String) -> Array<Section>.Index? {
|
||||
|
||||
return self.sections.firstIndex(where: { $0.id == sectionID })
|
||||
return self.sections.firstIndex(where: { $0.differenceIdentifier == sectionID })
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
@@ -515,7 +543,7 @@ extension Internals {
|
||||
|
||||
for (itemRelativeIndex, item) in section.element.elements.enumerated() {
|
||||
|
||||
result[item.id] = ItemPosition(
|
||||
result[item.differenceIdentifier] = ItemPosition(
|
||||
item: item,
|
||||
itemRelativeIndex: itemRelativeIndex,
|
||||
section: section.element,
|
||||
@@ -526,58 +554,6 @@ extension Internals {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Item
|
||||
|
||||
fileprivate struct Item: Identifiable, Equatable {
|
||||
|
||||
var isReloaded: Bool
|
||||
|
||||
init(id: NSManagedObjectID, isReloaded: Bool = false) {
|
||||
|
||||
self.id = id
|
||||
self.isReloaded = isReloaded
|
||||
}
|
||||
|
||||
func isContentEqual(to source: Item) -> Bool {
|
||||
|
||||
return !self.isReloaded && self.id == source.id
|
||||
}
|
||||
|
||||
// MARK: Identifiable
|
||||
|
||||
let id: NSManagedObjectID
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Section
|
||||
|
||||
fileprivate struct Section: Identifiable, Equatable {
|
||||
|
||||
var elements: [Item] = []
|
||||
var isReloaded: Bool
|
||||
|
||||
init(id: String, items: [Item] = [], isReloaded: Bool = false) {
|
||||
self.id = id
|
||||
self.elements = items
|
||||
self.isReloaded = isReloaded
|
||||
}
|
||||
|
||||
init<S: Sequence>(source: Section, elements: S) where S.Element == Item {
|
||||
|
||||
self.init(id: source.id, items: Array(elements), isReloaded: source.isReloaded)
|
||||
}
|
||||
|
||||
func isContentEqual(to source: Section) -> Bool {
|
||||
|
||||
return !self.isReloaded && self.id == source.id
|
||||
}
|
||||
|
||||
// MARK: Identifiable
|
||||
|
||||
let id: String
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ItemPosition
|
||||
|
||||
fileprivate struct ItemPosition {
|
||||
@@ -595,73 +571,7 @@ extension Internals {
|
||||
// MARK: - NSDiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
extension NSDiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol where SectionIdentifierType == NSString, ItemIdentifierType == NSManagedObjectID {
|
||||
|
||||
internal var sectionIdentifiers: [String] {
|
||||
|
||||
return self.sectionIdentifiers as [NSString] as [String]
|
||||
}
|
||||
|
||||
internal func numberOfItems(inSection identifier: String) -> Int {
|
||||
|
||||
return self.numberOfItems(inSection: identifier as NSString)
|
||||
}
|
||||
|
||||
internal func itemIdentifiers(inSection identifier: String) -> [NSManagedObjectID] {
|
||||
|
||||
return self.itemIdentifiers(inSection: identifier as NSString)
|
||||
}
|
||||
|
||||
internal func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> String? {
|
||||
|
||||
return self.sectionIdentifier(containingItem: identifier) as NSString? as String?
|
||||
}
|
||||
|
||||
internal func indexOfSection(_ identifier: String) -> Int? {
|
||||
|
||||
return self.indexOfSection(identifier as NSString)
|
||||
}
|
||||
|
||||
internal mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: String?) {
|
||||
|
||||
self.appendItems(identifiers, toSection: sectionIdentifier as NSString?)
|
||||
}
|
||||
|
||||
internal mutating func appendSections(_ identifiers: [String]) {
|
||||
|
||||
self.appendSections(identifiers as [NSString])
|
||||
}
|
||||
|
||||
internal mutating func insertSections(_ identifiers: [String], beforeSection toIdentifier: String) {
|
||||
|
||||
self.insertSections(identifiers as [NSString], beforeSection: toIdentifier as NSString)
|
||||
}
|
||||
|
||||
internal mutating func insertSections(_ identifiers: [String], afterSection toIdentifier: String) {
|
||||
|
||||
return self.insertSections(identifiers as [NSString], afterSection: toIdentifier as NSString)
|
||||
}
|
||||
|
||||
internal mutating func deleteSections(_ identifiers: [String]) {
|
||||
|
||||
self.deleteSections(identifiers as [NSString])
|
||||
}
|
||||
|
||||
internal mutating func moveSection(_ identifier: String, beforeSection toIdentifier: String) {
|
||||
|
||||
self.moveSection(identifier as NSString, beforeSection: toIdentifier as NSString)
|
||||
}
|
||||
|
||||
internal mutating func moveSection(_ identifier: String, afterSection toIdentifier: String) {
|
||||
|
||||
self.moveSection(identifier as NSString, afterSection: toIdentifier as NSString)
|
||||
}
|
||||
|
||||
internal mutating func reloadSections(_ identifiers: [String]) {
|
||||
|
||||
self.reloadSections(identifiers as [NSString])
|
||||
}
|
||||
}
|
||||
extension NSDiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol where SectionIdentifierType == String, ItemIdentifierType == NSManagedObjectID {}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
141
Sources/Internals.DiffableDataUIDispatcher.Changeset.swift
Normal file
141
Sources/Internals.DiffableDataUIDispatcher.Changeset.swift
Normal file
@@ -0,0 +1,141 @@
|
||||
//
|
||||
// Internals.DiffableDataUIDispatcher.Changeset.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.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - Internals.DiffableDataUIDispatcher
|
||||
|
||||
extension Internals.DiffableDataUIDispatcher {
|
||||
|
||||
// MARK: - ChangeSet
|
||||
|
||||
internal struct Changeset<C: Collection>: Equatable where C: Equatable {
|
||||
|
||||
var data: C
|
||||
var sectionDeleted: [Int]
|
||||
var sectionInserted: [Int]
|
||||
var sectionUpdated: [Int]
|
||||
var sectionMoved: [(source: Int, target: Int)]
|
||||
|
||||
var elementDeleted: [ElementPath]
|
||||
var elementInserted: [ElementPath]
|
||||
var elementUpdated: [ElementPath]
|
||||
var elementMoved: [(source: ElementPath, target: ElementPath)]
|
||||
|
||||
@inlinable
|
||||
init(
|
||||
data: C,
|
||||
sectionDeleted: [Int] = [],
|
||||
sectionInserted: [Int] = [],
|
||||
sectionUpdated: [Int] = [],
|
||||
sectionMoved: [(source: Int, target: Int)] = [],
|
||||
elementDeleted: [ElementPath] = [],
|
||||
elementInserted: [ElementPath] = [],
|
||||
elementUpdated: [ElementPath] = [],
|
||||
elementMoved: [(source: ElementPath, target: ElementPath)] = []
|
||||
) {
|
||||
self.data = data
|
||||
self.sectionDeleted = sectionDeleted
|
||||
self.sectionInserted = sectionInserted
|
||||
self.sectionUpdated = sectionUpdated
|
||||
self.sectionMoved = sectionMoved
|
||||
self.elementDeleted = elementDeleted
|
||||
self.elementInserted = elementInserted
|
||||
self.elementUpdated = elementUpdated
|
||||
self.elementMoved = elementMoved
|
||||
}
|
||||
|
||||
@inlinable
|
||||
var sectionChangeCount: Int {
|
||||
|
||||
return self.sectionDeleted.count
|
||||
+ self.sectionInserted.count
|
||||
+ self.sectionUpdated.count
|
||||
+ self.sectionMoved.count
|
||||
}
|
||||
|
||||
@inlinable
|
||||
var elementChangeCount: Int {
|
||||
|
||||
return self.elementDeleted.count
|
||||
+ self.elementInserted.count
|
||||
+ self.elementUpdated.count
|
||||
+ self.elementMoved.count
|
||||
}
|
||||
|
||||
@inlinable
|
||||
var changeCount: Int {
|
||||
|
||||
return self.sectionChangeCount + self.elementChangeCount
|
||||
}
|
||||
|
||||
@inlinable
|
||||
var hasSectionChanges: Bool {
|
||||
|
||||
return self.sectionChangeCount > 0
|
||||
}
|
||||
|
||||
@inlinable
|
||||
var hasElementChanges: Bool {
|
||||
|
||||
return self.elementChangeCount > 0
|
||||
}
|
||||
|
||||
@inlinable
|
||||
var hasChanges: Bool {
|
||||
|
||||
return self.changeCount > 0
|
||||
}
|
||||
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
static func == (lhs: Changeset, rhs: Changeset) -> Bool {
|
||||
return lhs.data == rhs.data
|
||||
&& Set(lhs.sectionDeleted) == Set(rhs.sectionDeleted)
|
||||
&& Set(lhs.sectionInserted) == Set(rhs.sectionInserted)
|
||||
&& Set(lhs.sectionUpdated) == Set(rhs.sectionUpdated)
|
||||
&& Set(lhs.sectionMoved.map(HashablePair.init)) == Set(rhs.sectionMoved.map(HashablePair.init))
|
||||
&& Set(lhs.elementDeleted) == Set(rhs.elementDeleted)
|
||||
&& Set(lhs.elementInserted) == Set(rhs.elementInserted)
|
||||
&& Set(lhs.elementUpdated) == Set(rhs.elementUpdated)
|
||||
&& Set(lhs.elementMoved.map(HashablePair.init)) == Set(rhs.elementMoved.map(HashablePair.init))
|
||||
}
|
||||
|
||||
|
||||
// MARK: - HashablePair
|
||||
|
||||
private struct HashablePair<H: Hashable>: Hashable {
|
||||
|
||||
let first: H
|
||||
let second: H
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
320
Sources/Internals.DiffableDataUIDispatcher.DiffResult.swift
Normal file
320
Sources/Internals.DiffableDataUIDispatcher.DiffResult.swift
Normal file
@@ -0,0 +1,320 @@
|
||||
//
|
||||
// Internals.DiffableDataUIDispatcher.DiffResult.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.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - Internals.DiffableDataUIDispatcher
|
||||
|
||||
extension Internals.DiffableDataUIDispatcher {
|
||||
|
||||
// MARK: - DiffResult
|
||||
|
||||
@usableFromInline
|
||||
internal struct DiffResult<Index> {
|
||||
|
||||
@usableFromInline
|
||||
internal let deleted: [Index]
|
||||
@usableFromInline
|
||||
internal let inserted: [Index]
|
||||
@usableFromInline
|
||||
internal let updated: [Index]
|
||||
@usableFromInline
|
||||
internal let moved: [(source: Index, target: Index)]
|
||||
@usableFromInline
|
||||
internal let sourceTraces: ContiguousArray<Trace<Int>>
|
||||
@usableFromInline
|
||||
internal let targetReferences: ContiguousArray<Int?>
|
||||
|
||||
@inlinable
|
||||
@discardableResult
|
||||
static func diff<E: Differentiable>(
|
||||
source: ContiguousArray<E>,
|
||||
target: ContiguousArray<E>,
|
||||
useTargetIndexForUpdated: Bool,
|
||||
mapIndex: (Int) -> Index,
|
||||
updatedElementsPointer: UnsafeMutablePointer<ContiguousArray<E>>? = nil,
|
||||
notDeletedElementsPointer: UnsafeMutablePointer<ContiguousArray<E>>? = nil
|
||||
) -> DiffResult<Index> {
|
||||
|
||||
var deleted = [Index]()
|
||||
var inserted = [Index]()
|
||||
var updated = [Index]()
|
||||
var moved = [(source: Index, target: Index)]()
|
||||
|
||||
var sourceTraces = ContiguousArray<Trace<Int>>()
|
||||
var sourceIdentifiers = ContiguousArray<E.DifferenceIdentifier>()
|
||||
var targetReferences = ContiguousArray<Int?>(repeating: nil, count: target.count)
|
||||
|
||||
sourceTraces.reserveCapacity(source.count)
|
||||
sourceIdentifiers.reserveCapacity(source.count)
|
||||
|
||||
for sourceElement in source {
|
||||
|
||||
sourceTraces.append(Trace())
|
||||
sourceIdentifiers.append(sourceElement.differenceIdentifier)
|
||||
}
|
||||
sourceIdentifiers.withUnsafeBufferPointer { bufferPointer in
|
||||
|
||||
var sourceOccurrencesTable = [TableKey<E.DifferenceIdentifier>: Occurrence](minimumCapacity: source.count)
|
||||
|
||||
for sourceIndex in sourceIdentifiers.indices {
|
||||
|
||||
let pointer = bufferPointer.baseAddress!.advanced(by: sourceIndex)
|
||||
let key = TableKey(pointer: pointer)
|
||||
|
||||
switch sourceOccurrencesTable[key] {
|
||||
case .none:
|
||||
sourceOccurrencesTable[key] = .unique(index: sourceIndex)
|
||||
|
||||
case .unique(let otherIndex)?:
|
||||
let reference = IndicesReference([otherIndex, sourceIndex])
|
||||
sourceOccurrencesTable[key] = .duplicate(reference: reference)
|
||||
|
||||
case .duplicate(let reference)?:
|
||||
reference.push(sourceIndex)
|
||||
}
|
||||
}
|
||||
for targetIndex in target.indices {
|
||||
|
||||
var targetIdentifier = target[targetIndex].differenceIdentifier
|
||||
let key = TableKey(pointer: &targetIdentifier)
|
||||
|
||||
switch sourceOccurrencesTable[key] {
|
||||
|
||||
case .none:
|
||||
break
|
||||
|
||||
case .unique(let sourceIndex)?:
|
||||
if case .none = sourceTraces[sourceIndex].reference {
|
||||
|
||||
targetReferences[targetIndex] = sourceIndex
|
||||
sourceTraces[sourceIndex].reference = targetIndex
|
||||
}
|
||||
|
||||
case .duplicate(let reference)?:
|
||||
if let sourceIndex = reference.next() {
|
||||
|
||||
targetReferences[targetIndex] = sourceIndex
|
||||
sourceTraces[sourceIndex].reference = targetIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var offsetByDelete = 0
|
||||
var untrackedSourceIndex: Int? = 0
|
||||
|
||||
for sourceIndex in source.indices {
|
||||
|
||||
sourceTraces[sourceIndex].deleteOffset = offsetByDelete
|
||||
|
||||
if let targetIndex = sourceTraces[sourceIndex].reference {
|
||||
|
||||
let targetElement = target[targetIndex]
|
||||
updatedElementsPointer?.pointee.append(targetElement)
|
||||
notDeletedElementsPointer?.pointee.append(targetElement)
|
||||
}
|
||||
else {
|
||||
|
||||
let sourceElement = source[sourceIndex]
|
||||
deleted.append(mapIndex(sourceIndex))
|
||||
sourceTraces[sourceIndex].isTracked = true
|
||||
offsetByDelete += 1
|
||||
updatedElementsPointer?.pointee.append(sourceElement)
|
||||
}
|
||||
}
|
||||
for targetIndex in target.indices {
|
||||
|
||||
untrackedSourceIndex = untrackedSourceIndex.flatMap { index in
|
||||
|
||||
sourceTraces.suffix(from: index).firstIndex { !$0.isTracked }
|
||||
}
|
||||
if let sourceIndex = targetReferences[targetIndex] {
|
||||
|
||||
sourceTraces[sourceIndex].isTracked = true
|
||||
|
||||
let sourceElement = source[sourceIndex]
|
||||
let targetElement = target[targetIndex]
|
||||
|
||||
if !targetElement.isContentEqual(to: sourceElement) {
|
||||
|
||||
updated.append(mapIndex(useTargetIndexForUpdated ? targetIndex : sourceIndex))
|
||||
}
|
||||
if sourceIndex != untrackedSourceIndex {
|
||||
|
||||
let deleteOffset = sourceTraces[sourceIndex].deleteOffset
|
||||
moved.append((source: mapIndex(sourceIndex - deleteOffset), target: mapIndex(targetIndex)))
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
inserted.append(mapIndex(targetIndex))
|
||||
}
|
||||
}
|
||||
return DiffResult(
|
||||
deleted: deleted,
|
||||
inserted: inserted,
|
||||
updated: updated,
|
||||
moved: moved,
|
||||
sourceTraces: sourceTraces,
|
||||
targetReferences: targetReferences
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@inlinable
|
||||
internal init(
|
||||
deleted: [Index] = [],
|
||||
inserted: [Index] = [],
|
||||
updated: [Index] = [],
|
||||
moved: [(source: Index, target: Index)] = [],
|
||||
sourceTraces: ContiguousArray<Trace<Int>>,
|
||||
targetReferences: ContiguousArray<Int?>
|
||||
) {
|
||||
|
||||
self.deleted = deleted
|
||||
self.inserted = inserted
|
||||
self.updated = updated
|
||||
self.moved = moved
|
||||
self.sourceTraces = sourceTraces
|
||||
self.targetReferences = targetReferences
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Trace
|
||||
|
||||
@usableFromInline
|
||||
internal struct Trace<Index> {
|
||||
|
||||
@usableFromInline
|
||||
internal var reference: Index?
|
||||
|
||||
@usableFromInline
|
||||
internal var deleteOffset = 0
|
||||
|
||||
@usableFromInline
|
||||
internal var isTracked = false
|
||||
|
||||
@inlinable
|
||||
init() {}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Occurrence
|
||||
|
||||
@usableFromInline
|
||||
internal enum Occurrence {
|
||||
|
||||
case unique(index: Int)
|
||||
case duplicate(reference: IndicesReference)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - IndicesReference
|
||||
|
||||
@usableFromInline
|
||||
internal final class IndicesReference {
|
||||
|
||||
@usableFromInline
|
||||
internal var indices: ContiguousArray<Int>
|
||||
|
||||
@usableFromInline
|
||||
internal var position = 0
|
||||
|
||||
@inlinable
|
||||
internal init(_ indices: ContiguousArray<Int>) {
|
||||
|
||||
self.indices = indices
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal func push(_ index: Int) {
|
||||
|
||||
self.indices.append(index)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal func next() -> Int? {
|
||||
|
||||
guard self.position < self.indices.endIndex else {
|
||||
|
||||
return nil
|
||||
}
|
||||
defer {
|
||||
|
||||
self.position += 1
|
||||
}
|
||||
return self.indices[self.position]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - TableKey
|
||||
|
||||
@usableFromInline
|
||||
internal struct TableKey<T: Hashable>: Hashable {
|
||||
|
||||
@usableFromInline
|
||||
internal let pointeeHashValue: Int
|
||||
|
||||
@usableFromInline
|
||||
internal let pointer: UnsafePointer<T>
|
||||
|
||||
@inlinable
|
||||
internal init(pointer: UnsafePointer<T>) {
|
||||
|
||||
self.pointeeHashValue = pointer.pointee.hashValue
|
||||
self.pointer = pointer
|
||||
}
|
||||
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
@inlinable
|
||||
internal static func == (lhs: TableKey, rhs: TableKey) -> Bool {
|
||||
|
||||
return lhs.pointeeHashValue == rhs.pointeeHashValue
|
||||
&& (lhs.pointer.distance(to: rhs.pointer) == 0
|
||||
|| lhs.pointer.pointee == rhs.pointer.pointee)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
@inlinable
|
||||
internal func hash(into hasher: inout Hasher) {
|
||||
|
||||
hasher.combine(pointeeHashValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
516
Sources/Internals.DiffableDataUIDispatcher.StagedChangeset.swift
Normal file
516
Sources/Internals.DiffableDataUIDispatcher.StagedChangeset.swift
Normal file
@@ -0,0 +1,516 @@
|
||||
//
|
||||
// Internals.DiffableDataUIDispatcher.StagedChangeset.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.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - Internals.DiffableDataUIDispatcher
|
||||
|
||||
extension Internals.DiffableDataUIDispatcher {
|
||||
|
||||
// MARK: - StagedChangeset
|
||||
|
||||
internal struct StagedChangeset<C: Collection>: ExpressibleByArrayLiteral, Equatable, RandomAccessCollection, RangeReplaceableCollection where C: Equatable {
|
||||
|
||||
@usableFromInline
|
||||
var changesets: ContiguousArray<Changeset<C>>
|
||||
|
||||
@inlinable
|
||||
init<S: Sequence>(_ changesets: S) where S.Element == Changeset<C> {
|
||||
|
||||
self.changesets = ContiguousArray(changesets)
|
||||
}
|
||||
|
||||
|
||||
// MARK: ExpressibleByArrayLiteral
|
||||
|
||||
@inlinable
|
||||
init(arrayLiteral elements: Changeset<C>...) {
|
||||
|
||||
self.init(elements)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
@inlinable
|
||||
static func == (lhs: StagedChangeset, rhs: StagedChangeset) -> Bool {
|
||||
|
||||
return lhs.changesets == rhs.changesets
|
||||
}
|
||||
|
||||
|
||||
// MARK: Sequence
|
||||
|
||||
typealias Element = Changeset<C>
|
||||
|
||||
|
||||
// MARK: RandomAccessCollection
|
||||
|
||||
@inlinable
|
||||
var startIndex: Int {
|
||||
|
||||
return self.changesets.startIndex
|
||||
}
|
||||
|
||||
@inlinable
|
||||
var endIndex: Int {
|
||||
|
||||
return self.changesets.endIndex
|
||||
}
|
||||
|
||||
@inlinable
|
||||
func index(after i: Int) -> Int {
|
||||
|
||||
return self.changesets.index(after: i)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
subscript(position: Int) -> Changeset<C> {
|
||||
|
||||
get { return self.changesets[position] }
|
||||
set { self.changesets[position] = newValue }
|
||||
}
|
||||
|
||||
|
||||
// MARK: RangeReplaceableCollection
|
||||
|
||||
@inlinable
|
||||
init() {
|
||||
|
||||
self.init([])
|
||||
}
|
||||
|
||||
@inlinable
|
||||
mutating func replaceSubrange<C2: Collection, R: RangeExpression>(_ subrange: R, with newElements: C2) where C2.Element == Changeset<C>, R.Bound == Int {
|
||||
|
||||
self.changesets.replaceSubrange(subrange, with: newElements)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: Differentiable
|
||||
|
||||
extension Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: Differentiable {
|
||||
|
||||
@inlinable
|
||||
internal init(source: C, target: C) {
|
||||
|
||||
self.init(source: source, target: target, section: 0)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal init(source: C, target: C, section: Int) {
|
||||
|
||||
typealias Changeset = Internals.DiffableDataUIDispatcher<O>.Changeset
|
||||
typealias ElementPath = Internals.DiffableDataUIDispatcher<O>.ElementPath
|
||||
typealias DiffResult = Internals.DiffableDataUIDispatcher<O>.DiffResult
|
||||
|
||||
let sourceElements = ContiguousArray(source)
|
||||
let targetElements = ContiguousArray(target)
|
||||
if sourceElements.isEmpty && targetElements.isEmpty {
|
||||
|
||||
self.init()
|
||||
return
|
||||
}
|
||||
if !sourceElements.isEmpty && targetElements.isEmpty {
|
||||
|
||||
self.init(
|
||||
[
|
||||
Changeset(
|
||||
data: target,
|
||||
elementDeleted: sourceElements.indices.map {
|
||||
ElementPath(
|
||||
element: $0,
|
||||
section: section
|
||||
)
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
return
|
||||
}
|
||||
if sourceElements.isEmpty && !targetElements.isEmpty {
|
||||
|
||||
self.init(
|
||||
[
|
||||
Changeset(
|
||||
data: target,
|
||||
elementInserted: targetElements.indices.map {
|
||||
ElementPath(
|
||||
element: $0,
|
||||
section: section
|
||||
)
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
return
|
||||
}
|
||||
var firstStageElements = ContiguousArray<C.Element>()
|
||||
var secondStageElements = ContiguousArray<C.Element>()
|
||||
let result = DiffResult.diff(
|
||||
source: sourceElements,
|
||||
target: targetElements,
|
||||
useTargetIndexForUpdated: false,
|
||||
mapIndex: { ElementPath(element: $0, section: section) },
|
||||
updatedElementsPointer: &firstStageElements,
|
||||
notDeletedElementsPointer: &secondStageElements
|
||||
)
|
||||
|
||||
var changesets = ContiguousArray<Changeset<C>>()
|
||||
if !result.updated.isEmpty {
|
||||
|
||||
changesets.append(
|
||||
Changeset(
|
||||
data: C(firstStageElements),
|
||||
elementUpdated: result.updated
|
||||
)
|
||||
)
|
||||
}
|
||||
if !result.deleted.isEmpty {
|
||||
|
||||
changesets.append(
|
||||
Changeset(
|
||||
data: C(secondStageElements),
|
||||
elementDeleted: result.deleted
|
||||
)
|
||||
)
|
||||
}
|
||||
if !result.inserted.isEmpty || !result.moved.isEmpty {
|
||||
|
||||
changesets.append(
|
||||
Changeset(
|
||||
data: target,
|
||||
elementInserted: result.inserted,
|
||||
elementMoved: result.moved
|
||||
)
|
||||
)
|
||||
}
|
||||
if !changesets.isEmpty {
|
||||
|
||||
let index = changesets.index(before: changesets.endIndex)
|
||||
changesets[index].data = target
|
||||
}
|
||||
self.init(changesets)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: DifferentiableSection
|
||||
|
||||
extension Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: DifferentiableSection {
|
||||
|
||||
@inlinable
|
||||
internal init(source: C, target: C) {
|
||||
|
||||
typealias Section = C.Element
|
||||
typealias SectionIdentifier = C.Element.DifferenceIdentifier
|
||||
typealias Element = C.Element.Collection.Element
|
||||
typealias ElementIdentifier = C.Element.Collection.Element.DifferenceIdentifier
|
||||
|
||||
typealias Changeset = Internals.DiffableDataUIDispatcher<O>.Changeset
|
||||
typealias ElementPath = Internals.DiffableDataUIDispatcher<O>.ElementPath
|
||||
typealias DiffResult = Internals.DiffableDataUIDispatcher<O>.DiffResult
|
||||
|
||||
typealias Trace = Internals.DiffableDataUIDispatcher<O>.DiffResult<Section>.Trace
|
||||
typealias TableKey = Internals.DiffableDataUIDispatcher<O>.DiffResult<Section>.TableKey
|
||||
typealias Occurrence = Internals.DiffableDataUIDispatcher<O>.DiffResult<Section>.Occurrence
|
||||
typealias IndicesReference = Internals.DiffableDataUIDispatcher<O>.DiffResult<Section>.IndicesReference
|
||||
|
||||
let sourceSections = ContiguousArray(source)
|
||||
let targetSections = ContiguousArray(target)
|
||||
|
||||
let contiguousSourceSections = ContiguousArray(sourceSections.map { ContiguousArray($0.elements) })
|
||||
let contiguousTargetSections = ContiguousArray(targetSections.map { ContiguousArray($0.elements) })
|
||||
|
||||
var firstStageSections = sourceSections
|
||||
var secondStageSections = ContiguousArray<Section>()
|
||||
var thirdStageSections = ContiguousArray<Section>()
|
||||
var fourthStageSections = ContiguousArray<Section>()
|
||||
|
||||
var sourceElementTraces = contiguousSourceSections.map { section in
|
||||
|
||||
ContiguousArray(repeating: Trace<ElementPath>(), count: section.count)
|
||||
}
|
||||
var targetElementReferences = contiguousTargetSections.map { section in
|
||||
|
||||
ContiguousArray<ElementPath?>(repeating: nil, count: section.count)
|
||||
}
|
||||
|
||||
let flattenSourceCount = contiguousSourceSections.reduce(into: 0) { $0 += $1.count }
|
||||
var flattenSourceIdentifiers = ContiguousArray<ElementIdentifier>()
|
||||
var flattenSourceElementPaths = ContiguousArray<ElementPath>()
|
||||
|
||||
thirdStageSections.reserveCapacity(contiguousTargetSections.count)
|
||||
fourthStageSections.reserveCapacity(contiguousTargetSections.count)
|
||||
|
||||
flattenSourceIdentifiers.reserveCapacity(flattenSourceCount)
|
||||
flattenSourceElementPaths.reserveCapacity(flattenSourceCount)
|
||||
|
||||
let sectionResult = DiffResult.diff(
|
||||
source: sourceSections,
|
||||
target: targetSections,
|
||||
useTargetIndexForUpdated: true,
|
||||
mapIndex: { $0 }
|
||||
)
|
||||
|
||||
var elementDeleted = [ElementPath]()
|
||||
var elementInserted = [ElementPath]()
|
||||
var elementUpdated = [ElementPath]()
|
||||
var elementMoved = [(source: ElementPath, target: ElementPath)]()
|
||||
|
||||
for sourceSectionIndex in contiguousSourceSections.indices {
|
||||
|
||||
for sourceElementIndex in contiguousSourceSections[sourceSectionIndex].indices {
|
||||
|
||||
let sourceElementPath = ElementPath(element: sourceElementIndex, section: sourceSectionIndex)
|
||||
let sourceElement = contiguousSourceSections[sourceElementPath]
|
||||
flattenSourceIdentifiers.append(sourceElement.differenceIdentifier)
|
||||
flattenSourceElementPaths.append(sourceElementPath)
|
||||
}
|
||||
}
|
||||
flattenSourceIdentifiers.withUnsafeBufferPointer { bufferPointer in
|
||||
|
||||
var sourceOccurrencesTable = [TableKey<ElementIdentifier>: Occurrence](minimumCapacity: flattenSourceCount)
|
||||
|
||||
for flattenSourceIndex in flattenSourceIdentifiers.indices {
|
||||
let pointer = bufferPointer.baseAddress!.advanced(by: flattenSourceIndex)
|
||||
let key = TableKey(pointer: pointer)
|
||||
|
||||
switch sourceOccurrencesTable[key] {
|
||||
|
||||
case .none:
|
||||
sourceOccurrencesTable[key] = .unique(index: flattenSourceIndex)
|
||||
|
||||
case .unique(let otherIndex)?:
|
||||
let reference = IndicesReference([otherIndex, flattenSourceIndex])
|
||||
sourceOccurrencesTable[key] = .duplicate(reference: reference)
|
||||
|
||||
case .duplicate(let reference)?:
|
||||
reference.push(flattenSourceIndex)
|
||||
}
|
||||
}
|
||||
|
||||
for targetSectionIndex in contiguousTargetSections.indices {
|
||||
|
||||
let targetElements = contiguousTargetSections[targetSectionIndex]
|
||||
for targetElementIndex in targetElements.indices {
|
||||
|
||||
var targetIdentifier = targetElements[targetElementIndex].differenceIdentifier
|
||||
let key = TableKey(pointer: &targetIdentifier)
|
||||
|
||||
switch sourceOccurrencesTable[key] {
|
||||
|
||||
case .none:
|
||||
break
|
||||
|
||||
case .unique(let flattenSourceIndex)?:
|
||||
let sourceElementPath = flattenSourceElementPaths[flattenSourceIndex]
|
||||
let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex)
|
||||
if case .none = sourceElementTraces[sourceElementPath].reference {
|
||||
|
||||
targetElementReferences[targetElementPath] = sourceElementPath
|
||||
sourceElementTraces[sourceElementPath].reference = targetElementPath
|
||||
}
|
||||
|
||||
case .duplicate(let reference)?:
|
||||
if let flattenSourceIndex = reference.next() {
|
||||
|
||||
let sourceElementPath = flattenSourceElementPaths[flattenSourceIndex]
|
||||
let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex)
|
||||
targetElementReferences[targetElementPath] = sourceElementPath
|
||||
sourceElementTraces[sourceElementPath].reference = targetElementPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for sourceSectionIndex in contiguousSourceSections.indices {
|
||||
|
||||
let sourceSection = sourceSections[sourceSectionIndex]
|
||||
let sourceElements = contiguousSourceSections[sourceSectionIndex]
|
||||
var firstStageElements = sourceElements
|
||||
|
||||
if case .some = sectionResult.sourceTraces[sourceSectionIndex].reference {
|
||||
|
||||
var offsetByDelete = 0
|
||||
var secondStageElements = ContiguousArray<Element>()
|
||||
for sourceElementIndex in sourceElements.indices {
|
||||
|
||||
let sourceElementPath = ElementPath(element: sourceElementIndex, section: sourceSectionIndex)
|
||||
sourceElementTraces[sourceElementPath].deleteOffset = offsetByDelete
|
||||
|
||||
if let targetElementPath = sourceElementTraces[sourceElementPath].reference,
|
||||
case .some = sectionResult.targetReferences[targetElementPath.section] {
|
||||
|
||||
let targetElement = contiguousTargetSections[targetElementPath]
|
||||
firstStageElements[sourceElementIndex] = targetElement
|
||||
secondStageElements.append(targetElement)
|
||||
continue
|
||||
}
|
||||
elementDeleted.append(sourceElementPath)
|
||||
sourceElementTraces[sourceElementPath].isTracked = true
|
||||
offsetByDelete += 1
|
||||
}
|
||||
|
||||
let secondStageSection = Section(source: sourceSection, elements: secondStageElements)
|
||||
secondStageSections.append(secondStageSection)
|
||||
}
|
||||
|
||||
let firstStageSection = Section(source: sourceSection, elements: firstStageElements)
|
||||
firstStageSections[sourceSectionIndex] = firstStageSection
|
||||
}
|
||||
for targetSectionIndex in contiguousTargetSections.indices {
|
||||
|
||||
guard let sourceSectionIndex = sectionResult.targetReferences[targetSectionIndex] else {
|
||||
|
||||
thirdStageSections.append(targetSections[targetSectionIndex])
|
||||
fourthStageSections.append(targetSections[targetSectionIndex])
|
||||
continue
|
||||
}
|
||||
|
||||
var untrackedSourceIndex: Int? = 0
|
||||
let targetElements = contiguousTargetSections[targetSectionIndex]
|
||||
let sectionDeleteOffset = sectionResult.sourceTraces[sourceSectionIndex].deleteOffset
|
||||
let thirdStageSection = secondStageSections[sourceSectionIndex - sectionDeleteOffset]
|
||||
thirdStageSections.append(thirdStageSection)
|
||||
|
||||
var fourthStageElements = ContiguousArray<Element>()
|
||||
fourthStageElements.reserveCapacity(targetElements.count)
|
||||
|
||||
for targetElementIndex in targetElements.indices {
|
||||
|
||||
untrackedSourceIndex = untrackedSourceIndex.flatMap { index in
|
||||
|
||||
sourceElementTraces[sourceSectionIndex].suffix(from: index).firstIndex { !$0.isTracked }
|
||||
}
|
||||
let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex)
|
||||
let targetElement = contiguousTargetSections[targetElementPath]
|
||||
|
||||
guard
|
||||
let sourceElementPath = targetElementReferences[targetElementPath],
|
||||
let movedSourceSectionIndex = sectionResult.sourceTraces[sourceElementPath.section].reference
|
||||
else {
|
||||
|
||||
fourthStageElements.append(targetElement)
|
||||
elementInserted.append(targetElementPath)
|
||||
continue
|
||||
}
|
||||
sourceElementTraces[sourceElementPath].isTracked = true
|
||||
|
||||
let sourceElement = contiguousSourceSections[sourceElementPath]
|
||||
fourthStageElements.append(targetElement)
|
||||
|
||||
if !targetElement.isContentEqual(to: sourceElement) {
|
||||
|
||||
elementUpdated.append(sourceElementPath)
|
||||
}
|
||||
if sourceElementPath.section != sourceSectionIndex || sourceElementPath.element != untrackedSourceIndex {
|
||||
|
||||
let deleteOffset = sourceElementTraces[sourceElementPath].deleteOffset
|
||||
let moveSourceElementPath = ElementPath(element: sourceElementPath.element - deleteOffset, section: movedSourceSectionIndex)
|
||||
elementMoved.append((source: moveSourceElementPath, target: targetElementPath))
|
||||
}
|
||||
}
|
||||
let fourthStageSection = Section(source: thirdStageSection, elements: fourthStageElements)
|
||||
fourthStageSections.append(fourthStageSection)
|
||||
}
|
||||
|
||||
var changesets = ContiguousArray<Changeset<C>>()
|
||||
if !elementUpdated.isEmpty {
|
||||
|
||||
changesets.append(
|
||||
Changeset(
|
||||
data: C(firstStageSections),
|
||||
elementUpdated: elementUpdated
|
||||
)
|
||||
)
|
||||
}
|
||||
if !sectionResult.deleted.isEmpty || !elementDeleted.isEmpty {
|
||||
|
||||
changesets.append(
|
||||
Changeset(
|
||||
data: C(secondStageSections),
|
||||
sectionDeleted: sectionResult.deleted,
|
||||
elementDeleted: elementDeleted
|
||||
)
|
||||
)
|
||||
}
|
||||
if !sectionResult.inserted.isEmpty || !sectionResult.moved.isEmpty {
|
||||
|
||||
changesets.append(
|
||||
Changeset(
|
||||
data: C(thirdStageSections),
|
||||
sectionInserted: sectionResult.inserted,
|
||||
sectionMoved: sectionResult.moved
|
||||
)
|
||||
)
|
||||
}
|
||||
if !elementInserted.isEmpty || !elementMoved.isEmpty {
|
||||
|
||||
changesets.append(
|
||||
Changeset(
|
||||
data: C(fourthStageSections),
|
||||
elementInserted: elementInserted,
|
||||
elementMoved: elementMoved
|
||||
)
|
||||
)
|
||||
}
|
||||
if !sectionResult.updated.isEmpty {
|
||||
|
||||
changesets.append(
|
||||
Changeset(
|
||||
data: target,
|
||||
sectionUpdated: sectionResult.updated
|
||||
)
|
||||
)
|
||||
}
|
||||
if !changesets.isEmpty {
|
||||
|
||||
let index = changesets.index(before: changesets.endIndex)
|
||||
changesets[index].data = target
|
||||
}
|
||||
self.init(changesets)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - MutableCollection
|
||||
|
||||
extension MutableCollection where Element: MutableCollection, Index == Int, Element.Index == Int {
|
||||
|
||||
@inlinable
|
||||
internal subscript<O>(path: Internals.DiffableDataUIDispatcher<O>.ElementPath) -> Element.Element {
|
||||
|
||||
get { return self[path.section][path.element] }
|
||||
set { self[path.section][path.element] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
229
Sources/Internals.DiffableDataUIDispatcher.swift
Normal file
229
Sources/Internals.DiffableDataUIDispatcher.swift
Normal file
@@ -0,0 +1,229 @@
|
||||
//
|
||||
// Internals.DiffableDataUIDispatcher.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.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
import CoreData
|
||||
|
||||
#if canImport(QuartzCore)
|
||||
import QuartzCore
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
extension Internals {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
@usableFromInline
|
||||
internal final class DiffableDataUIDispatcher<O: DynamicObject> {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
typealias ObjectType = O
|
||||
|
||||
init(dataStack: DataStack) {
|
||||
|
||||
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) {
|
||||
|
||||
self.dispatcher.dispatch { [weak self] in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.currentSnapshot = snapshot
|
||||
|
||||
let newSections = snapshot.sections
|
||||
guard let view = view else {
|
||||
|
||||
return self.sections = newSections
|
||||
}
|
||||
|
||||
let performDiffingUpdates: () -> Void = {
|
||||
|
||||
let changeset = StagedChangeset(source: self.sections, target: newSections)
|
||||
performUpdates(view, changeset) { sections in
|
||||
|
||||
self.sections = sections
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(QuartzCore)
|
||||
|
||||
if !animatingDifferences {
|
||||
|
||||
CATransaction.begin()
|
||||
CATransaction.setDisableActions(true)
|
||||
|
||||
performDiffingUpdates()
|
||||
|
||||
CATransaction.commit()
|
||||
return
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
performDiffingUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
func snapshot() -> DiffableDataSourceSnapshot {
|
||||
|
||||
var snapshot: DiffableDataSourceSnapshot = .init()
|
||||
snapshot.sections = self.currentSnapshot.sections
|
||||
return snapshot
|
||||
}
|
||||
|
||||
func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
guard (0 ..< self.sections.endIndex) ~= indexPath.section else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let items = self.sections[indexPath.section].elements
|
||||
guard (0 ..< items.endIndex) ~= indexPath.item else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return items[indexPath.item].differenceIdentifier
|
||||
}
|
||||
|
||||
func indexPath(for itemIdentifier: O.ObjectID) -> IndexPath? {
|
||||
|
||||
let indexPathMap: [O.ObjectID: IndexPath] = self.sections.enumerated().reduce(into: [:]) { result, section in
|
||||
|
||||
for (itemIndex, item) in section.element.elements.enumerated() {
|
||||
|
||||
result[item.differenceIdentifier] = IndexPath(
|
||||
item: itemIndex,
|
||||
section: section.offset
|
||||
)
|
||||
}
|
||||
}
|
||||
return indexPathMap[itemIdentifier]
|
||||
}
|
||||
|
||||
func numberOfSections() -> Int {
|
||||
|
||||
return self.sections.count
|
||||
}
|
||||
|
||||
func numberOfItems(inSection section: Int) -> Int {
|
||||
|
||||
return self.sections[section].elements.count
|
||||
}
|
||||
|
||||
func sectionIdentifier(inSection section: Int) -> String {
|
||||
|
||||
return self.sections[section].differenceIdentifier
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let dispatcher: MainThreadSerialDispatcher = .init()
|
||||
private let dataStack: DataStack
|
||||
|
||||
private var currentSnapshot: Internals.DiffableDataSourceSnapshot = .init()
|
||||
private var sections: [Internals.DiffableDataSourceSnapshot.Section] = []
|
||||
|
||||
|
||||
// MARK: - ElementPath
|
||||
|
||||
@usableFromInline
|
||||
internal struct ElementPath: Hashable {
|
||||
|
||||
@usableFromInline
|
||||
var element: Int
|
||||
|
||||
@usableFromInline
|
||||
var section: Int
|
||||
|
||||
@inlinable
|
||||
init(element: Int, section: Int) {
|
||||
|
||||
self.element = element
|
||||
self.section = section
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - MainThreadSerialDispatcher
|
||||
|
||||
fileprivate final class MainThreadSerialDispatcher {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate init() {
|
||||
|
||||
self.executingCount.initialize(to: 0)
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
self.executingCount.deinitialize(count: 1)
|
||||
self.executingCount.deallocate()
|
||||
}
|
||||
|
||||
fileprivate func dispatch(_ action: @escaping () -> Void) {
|
||||
|
||||
let count = OSAtomicIncrement32(self.executingCount)
|
||||
if Thread.isMainThread && count == 1 {
|
||||
|
||||
action()
|
||||
OSAtomicDecrement32(executingCount)
|
||||
}
|
||||
else {
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
action()
|
||||
OSAtomicDecrement32(self.executingCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let executingCount: UnsafeMutablePointer<Int32> = .allocate(capacity: 1)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,157 +0,0 @@
|
||||
//
|
||||
// Internals.FallbackDiffableDataUIDispatcher.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.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
import CoreData
|
||||
|
||||
#if canImport(QuartzCore)
|
||||
import QuartzCore
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
extension Internals {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
// // Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
// internal final class FallbackDiffableDataUIDispatcher {
|
||||
//
|
||||
// // MARK: Internal
|
||||
//
|
||||
// internal func apply() {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// func snapshot() -> DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType> {
|
||||
// var snapshot = DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>()
|
||||
// snapshot.structure.sections = currentSnapshot.structure.sections
|
||||
// return snapshot
|
||||
// }
|
||||
//
|
||||
// func itemIdentifier(for indexPath: IndexPath) -> ItemIdentifierType? {
|
||||
// guard 0..<sections.endIndex ~= indexPath.section else {
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// let items = sections[indexPath.section].elements
|
||||
//
|
||||
// guard 0..<items.endIndex ~= indexPath.item else {
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// return items[indexPath.item].differenceIdentifier
|
||||
// }
|
||||
//
|
||||
// func unsafeItemIdentifier(for indexPath: IndexPath, file: StaticString = #file, line: UInt = #line) -> ItemIdentifierType {
|
||||
// guard let itemIdentifier = itemIdentifier(for: indexPath) else {
|
||||
// universalError("Item not found at the specified index path(\(indexPath)).")
|
||||
// }
|
||||
//
|
||||
// return itemIdentifier
|
||||
// }
|
||||
//
|
||||
// func indexPath(for itemIdentifier: ItemIdentifierType) -> IndexPath? {
|
||||
// let indexPathMap: [ItemIdentifierType: IndexPath] = sections.enumerated()
|
||||
// .reduce(into: [:]) { result, section in
|
||||
// for (itemIndex, item) in section.element.elements.enumerated() {
|
||||
// result[item.differenceIdentifier] = IndexPath(
|
||||
// item: itemIndex,
|
||||
// section: section.offset
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// return indexPathMap[itemIdentifier]
|
||||
// }
|
||||
//
|
||||
// func numberOfSections() -> Int {
|
||||
// return sections.count
|
||||
// }
|
||||
//
|
||||
// func numberOfItems(inSection section: Int) -> Int {
|
||||
// return sections[section].elements.count
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// // MARK: Private
|
||||
//
|
||||
// private let dispatcher: MainThreadSerialDispatcher = .init()
|
||||
//
|
||||
// private var currentSnapshot: Internals.DiffableDataSourceSnapshot = FallbackDiffableDataSourceSnapshot()
|
||||
// private var sections: [FallbackDiffableDataSourceSnapshot.BackingStructure.Section] = []
|
||||
//
|
||||
//
|
||||
// // MARK: - MainThreadSerialDispatcher
|
||||
//
|
||||
// fileprivate final class MainThreadSerialDispatcher {
|
||||
//
|
||||
// // MARK: FilePrivate
|
||||
//
|
||||
// fileprivate init() {
|
||||
//
|
||||
// self.executingCount.initialize(to: 0)
|
||||
// }
|
||||
//
|
||||
// deinit {
|
||||
//
|
||||
// self.executingCount.deinitialize(count: 1)
|
||||
// self.executingCount.deallocate()
|
||||
// }
|
||||
//
|
||||
// fileprivate func dispatch(_ action: @escaping () -> Void) {
|
||||
//
|
||||
// let count = OSAtomicIncrement32(self.executingCount)
|
||||
// if Thread.isMainThread && count == 1 {
|
||||
//
|
||||
// action()
|
||||
// OSAtomicDecrement32(executingCount)
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// DispatchQueue.main.async { [weak self] in
|
||||
//
|
||||
// guard let self = self else {
|
||||
//
|
||||
// return
|
||||
// }
|
||||
// action()
|
||||
// OSAtomicDecrement32(self.executingCount)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // MARK: Private
|
||||
//
|
||||
// private let executingCount: UnsafeMutablePointer<Int32> = .allocate(capacity: 1)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -41,7 +41,12 @@ import AppKit
|
||||
|
||||
internal protocol FetchedDiffableDataSourceSnapshotHandler: AnyObject {
|
||||
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: DiffableDataSourceSnapshotProtocol)
|
||||
// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
// func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshot<String, NSManagedObjectID>)
|
||||
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot)
|
||||
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String?
|
||||
}
|
||||
|
||||
|
||||
@@ -75,14 +80,14 @@ extension Internals {
|
||||
|
||||
internal func initialFetch() {
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
#endif
|
||||
// #if canImport(UIKit) || canImport(AppKit)
|
||||
//
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// #endif
|
||||
|
||||
guard let fetchedResultsController = self.fetchedResultsController else {
|
||||
|
||||
@@ -94,19 +99,19 @@ extension Internals {
|
||||
|
||||
// MARK: NSFetchedResultsControllerDelegate
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
@objc
|
||||
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
|
||||
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangeContentWith: snapshot as NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
|
||||
)
|
||||
}
|
||||
|
||||
#endif
|
||||
// #if canImport(UIKit) || canImport(AppKit)
|
||||
//
|
||||
// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
// @objc
|
||||
// dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
|
||||
//
|
||||
// self.handler?.controller(
|
||||
// controller,
|
||||
// didChangeContentWith: snapshot as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// #endif
|
||||
|
||||
@objc
|
||||
dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
@@ -118,5 +123,14 @@ extension Internals {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String) -> String? {
|
||||
|
||||
return self.handler?.controller(
|
||||
controller,
|
||||
sectionIndexTitleForSectionName: sectionName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,18 +56,19 @@ extension Internals {
|
||||
deinit {
|
||||
|
||||
self.observer.map(NotificationCenter.default.removeObserver(_:))
|
||||
self.observers.removeAllObjects()
|
||||
}
|
||||
|
||||
internal func addObserver<U: AnyObject>(_ observer: U, closure: @escaping (T) -> Void) {
|
||||
|
||||
self.observers.setObject(Closure(closure), forKey: observer)
|
||||
self.observers.setObject(Closure<T, Void>(closure), forKey: observer)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var observer: NSObjectProtocol!
|
||||
private let observers: NSMapTable<AnyObject, Closure> = .weakToStrongObjects()
|
||||
private let observers: NSMapTable<AnyObject, Closure<T, Void>> = .weakToStrongObjects()
|
||||
|
||||
private func notifyObservers(_ sharedValue: T) {
|
||||
|
||||
@@ -77,31 +78,8 @@ extension Internals {
|
||||
}
|
||||
for closure in enumerator {
|
||||
|
||||
(closure as! Closure).invoke(with: sharedValue)
|
||||
(closure as! Closure<T, Void>).invoke(with: sharedValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Closure
|
||||
|
||||
fileprivate final class Closure {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate init(_ closure: @escaping (T) -> Void) {
|
||||
|
||||
self.closure = closure
|
||||
}
|
||||
|
||||
fileprivate func invoke(with argument: T) {
|
||||
|
||||
self.closure(argument)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let closure: (T) -> Void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import Foundation
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
@usableFromInline
|
||||
internal enum Internals {
|
||||
|
||||
// MARK: Associated Objects
|
||||
|
||||
@@ -1453,7 +1453,7 @@ extension ListMonitor: FetchedResultsControllerHandler {
|
||||
)
|
||||
}
|
||||
|
||||
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String? {
|
||||
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String? {
|
||||
|
||||
return self.sectionIndexTransformer(sectionName)
|
||||
}
|
||||
|
||||
@@ -42,6 +42,24 @@ public struct ListSnapshot<O: DynamicObject>: SnapshotResult, RandomAccessCollec
|
||||
|
||||
public typealias SectionID = String
|
||||
public typealias ItemID = O.ObjectID
|
||||
|
||||
public init(byCloning snapshot: ListSnapshot<O>, for dataStack: DataStack) {
|
||||
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// self.init(
|
||||
// diffableSnapshot: snapshot.diffableSnapshot as! NSDiffableDataSourceSnapshot<String, NSManagedObjectID>,
|
||||
// context: dataStack.mainContext
|
||||
// )
|
||||
// }
|
||||
// else {
|
||||
|
||||
self.init(
|
||||
diffableSnapshot: snapshot.diffableSnapshot as! Internals.DiffableDataSourceSnapshot,
|
||||
context: dataStack.mainContext
|
||||
)
|
||||
// }
|
||||
}
|
||||
|
||||
public subscript<S: Sequence>(indices indices: S) -> [LiveObject<O>] where S.Element == Index {
|
||||
|
||||
@@ -252,13 +270,29 @@ public struct ListSnapshot<O: DynamicObject>: SnapshotResult, RandomAccessCollec
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal private(set) var diffableSnapshot: DiffableDataSourceSnapshotProtocol
|
||||
|
||||
internal init() {
|
||||
|
||||
self.diffableSnapshot = Internals.DiffableDataSourceSnapshot()
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// self.diffableSnapshot = NSDiffableDataSourceSnapshot<String, NSManagedObjectID>()
|
||||
// }
|
||||
// else {
|
||||
|
||||
self.diffableSnapshot = Internals.DiffableDataSourceSnapshot()
|
||||
// }
|
||||
self.context = nil
|
||||
}
|
||||
|
||||
internal init(diffableSnapshot: DiffableDataSourceSnapshotProtocol, context: NSManagedObjectContext) {
|
||||
// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
// internal init(diffableSnapshot: NSDiffableDataSourceSnapshot<String, NSManagedObjectID>, context: NSManagedObjectContext) {
|
||||
//
|
||||
// self.diffableSnapshot = diffableSnapshot
|
||||
// self.context = context
|
||||
// }
|
||||
|
||||
internal init(diffableSnapshot: Internals.DiffableDataSourceSnapshot, context: NSManagedObjectContext) {
|
||||
|
||||
self.diffableSnapshot = diffableSnapshot
|
||||
self.context = context
|
||||
@@ -270,5 +304,4 @@ public struct ListSnapshot<O: DynamicObject>: SnapshotResult, RandomAccessCollec
|
||||
private let id: UUID = .init()
|
||||
private let context: NSManagedObjectContext?
|
||||
|
||||
private var diffableSnapshot: DiffableDataSourceSnapshotProtocol
|
||||
}
|
||||
|
||||
@@ -45,29 +45,6 @@ public final class LiveList<O: DynamicObject>: Hashable {
|
||||
public typealias SectionID = SnapshotType.SectionID
|
||||
public typealias ItemID = SnapshotType.ItemID
|
||||
|
||||
public fileprivate(set) var snapshot: SnapshotType = .init() {
|
||||
|
||||
willSet {
|
||||
|
||||
self.willChange()
|
||||
}
|
||||
}
|
||||
|
||||
public var numberOfItems: Int {
|
||||
|
||||
return self.snapshot.numberOfItems
|
||||
}
|
||||
|
||||
public var numberOfSections: Int {
|
||||
|
||||
return self.snapshot.numberOfSections
|
||||
}
|
||||
|
||||
public var sectionIdentifiers: [SectionID] {
|
||||
|
||||
return self.snapshot.sectionIdentifiers
|
||||
}
|
||||
|
||||
public subscript(section sectionID: SectionID) -> [LiveObject<O>] {
|
||||
|
||||
let context = self.context
|
||||
@@ -85,6 +62,24 @@ public final class LiveList<O: DynamicObject>: Hashable {
|
||||
return self.context.liveObject(id: validID)
|
||||
}
|
||||
|
||||
public subscript(indexPath indexPath: IndexPath) -> LiveObject<O>? {
|
||||
|
||||
let snapshot = self.snapshot
|
||||
let sectionIdentifiers = snapshot.sectionIdentifiers
|
||||
guard sectionIdentifiers.indices.contains(indexPath.section) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let sectionID = sectionIdentifiers[indexPath.section]
|
||||
let itemIdentifiers = snapshot.itemIdentifiers(inSection: sectionID)
|
||||
guard itemIdentifiers.indices.contains(indexPath.item) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let itemID = itemIdentifiers[indexPath.item]
|
||||
return self.context.liveObject(id: itemID)
|
||||
}
|
||||
|
||||
public subscript<S: Sequence>(section sectionID: SectionID, itemIndices itemIndices: S) -> [LiveObject<O>] where S.Element == Int {
|
||||
|
||||
let context = self.context
|
||||
@@ -96,6 +91,34 @@ public final class LiveList<O: DynamicObject>: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public fileprivate(set) var snapshot: SnapshotType = .init() {
|
||||
|
||||
willSet {
|
||||
|
||||
self.willChange()
|
||||
}
|
||||
didSet {
|
||||
|
||||
self.notifyObservers(self.snapshot)
|
||||
self.didChange()
|
||||
}
|
||||
}
|
||||
|
||||
public var numberOfItems: Int {
|
||||
|
||||
return self.snapshot.numberOfItems
|
||||
}
|
||||
|
||||
public var numberOfSections: Int {
|
||||
|
||||
return self.snapshot.numberOfSections
|
||||
}
|
||||
|
||||
public var sectionIdentifiers: [SectionID] {
|
||||
|
||||
return self.snapshot.sectionIdentifiers
|
||||
}
|
||||
|
||||
public var items: [LiveObject<O>] {
|
||||
|
||||
let context = self.context
|
||||
@@ -141,6 +164,26 @@ public final class LiveList<O: DynamicObject>: Hashable {
|
||||
|
||||
return self.snapshot.indexOfSection(identifier)
|
||||
}
|
||||
|
||||
public func addObserver<T: AnyObject>(_ observer: T, _ callback: @escaping (LiveList<O>, ListSnapshot<O>) -> Void) {
|
||||
|
||||
self.observers.setObject(
|
||||
Internals.Closure(callback),
|
||||
forKey: observer
|
||||
)
|
||||
|
||||
callback(self, self.snapshot)
|
||||
}
|
||||
|
||||
public func removeObserver<T: AnyObject>(_ observer: T) {
|
||||
|
||||
self.observers.removeObject(forKey: observer)
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
self.observers.removeAllObjects()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Equatable
|
||||
@@ -222,12 +265,14 @@ public final class LiveList<O: DynamicObject>: Hashable {
|
||||
|
||||
private var fetchedResultsController: Internals.CoreStoreFetchedResultsController
|
||||
private var fetchedResultsControllerDelegate: Internals.FetchedDiffableDataSourceSnapshotDelegate
|
||||
private let sectionIndexTransformer: (_ sectionName: KeyPathString?) -> String?
|
||||
private var applyFetchClauses: (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
|
||||
private var observerForWillChangePersistentStore: Internals.NotificationObserver!
|
||||
private var observerForDidChangePersistentStore: Internals.NotificationObserver!
|
||||
|
||||
private let from: From<ObjectType>
|
||||
private let sectionBy: SectionBy<ObjectType>?
|
||||
private let observers: NSMapTable<AnyObject, Internals.Closure<(LiveList<O>, ListSnapshot<O>), Void>> = .weakToStrongObjects()
|
||||
|
||||
private lazy var context: NSManagedObjectContext = self.fetchedResultsController.managedObjectContext
|
||||
|
||||
@@ -279,20 +324,31 @@ public final class LiveList<O: DynamicObject>: Hashable {
|
||||
self.rawObjectWillChange = nil
|
||||
}
|
||||
|
||||
if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
|
||||
|
||||
// if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
|
||||
//
|
||||
// self.sectionIndexTransformer = sectionIndexTransformer
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// self.sectionIndexTransformer = { $0 }
|
||||
// }
|
||||
self.sectionIndexTransformer = sectionIndexTransformer
|
||||
}
|
||||
else {
|
||||
|
||||
self.sectionIndexTransformer = { $0 }
|
||||
}
|
||||
self.applyFetchClauses = applyFetchClauses
|
||||
self.fetchedResultsControllerDelegate.handler = self
|
||||
|
||||
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
|
||||
}
|
||||
|
||||
private func notifyObservers(_ snapshot: ListSnapshot<O>) {
|
||||
|
||||
guard let enumerator = self.observers.objectEnumerator() else {
|
||||
|
||||
return
|
||||
}
|
||||
for closure in enumerator {
|
||||
|
||||
(closure as! Internals.Closure<(LiveList<O>, ListSnapshot<O>), Void>).invoke(with: (self, snapshot))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -301,14 +357,28 @@ public final class LiveList<O: DynamicObject>: Hashable {
|
||||
extension LiveList: FetchedDiffableDataSourceSnapshotHandler {
|
||||
|
||||
// MARK: FetchedDiffableDataSourceSnapshotHandler
|
||||
|
||||
// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
// internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshot<String, NSManagedObjectID>) {
|
||||
//
|
||||
// self.snapshot = .init(
|
||||
// diffableSnapshot: snapshot,
|
||||
// context: controller.managedObjectContext
|
||||
// )
|
||||
// }
|
||||
|
||||
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: DiffableDataSourceSnapshotProtocol) {
|
||||
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot) {
|
||||
|
||||
self.snapshot = .init(
|
||||
diffableSnapshot: snapshot,
|
||||
context: controller.managedObjectContext
|
||||
)
|
||||
}
|
||||
|
||||
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String? {
|
||||
|
||||
return self.sectionIndexTransformer(sectionName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user