mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-12 04:10:36 +01:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4489301ac | ||
|
|
c025e5acc6 | ||
|
|
57745f36a8 | ||
|
|
eef1c99f11 | ||
|
|
9a19919392 | ||
|
|
3e2d62fe67 | ||
|
|
6f275eb63a | ||
|
|
b12dba4d15 | ||
|
|
4ee1b04523 | ||
|
|
b1decc9853 | ||
|
|
c2e4c033ef | ||
|
|
e12223df85 | ||
|
|
468922d5ed | ||
|
|
6b9a4b480b | ||
|
|
81b482e28b | ||
|
|
c112a84c0a | ||
|
|
88ab0b5e15 | ||
|
|
717cb75720 | ||
|
|
998938490c | ||
|
|
f3beca8769 | ||
|
|
4baeb6d922 | ||
|
|
98d860aff6 | ||
|
|
11a9e3991c | ||
|
|
f380d9dc25 | ||
|
|
d546ff154f | ||
|
|
f21597d332 | ||
|
|
56d9719984 | ||
|
|
6d75dcbc32 |
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "CoreStore"
|
||||
s.version = "7.0.0"
|
||||
s.version = "7.0.2"
|
||||
s.swift_version = "5.1"
|
||||
s.license = "MIT"
|
||||
s.homepage = "https://github.com/JohnEstropia/CoreStore"
|
||||
@@ -18,5 +18,5 @@ Pod::Spec.new do |s|
|
||||
s.public_header_files = "Sources/**/*.h"
|
||||
s.frameworks = "Foundation", "CoreData"
|
||||
s.requires_arc = true
|
||||
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS[config=Debug]' => '-D DEBUG', 'OTHER_LDFLAGS' => '-weak_framework Combine' }
|
||||
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS[config=Debug]' => '-D DEBUG', 'OTHER_LDFLAGS' => '-weak_framework Combine -weak_framework SwiftUI' }
|
||||
end
|
||||
|
||||
@@ -141,6 +141,13 @@
|
||||
B51260941E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */; };
|
||||
B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */; };
|
||||
B51260961E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */; };
|
||||
B514EF0E23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; };
|
||||
B514EF0F23A8DB180093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; };
|
||||
B514EF1023A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; };
|
||||
B514EF1123A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; };
|
||||
B514EF1223A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; };
|
||||
B514EF1323A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; };
|
||||
B514EF1423A8DB1E0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; };
|
||||
B51B5C2B22D43931009FA3BA /* String+KeyPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51B5C2A22D43931009FA3BA /* String+KeyPaths.swift */; };
|
||||
B51B5C2D22D43E38009FA3BA /* KeyPath+KeyPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51B5C2C22D43E38009FA3BA /* KeyPath+KeyPaths.swift */; };
|
||||
B51FE5AB1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */; };
|
||||
@@ -465,10 +472,10 @@
|
||||
B56321B31BD6521C006C9394 /* NSManagedObjectContext+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */; };
|
||||
B56321B41BD6521C006C9394 /* NSManagedObjectContext+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */; };
|
||||
B56321B61BD6521C006C9394 /* Internals.WeakObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2D1AFF849C0064E85B /* Internals.WeakObject.swift */; };
|
||||
B5635D142356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */; };
|
||||
B5635D152356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */; };
|
||||
B5635D162356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */; };
|
||||
B5635D172356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */; };
|
||||
B5635D142356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift */; };
|
||||
B5635D152356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift */; };
|
||||
B5635D162356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift */; };
|
||||
B5635D172356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift */; };
|
||||
B56507941D3930BC000596DA /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B56507931D3930BC000596DA /* CoreData.framework */; };
|
||||
B56507961D3930C1000596DA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B56507951D3930C1000596DA /* Foundation.framework */; };
|
||||
B56507981D3930CC000596DA /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B56507971D3930CC000596DA /* CoreData.framework */; };
|
||||
@@ -522,6 +529,9 @@
|
||||
B580857A1CDF808C004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; };
|
||||
B580857B1CDF808D004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; };
|
||||
B580857C1CDF808F004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; };
|
||||
B581B9322362BB8C002BDB2B /* ObjectPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */; };
|
||||
B581B9332362BB8C002BDB2B /* ObjectPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */; };
|
||||
B581B9342362BB8C002BDB2B /* ObjectPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */; };
|
||||
B5831B701F34AC3400A9F647 /* AttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */; };
|
||||
B5831B711F34AC3400A9F647 /* AttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */; };
|
||||
B5831B721F34AC3400A9F647 /* AttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */; };
|
||||
@@ -580,10 +590,10 @@
|
||||
B5A992201EA898720091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; };
|
||||
B5A992211EA898720091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; };
|
||||
B5A992221EA898720091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; };
|
||||
B5AA37F1235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift */; };
|
||||
B5AA37F2235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift */; };
|
||||
B5AA37F3235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift */; };
|
||||
B5AA37F4235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift */; };
|
||||
B5AA37F1235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift */; };
|
||||
B5AA37F2235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift */; };
|
||||
B5AA37F3235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift */; };
|
||||
B5AA37F4235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift */; };
|
||||
B5AA37FD235C3D1A00FFD4B9 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5AA37FC235C3D1A00FFD4B9 /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
B5AEFAB51C9962AE00AD137F /* CoreStoreBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */; };
|
||||
B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */; };
|
||||
@@ -597,10 +607,10 @@
|
||||
B5BF7FB3234C97910070E741 /* DiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */; };
|
||||
B5BF7FB4234C97910070E741 /* DiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */; };
|
||||
B5BF7FB5234C97910070E741 /* DiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */; };
|
||||
B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */; };
|
||||
B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */; };
|
||||
B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */; };
|
||||
B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */; };
|
||||
B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift */; };
|
||||
B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift */; };
|
||||
B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift */; };
|
||||
B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.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 */; };
|
||||
@@ -665,6 +675,7 @@
|
||||
B5D33A041E96012400C880DE /* Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D33A001E96012400C880DE /* Relationship.swift */; };
|
||||
B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; };
|
||||
B5D39A0219FD00C9000E91BB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0119FD00C9000E91BB /* Foundation.framework */; };
|
||||
B5D4A6B723A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; };
|
||||
B5D7A5B61CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; };
|
||||
B5D7A5B81CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; };
|
||||
B5D7A5B91CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; };
|
||||
@@ -903,6 +914,7 @@
|
||||
B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStoreObject+Convenience.swift"; sourceTree = "<group>"; };
|
||||
B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSEntityDescription+DynamicModel.swift"; sourceTree = "<group>"; };
|
||||
B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Internals.EntityIdentifier.swift; sourceTree = "<group>"; };
|
||||
B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.Target.swift; sourceTree = "<group>"; };
|
||||
B51B5C2A22D43931009FA3BA /* String+KeyPaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+KeyPaths.swift"; sourceTree = "<group>"; };
|
||||
B51B5C2C22D43E38009FA3BA /* KeyPath+KeyPaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyPath+KeyPaths.swift"; sourceTree = "<group>"; };
|
||||
B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+CustomDebugStringConvertible.swift"; sourceTree = "<group>"; };
|
||||
@@ -973,7 +985,7 @@
|
||||
B563216F1BD65082006C9394 /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B56321791BD650DE006C9394 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS2.0.sdk/System/Library/Frameworks/CoreData.framework; sourceTree = DEVELOPER_DIR; };
|
||||
B563217B1BD650E3006C9394 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS2.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||
B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffableDataSource.CollectionView-UIKit.swift"; sourceTree = "<group>"; };
|
||||
B5635D132356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffableDataSource.CollectionViewAdapter-UIKit.swift"; sourceTree = "<group>"; };
|
||||
B56507931D3930BC000596DA /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.0.sdk/System/Library/Frameworks/CoreData.framework; sourceTree = DEVELOPER_DIR; };
|
||||
B56507951D3930C1000596DA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||
B56507971D3930CC000596DA /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/System/Library/Frameworks/CoreData.framework; sourceTree = DEVELOPER_DIR; };
|
||||
@@ -994,6 +1006,7 @@
|
||||
B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTestDataTestCase.swift; sourceTree = "<group>"; };
|
||||
B57D27C11D0BC20100539C58 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = "<group>"; };
|
||||
B58085741CDF7F00004C2EEB /* SetupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupTests.swift; sourceTree = "<group>"; };
|
||||
B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectPublisherTests.swift; sourceTree = "<group>"; };
|
||||
B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeProtocol.swift; sourceTree = "<group>"; };
|
||||
B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelationshipProtocol.swift; sourceTree = "<group>"; };
|
||||
B5831B791F34ACBA00A9F647 /* Transformable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transformable.swift; sourceTree = "<group>"; };
|
||||
@@ -1010,14 +1023,14 @@
|
||||
B5A80DF52212C1BC006096AA /* Playground_iOS.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Playground_iOS.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionLock.swift; sourceTree = "<group>"; };
|
||||
B5A9921E1EA898710091A2E3 /* UserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInfo.swift; sourceTree = "<group>"; };
|
||||
B5AA37F0235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "diffableDataSource.CollectionView-AppKit.swift"; sourceTree = "<group>"; };
|
||||
B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffableDataSource.CollectionViewAdapter-AppKit.swift"; sourceTree = "<group>"; };
|
||||
B5AA37FA235C3D1300FFD4B9 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS6.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
B5AA37FC235C3D1A00FFD4B9 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS6.0.sdk/System/Library/Frameworks/SwiftUI.framework; sourceTree = DEVELOPER_DIR; };
|
||||
B5AD60CD1C90141E00F2B2E8 /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = SOURCE_ROOT; };
|
||||
B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreBridge.swift; sourceTree = "<group>"; };
|
||||
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-UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffableDataSource.TableView-UIKit.swift"; sourceTree = "<group>"; };
|
||||
B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffableDataSource.TableViewAdapter-UIKit.swift"; sourceTree = "<group>"; };
|
||||
B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.swift; sourceTree = "<group>"; };
|
||||
B5BF7FC0234D7B2E0070E741 /* ObjectPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectPublisher.swift; sourceTree = "<group>"; };
|
||||
B5BF7FC5234D7E460070E741 /* ObjectSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectSnapshot.swift; sourceTree = "<group>"; };
|
||||
@@ -1038,6 +1051,7 @@
|
||||
B5D33A001E96012400C880DE /* Relationship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Relationship.swift; sourceTree = "<group>"; };
|
||||
B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
|
||||
B5D39A0119FD00C9000E91BB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.BaseAdapter.swift; sourceTree = "<group>"; };
|
||||
B5D7A5B51CA3BF8F005C752B /* CSInto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSInto.swift; sourceTree = "<group>"; };
|
||||
B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataStack+DataSources.swift"; sourceTree = "<group>"; };
|
||||
B5D8CA7A2346EC550055D7D1 /* ListPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListPublisherTests.swift; sourceTree = "<group>"; };
|
||||
@@ -1262,6 +1276,7 @@
|
||||
B5D8CA7A2346EC550055D7D1 /* ListPublisherTests.swift */,
|
||||
B5DC47C51C93D22900FA3BF3 /* MigrationChainTests.swift */,
|
||||
B5220E071D0C5F8D009BC71E /* ObjectObserverTests.swift */,
|
||||
B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */,
|
||||
B52557771D02826E00E51965 /* OrderByTests.swift */,
|
||||
B57D27C11D0BC20100539C58 /* QueryTests.swift */,
|
||||
B52557831D02A07400E51965 /* SectionByTests.swift */,
|
||||
@@ -1602,9 +1617,11 @@
|
||||
B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */,
|
||||
B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */,
|
||||
B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */,
|
||||
B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */,
|
||||
B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */,
|
||||
B5AA37F0235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift */,
|
||||
B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */,
|
||||
B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */,
|
||||
B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift */,
|
||||
B5635D132356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift */,
|
||||
B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift */,
|
||||
);
|
||||
name = DataSources;
|
||||
sourceTree = "<group>";
|
||||
@@ -2084,6 +2101,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */,
|
||||
B5DE5230230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */,
|
||||
B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */,
|
||||
B5ECDBF91CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */,
|
||||
@@ -2116,12 +2134,11 @@
|
||||
B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */,
|
||||
B53FBA0B1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
|
||||
B5E84F141AFF847B0064E85B /* DataStack+Querying.swift in Sources */,
|
||||
B5AA37F1235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */,
|
||||
B5AA37F1235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */,
|
||||
B5D7A5B61CA3BF8F005C752B /* CSInto.swift in Sources */,
|
||||
B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */,
|
||||
B5DE522B230BD7CC00A22534 /* Internals.swift in Sources */,
|
||||
B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */,
|
||||
B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */,
|
||||
B5ECDC1D1CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */,
|
||||
B5C976E31C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */,
|
||||
B53FBA121CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
|
||||
@@ -2146,7 +2163,7 @@
|
||||
B56923F01EB827F6007C4DC9 /* XcodeSchemaMappingProvider.swift in Sources */,
|
||||
B5E84F121AFF847B0064E85B /* OrderBy.swift in Sources */,
|
||||
B546F9581C99B17400D5AC55 /* CSCoreStore+Setup.swift in Sources */,
|
||||
B5635D142356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */,
|
||||
B5635D142356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */,
|
||||
B5E84F361AFF85470064E85B /* NSManagedObjectContext+Setup.swift in Sources */,
|
||||
B5FAD6AE1B518DCB00714891 /* CoreStore+Migration.swift in Sources */,
|
||||
B50E175223517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
|
||||
@@ -2219,6 +2236,7 @@
|
||||
B5BF7FBC234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
|
||||
B5215CA41FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
|
||||
B5D33A011E96012400C880DE /* Relationship.swift in Sources */,
|
||||
B514EF0E23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift in Sources */,
|
||||
B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */,
|
||||
B56923C91EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */,
|
||||
B56923E41EB827F5007C4DC9 /* CustomSchemaMappingProvider.swift in Sources */,
|
||||
@@ -2235,6 +2253,7 @@
|
||||
B512607F1E97A18000402229 /* CoreStoreObject+Convenience.swift in Sources */,
|
||||
B5E84EF81AFF846E0064E85B /* CoreStore+Transaction.swift in Sources */,
|
||||
B5E84F301AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift in Sources */,
|
||||
B5D4A6B723A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift in Sources */,
|
||||
B5831B7A1F34ACBA00A9F647 /* Transformable.swift in Sources */,
|
||||
B5BF7FB2234C97910070E741 /* DiffableDataSource.swift in Sources */,
|
||||
B546F9691C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
|
||||
@@ -2285,6 +2304,7 @@
|
||||
B5220E0C1D0D0D19009BC71E /* ImportTests.swift in Sources */,
|
||||
B5D339B41E925C2B00C880DE /* DynamicModelTests.swift in Sources */,
|
||||
B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */,
|
||||
B581B9322362BB8C002BDB2B /* ObjectPublisherTests.swift in Sources */,
|
||||
B52557881D02DE8100E51965 /* FetchTests.swift in Sources */,
|
||||
B5489F501CF603D5008B4978 /* FromTests.swift in Sources */,
|
||||
B52557781D02826E00E51965 /* OrderByTests.swift in Sources */,
|
||||
@@ -2310,7 +2330,7 @@
|
||||
files = (
|
||||
82BA18B61C4BBD3F00A0916E /* DataStack+Querying.swift in Sources */,
|
||||
B5ECDBFB1CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */,
|
||||
B5635D152356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */,
|
||||
B5635D152356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */,
|
||||
B5CA2B091F7E5ACA004B1936 /* WhereClauseType.swift in Sources */,
|
||||
B5C976E81C6E3A5D00B1AF90 /* Internals.CoreStoreFetchedResultsController.swift in Sources */,
|
||||
B56923F61EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
|
||||
@@ -2381,6 +2401,7 @@
|
||||
B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
|
||||
B5DBE2D31C991B3E00B5CEFA /* CSDataStack.swift in Sources */,
|
||||
82BA18B41C4BBD3900A0916E /* BaseDataTransaction+Importing.swift in Sources */,
|
||||
B514EF1223A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */,
|
||||
B53FBA1A1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */,
|
||||
82BA18CA1C4BBD5900A0916E /* MigrationResult.swift in Sources */,
|
||||
B5519A5A1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */,
|
||||
@@ -2429,6 +2450,7 @@
|
||||
B5215CAA1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
|
||||
B50E175823517DE4004F033C /* Differentiable.swift in Sources */,
|
||||
82BA18BA1C4BBD4A00A0916E /* Select.swift in Sources */,
|
||||
B514EF0F23A8DB180093DBA4 /* DiffableDataSource.Target.swift in Sources */,
|
||||
B52F744B1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
|
||||
B5E2222C1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */,
|
||||
@@ -2468,7 +2490,7 @@
|
||||
B52F743E1E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
|
||||
18166884232B9ED00097C275 /* KeyPath+KeyPaths.swift in Sources */,
|
||||
B5E8A72121C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
|
||||
B5AA37F2235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */,
|
||||
B5AA37F2235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */,
|
||||
B50E175D2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
|
||||
B5474D162227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
|
||||
B501322B2346A9AE00FC238B /* ListPublisher.swift in Sources */,
|
||||
@@ -2486,7 +2508,7 @@
|
||||
B5D339E81E9493A500C880DE /* Entity.swift in Sources */,
|
||||
B5BF7FC7234D7E460070E741 /* ObjectSnapshot.swift in Sources */,
|
||||
82BA18CC1C4BBD6400A0916E /* Progress+Convenience.swift in Sources */,
|
||||
B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */,
|
||||
B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */,
|
||||
82BA18C01C4BBD5300A0916E /* DataStack+Observing.swift in Sources */,
|
||||
82BA18A61C4BBD2900A0916E /* DefaultLogger.swift in Sources */,
|
||||
);
|
||||
@@ -2508,6 +2530,7 @@
|
||||
B5220E0D1D0D0D19009BC71E /* ImportTests.swift in Sources */,
|
||||
B5D339B51E925C2B00C880DE /* DynamicModelTests.swift in Sources */,
|
||||
B525576D1CFAF18F00E51965 /* IntoTests.swift in Sources */,
|
||||
B581B9332362BB8C002BDB2B /* ObjectPublisherTests.swift in Sources */,
|
||||
B580857B1CDF808D004C2EEB /* SetupTests.swift in Sources */,
|
||||
B52557891D02DE8100E51965 /* FetchTests.swift in Sources */,
|
||||
B5489F511CF603D5008B4978 /* FromTests.swift in Sources */,
|
||||
@@ -2531,9 +2554,12 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5BF7FB5234C97910070E741 /* DiffableDataSource.swift in Sources */,
|
||||
B5220E1E1D13080D009BC71E /* CSListMonitor.swift in Sources */,
|
||||
B5AA37F4235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */,
|
||||
B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */,
|
||||
B5635D172356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */,
|
||||
B5DBE2D01C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
|
||||
B5635D172356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */,
|
||||
B5CA2B0B1F7E5ACA004B1936 /* WhereClauseType.swift in Sources */,
|
||||
B56923F81EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
|
||||
B52DD1BE1BE1F94300949AFE /* Progress+Convenience.swift in Sources */,
|
||||
@@ -2574,7 +2600,6 @@
|
||||
B50564D62350CC3100482308 /* PropertyProtocol.swift in Sources */,
|
||||
B5831B781F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */,
|
||||
B52DD1981BE1F92500949AFE /* CoreStore+Setup.swift in Sources */,
|
||||
B5BF7FB5234C97910070E741 /* DiffableDataSource.swift in Sources */,
|
||||
B5D339F41E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
|
||||
B5220E241D13085E009BC71E /* NSFetchedResultsController+Convenience.swift in Sources */,
|
||||
B559CD471CAA8B6300E4D58B /* CSSetupResult.swift in Sources */,
|
||||
@@ -2602,6 +2627,7 @@
|
||||
B53FBA1C1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */,
|
||||
B5F8496F234898240029D57B /* ListSnapshot.swift in Sources */,
|
||||
B5831F452212700500D8604C /* Where.Expression.swift in Sources */,
|
||||
B514EF1423A8DB1E0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */,
|
||||
B51260961E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
|
||||
B5ECDBE31CA6BB2B00C7F112 /* CSBaseDataTransaction+Querying.swift in Sources */,
|
||||
B5ECDC031CA80CBA00C7F112 /* CSWhere.swift in Sources */,
|
||||
@@ -2650,6 +2676,7 @@
|
||||
B5220E1A1D130791009BC71E /* Internals.CoreStoreFetchedResultsController.swift in Sources */,
|
||||
B5215CAC1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
|
||||
B53FBA0F1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
|
||||
B514EF1123A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */,
|
||||
B50E175A23517DE4004F033C /* Differentiable.swift in Sources */,
|
||||
B52F744D1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B52DD19A1BE1F92800949AFE /* CoreStore+Logging.swift in Sources */,
|
||||
@@ -2691,7 +2718,6 @@
|
||||
B52F74401E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
|
||||
18166886232B9ED20097C275 /* KeyPath+KeyPaths.swift in Sources */,
|
||||
B5E8A72321C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
|
||||
B5AA37F4235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */,
|
||||
B50E175F2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
|
||||
B5474D182227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
|
||||
B501322E2346A9B100FC238B /* ListPublisher.swift in Sources */,
|
||||
@@ -2709,7 +2735,6 @@
|
||||
B5D339EA1E9493A500C880DE /* Entity.swift in Sources */,
|
||||
B5BF7FC9234D7E460070E741 /* ObjectSnapshot.swift in Sources */,
|
||||
B52DD1AA1BE1F93500949AFE /* TypeErasedClauses.swift in Sources */,
|
||||
B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */,
|
||||
B53FBA021CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
|
||||
B51FE5AF1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */,
|
||||
);
|
||||
@@ -2731,6 +2756,7 @@
|
||||
B525576E1CFAF18F00E51965 /* IntoTests.swift in Sources */,
|
||||
B5D339B61E925C2B00C880DE /* DynamicModelTests.swift in Sources */,
|
||||
B580857C1CDF808F004C2EEB /* SetupTests.swift in Sources */,
|
||||
B581B9342362BB8C002BDB2B /* ObjectPublisherTests.swift in Sources */,
|
||||
B525578A1D02DE8100E51965 /* FetchTests.swift in Sources */,
|
||||
B5220E281D1308E5009BC71E /* SectionByTests.swift in Sources */,
|
||||
B5489F521CF603D5008B4978 /* FromTests.swift in Sources */,
|
||||
@@ -2756,7 +2782,7 @@
|
||||
files = (
|
||||
B56321A91BD65219006C9394 /* Progress+Convenience.swift in Sources */,
|
||||
B5ECDBFC1CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */,
|
||||
B5635D162356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */,
|
||||
B5635D162356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */,
|
||||
B5CA2B0A1F7E5ACA004B1936 /* WhereClauseType.swift in Sources */,
|
||||
B5C976E91C6E3A5E00B1AF90 /* Internals.CoreStoreFetchedResultsController.swift in Sources */,
|
||||
B56923F71EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
|
||||
@@ -2827,6 +2853,7 @@
|
||||
B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
|
||||
B53FBA011CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
|
||||
B5DBE2D41C991B3E00B5CEFA /* CSDataStack.swift in Sources */,
|
||||
B514EF1323A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */,
|
||||
B50392FA1C47963F009900CA /* NSManagedObject+Transaction.swift in Sources */,
|
||||
B53FBA1B1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */,
|
||||
B5519A5B1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */,
|
||||
@@ -2875,6 +2902,7 @@
|
||||
B56321A21BD65216006C9394 /* ListObserver.swift in Sources */,
|
||||
B50E175923517DE4004F033C /* Differentiable.swift in Sources */,
|
||||
B5215CAB1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
|
||||
B514EF1023A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */,
|
||||
B563218A1BD65216006C9394 /* SynchronousDataTransaction.swift in Sources */,
|
||||
B52F744C1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B5AEFAB71C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
|
||||
@@ -2914,7 +2942,7 @@
|
||||
B52F743F1E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
|
||||
18166885232B9ED10097C275 /* KeyPath+KeyPaths.swift in Sources */,
|
||||
B5E8A72221C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
|
||||
B5AA37F3235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */,
|
||||
B5AA37F3235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */,
|
||||
B50E175E2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
|
||||
B5474D172227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
|
||||
B501322D2346A9B000FC238B /* ListPublisher.swift in Sources */,
|
||||
@@ -2932,7 +2960,7 @@
|
||||
B5D339E91E9493A500C880DE /* Entity.swift in Sources */,
|
||||
B5BF7FC8234D7E460070E741 /* ObjectSnapshot.swift in Sources */,
|
||||
B56321A41BD65216006C9394 /* CoreStore+Migration.swift in Sources */,
|
||||
B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */,
|
||||
B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */,
|
||||
B56321A01BD65216006C9394 /* ObjectObserver.swift in Sources */,
|
||||
B56321951BD65216006C9394 /* TypeErasedClauses.swift in Sources */,
|
||||
);
|
||||
@@ -3105,7 +3133,7 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 7.0.0;
|
||||
MARKETING_VERSION = 7.0.2;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
@@ -3128,7 +3156,7 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 7.0.0;
|
||||
MARKETING_VERSION = 7.0.2;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
|
||||
@@ -51,7 +51,7 @@ final class CollectionViewDemoViewController: UICollectionViewController {
|
||||
]
|
||||
self.filterBarButton = filterBarButton
|
||||
|
||||
self.dataSource = DiffableDataSource.CollectionView<Palette>(
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Palette>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: ColorsDemo.stack,
|
||||
cellProvider: { (collectionView, indexPath, palette) in
|
||||
@@ -118,7 +118,7 @@ final class CollectionViewDemoViewController: UICollectionViewController {
|
||||
// MARK: Private
|
||||
|
||||
private var filterBarButton: UIBarButtonItem?
|
||||
private var dataSource: DiffableDataSource.CollectionView<Palette>?
|
||||
private var dataSource: DiffableDataSource.CollectionViewAdapter<Palette>?
|
||||
|
||||
deinit {
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ struct ColorsDemo {
|
||||
|
||||
static let palettes: ListPublisher<Palette> = {
|
||||
|
||||
return ColorsDemo.stack.listPublisher(
|
||||
return ColorsDemo.stack.publishList(
|
||||
From<Palette>()
|
||||
.sectionBy(\.colorName)
|
||||
.orderBy(.ascending(\.hue))
|
||||
|
||||
@@ -16,7 +16,7 @@ final class ListObserverDemoViewController: UITableViewController {
|
||||
|
||||
// MARK: - EditableDataSource
|
||||
|
||||
final class EditableDataSource: DiffableDataSource.TableView<Palette> {
|
||||
final class EditableDataSource: DiffableDataSource.TableViewAdapter<Palette> {
|
||||
|
||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
|
||||
@@ -130,7 +130,7 @@ final class ListObserverDemoViewController: UITableViewController {
|
||||
// MARK: Private
|
||||
|
||||
private var filterBarButton: UIBarButtonItem?
|
||||
private var dataSource: DiffableDataSource.TableView<Palette>?
|
||||
private var dataSource: DiffableDataSource.TableViewAdapter<Palette>?
|
||||
|
||||
deinit {
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ final class SwiftUIContainerViewController: UIViewController {
|
||||
|
||||
let hostingController = UIHostingController(
|
||||
rootView: SwiftUIView(
|
||||
palettes: ColorsDemo.stack.listPublisher(
|
||||
palettes: ColorsDemo.stack.publishList(
|
||||
From<Palette>()
|
||||
.sectionBy(\.colorName)
|
||||
.orderBy(.ascending(\.hue))
|
||||
|
||||
@@ -171,7 +171,7 @@ struct SwiftUIView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
SwiftUIView(
|
||||
palettes: ColorsDemo.stack.listPublisher(
|
||||
palettes: ColorsDemo.stack.publishList(
|
||||
From<Palette>()
|
||||
.sectionBy(\.colorName)
|
||||
.orderBy(.ascending(\.hue))
|
||||
|
||||
@@ -41,99 +41,26 @@ class ListPublisherTests: BaseTestDataTestCase {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
// let observer = TestListObserver()
|
||||
let listPublisher = stack.listPublisher(
|
||||
let observer = NSObject()
|
||||
let listPublisher = stack.publishList(
|
||||
From<TestEntity1>(),
|
||||
SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
)
|
||||
// monitor.addObserver(observer)
|
||||
//
|
||||
// XCTAssertFalse(monitor.hasSections())
|
||||
// XCTAssertFalse(monitor.hasObjects())
|
||||
// XCTAssertTrue(monitor.objectsInAllSections().isEmpty)
|
||||
//
|
||||
// var events = 0
|
||||
//
|
||||
// let willChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 0)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 0
|
||||
// }
|
||||
// )
|
||||
// let didInsertSectionExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 1)
|
||||
// XCTAssertEqual(
|
||||
// ((note.userInfo as NSDictionary?) ?? [:]),
|
||||
// [
|
||||
// "sectionInfo": monitor.sectionInfo(at: 0),
|
||||
// "sectionIndex": 0
|
||||
// ] as NSDictionary
|
||||
// )
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 1
|
||||
// }
|
||||
// )
|
||||
// let didInsertObjectExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 2)
|
||||
//
|
||||
// let userInfo = note.userInfo
|
||||
// XCTAssertNotNil(userInfo)
|
||||
// XCTAssertEqual(
|
||||
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
// ["indexPath", "object"]
|
||||
// )
|
||||
//
|
||||
// let indexPath = userInfo?["indexPath"] as? NSIndexPath
|
||||
// XCTAssertEqual(indexPath?.index(atPosition: 0), 0)
|
||||
// XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
|
||||
//
|
||||
// let object = userInfo?["object"] as? TestEntity1
|
||||
// XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
|
||||
// XCTAssertEqual(object?.testNumber, NSNumber(value: 1))
|
||||
// XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1"))
|
||||
// XCTAssertEqual(object?.testString, "nil:TestEntity1:1")
|
||||
// XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
||||
// XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!)
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 2
|
||||
// }
|
||||
// )
|
||||
// let didChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 3
|
||||
// }
|
||||
// )
|
||||
XCTAssertFalse(listPublisher.snapshot.hasSections())
|
||||
XCTAssertFalse(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.itemIDs.isEmpty)
|
||||
|
||||
let didChangeExpectation = self.expectation(description: "didChange")
|
||||
listPublisher.addObserver(observer) { listPublisher in
|
||||
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 1)
|
||||
|
||||
didChangeExpectation.fulfill()
|
||||
}
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
@@ -161,522 +88,216 @@ class ListPublisherTests: BaseTestDataTestCase {
|
||||
self.waitAndCheckExpectations()
|
||||
|
||||
withExtendedLifetime(listPublisher, {})
|
||||
withExtendedLifetime(observer, {})
|
||||
}
|
||||
}
|
||||
|
||||
// @objc
|
||||
// dynamic func test_ThatListObservers_CanReceiveUpdateNotifications() {
|
||||
//
|
||||
// self.prepareStack { (stack) in
|
||||
//
|
||||
// self.prepareTestDataForStack(stack)
|
||||
//
|
||||
// let observer = TestListObserver()
|
||||
// let monitor = stack.monitorSectionedList(
|
||||
// From<TestEntity1>(),
|
||||
// SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
// OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
// )
|
||||
// monitor.addObserver(observer)
|
||||
//
|
||||
// XCTAssertTrue(monitor.hasSections())
|
||||
// XCTAssertEqual(monitor.numberOfSections(), 2)
|
||||
// XCTAssertTrue(monitor.hasObjects())
|
||||
// XCTAssertTrue(monitor.hasObjects(in: 0))
|
||||
// XCTAssertEqual(monitor.numberOfObjects(in: 0), 2)
|
||||
// XCTAssertEqual(monitor.numberOfObjects(in: 1), 3)
|
||||
//
|
||||
// var events = 0
|
||||
//
|
||||
// let willChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 0)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 0
|
||||
// }
|
||||
// )
|
||||
//
|
||||
// let didUpdateObjectExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssert(events == 1 || events == 2)
|
||||
//
|
||||
// let userInfo = note.userInfo
|
||||
// XCTAssertNotNil(userInfo)
|
||||
// XCTAssertEqual(
|
||||
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
// ["indexPath", "object"]
|
||||
// )
|
||||
//
|
||||
// let indexPath = userInfo?["indexPath"] as? NSIndexPath
|
||||
// let object = userInfo?["object"] as? TestEntity1
|
||||
//
|
||||
// switch object?.testEntityID {
|
||||
//
|
||||
// case NSNumber(value: 101)?:
|
||||
// XCTAssertEqual(indexPath?.index(atPosition: 0), 1)
|
||||
// XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
|
||||
//
|
||||
// XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
|
||||
// XCTAssertEqual(object?.testNumber, NSNumber(value: 11))
|
||||
// XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "11"))
|
||||
// XCTAssertEqual(object?.testString, "nil:TestEntity1:11")
|
||||
// XCTAssertEqual(object?.testData, ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
||||
// XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!)
|
||||
//
|
||||
// case NSNumber(value: 102)?:
|
||||
// XCTAssertEqual(indexPath?.index(atPosition: 0), 0)
|
||||
// XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
|
||||
//
|
||||
// XCTAssertEqual(object?.testBoolean, NSNumber(value: false))
|
||||
// XCTAssertEqual(object?.testNumber, NSNumber(value: 22))
|
||||
// XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "22"))
|
||||
// XCTAssertEqual(object?.testString, "nil:TestEntity1:22")
|
||||
// XCTAssertEqual(object?.testData, ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
||||
// XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!)
|
||||
//
|
||||
// default:
|
||||
// XCTFail()
|
||||
// }
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 1 || events == 2
|
||||
// }
|
||||
// )
|
||||
// let didChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 3)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 3
|
||||
// }
|
||||
// )
|
||||
// let saveExpectation = self.expectation(description: "save")
|
||||
// stack.perform(
|
||||
// asynchronous: { (transaction) -> Bool in
|
||||
//
|
||||
// if let object = try transaction.fetchOne(
|
||||
// From<TestEntity1>(),
|
||||
// Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) {
|
||||
//
|
||||
// object.testNumber = NSNumber(value: 11)
|
||||
// object.testDecimal = NSDecimalNumber(string: "11")
|
||||
// object.testString = "nil:TestEntity1:11"
|
||||
// object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
// object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// XCTFail()
|
||||
// }
|
||||
// if let object = try transaction.fetchOne(
|
||||
// From<TestEntity1>(),
|
||||
// Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
|
||||
//
|
||||
// object.testNumber = NSNumber(value: 22)
|
||||
// object.testDecimal = NSDecimalNumber(string: "22")
|
||||
// object.testString = "nil:TestEntity1:22"
|
||||
// object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
// object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// XCTFail()
|
||||
// }
|
||||
// return transaction.hasChanges
|
||||
// },
|
||||
// success: { (hasChanges) in
|
||||
//
|
||||
// XCTAssertTrue(hasChanges)
|
||||
// saveExpectation.fulfill()
|
||||
// },
|
||||
// failure: { _ in
|
||||
//
|
||||
// XCTFail()
|
||||
// }
|
||||
// )
|
||||
// self.waitAndCheckExpectations()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @objc
|
||||
// dynamic func test_ThatListObservers_CanReceiveMoveNotifications() {
|
||||
//
|
||||
// self.prepareStack { (stack) in
|
||||
//
|
||||
// self.prepareTestDataForStack(stack)
|
||||
//
|
||||
// let observer = TestListObserver()
|
||||
// let monitor = stack.monitorSectionedList(
|
||||
// From<TestEntity1>(),
|
||||
// SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
// OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
// )
|
||||
// monitor.addObserver(observer)
|
||||
//
|
||||
// var events = 0
|
||||
//
|
||||
// let willChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 0)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 0
|
||||
// }
|
||||
// )
|
||||
// let didMoveObjectExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 1)
|
||||
//
|
||||
// let userInfo = note.userInfo
|
||||
// XCTAssertNotNil(userInfo)
|
||||
// XCTAssertEqual(
|
||||
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
// ["fromIndexPath", "toIndexPath", "object"]
|
||||
// )
|
||||
//
|
||||
// let fromIndexPath = userInfo?["fromIndexPath"] as? NSIndexPath
|
||||
// XCTAssertEqual(fromIndexPath?.index(atPosition: 0), 0)
|
||||
// XCTAssertEqual(fromIndexPath?.index(atPosition: 1), 0)
|
||||
//
|
||||
// let toIndexPath = userInfo?["toIndexPath"] as? NSIndexPath
|
||||
// XCTAssertEqual(toIndexPath?.index(atPosition: 0), 1)
|
||||
// XCTAssertEqual(toIndexPath?.index(atPosition: 1), 1)
|
||||
//
|
||||
// let object = userInfo?["object"] as? TestEntity1
|
||||
// XCTAssertEqual(object?.testEntityID, NSNumber(value: 102))
|
||||
// XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
|
||||
//
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 1
|
||||
// }
|
||||
// )
|
||||
// let didChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 2)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 2
|
||||
// }
|
||||
// )
|
||||
// let saveExpectation = self.expectation(description: "save")
|
||||
// stack.perform(
|
||||
// asynchronous: { (transaction) -> Bool in
|
||||
//
|
||||
// if let object = try transaction.fetchOne(
|
||||
// From<TestEntity1>(),
|
||||
// Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
|
||||
//
|
||||
// object.testBoolean = NSNumber(value: true)
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// XCTFail()
|
||||
// }
|
||||
// return transaction.hasChanges
|
||||
// },
|
||||
// success: { (hasChanges) in
|
||||
//
|
||||
// XCTAssertTrue(hasChanges)
|
||||
// saveExpectation.fulfill()
|
||||
// },
|
||||
// failure: { _ in
|
||||
//
|
||||
// XCTFail()
|
||||
// }
|
||||
// )
|
||||
// self.waitAndCheckExpectations()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @objc
|
||||
// dynamic func test_ThatListObservers_CanReceiveDeleteNotifications() {
|
||||
//
|
||||
// self.prepareStack { (stack) in
|
||||
//
|
||||
// self.prepareTestDataForStack(stack)
|
||||
//
|
||||
// let observer = TestListObserver()
|
||||
// let monitor = stack.monitorSectionedList(
|
||||
// From<TestEntity1>(),
|
||||
// SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
// OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
// )
|
||||
// monitor.addObserver(observer)
|
||||
//
|
||||
// var events = 0
|
||||
//
|
||||
// let willChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 0)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 0
|
||||
// }
|
||||
// )
|
||||
// let didUpdateObjectExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssert(events == 1 || events == 2)
|
||||
//
|
||||
// let userInfo = note.userInfo
|
||||
// XCTAssertNotNil(userInfo)
|
||||
// XCTAssertEqual(
|
||||
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
// ["indexPath", "object"]
|
||||
// )
|
||||
//
|
||||
// let indexPath = userInfo?["indexPath"] as? NSIndexPath
|
||||
//
|
||||
// XCTAssertEqual(indexPath?.section, 0)
|
||||
// XCTAssert(indexPath?.index(atPosition: 1) == 0 || indexPath?.index(atPosition: 1) == 1)
|
||||
//
|
||||
// let object = userInfo?["object"] as? TestEntity1
|
||||
// XCTAssertEqual(object?.isDeleted, true)
|
||||
//
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 1 || events == 2
|
||||
// }
|
||||
// )
|
||||
// let didDeleteSectionExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 3)
|
||||
//
|
||||
// let userInfo = note.userInfo
|
||||
// XCTAssertNotNil(userInfo)
|
||||
// XCTAssertEqual(
|
||||
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
// ["sectionInfo", "sectionIndex"]
|
||||
// )
|
||||
//
|
||||
// let sectionInfo = userInfo?["sectionInfo"] as? NSFetchedResultsSectionInfo
|
||||
// XCTAssertNotNil(sectionInfo)
|
||||
// XCTAssertEqual(sectionInfo?.name, "0")
|
||||
//
|
||||
// let sectionIndex = userInfo?["sectionIndex"]
|
||||
// XCTAssertEqual(sectionIndex as? NSNumber, NSNumber(value: 0))
|
||||
//
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 3
|
||||
// }
|
||||
// )
|
||||
// let didChangeExpectation = self.expectation(
|
||||
// forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||
// object: observer,
|
||||
// handler: { (note) -> Bool in
|
||||
//
|
||||
// XCTAssertEqual(events, 4)
|
||||
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
// defer {
|
||||
//
|
||||
// events += 1
|
||||
// }
|
||||
// return events == 4
|
||||
// }
|
||||
// )
|
||||
// let saveExpectation = self.expectation(description: "save")
|
||||
// stack.perform(
|
||||
// asynchronous: { (transaction) -> Bool in
|
||||
//
|
||||
// let count = try transaction.deleteAll(
|
||||
// From<TestEntity1>(),
|
||||
// Where<TestEntity1>(#keyPath(TestEntity1.testBoolean), isEqualTo: false)
|
||||
// )
|
||||
// XCTAssertEqual(count, 2)
|
||||
// return transaction.hasChanges
|
||||
// },
|
||||
// success: { (hasChanges) in
|
||||
//
|
||||
// XCTAssertTrue(hasChanges)
|
||||
// saveExpectation.fulfill()
|
||||
// },
|
||||
// failure: { _ in
|
||||
//
|
||||
// XCTFail()
|
||||
// }
|
||||
// )
|
||||
// self.waitAndCheckExpectations()
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//// MARK: TestListObserver
|
||||
//
|
||||
//@available(macOS 10.12, *)
|
||||
//class TestListObserver: ListSectionObserver {
|
||||
//
|
||||
// // MARK: ListObserver
|
||||
//
|
||||
// typealias ListEntityType = TestEntity1
|
||||
//
|
||||
// func listMonitorWillChange(_ monitor: ListMonitor<TestEntity1>) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitorWillChange:"),
|
||||
// object: self,
|
||||
// userInfo: [:]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func listMonitorDidChange(_ monitor: ListMonitor<TestEntity1>) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitorDidChange:"),
|
||||
// object: self,
|
||||
// userInfo: [:]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func listMonitorWillRefetch(_ monitor: ListMonitor<TestEntity1>) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitorWillRefetch:"),
|
||||
// object: self,
|
||||
// userInfo: [:]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func listMonitorDidRefetch(_ monitor: ListMonitor<TestEntity1>) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitorDidRefetch:"),
|
||||
// object: self,
|
||||
// userInfo: [:]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // MARK: ListObjectObserver
|
||||
//
|
||||
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didInsertObject object: TestEntity1, toIndexPath indexPath: IndexPath) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
|
||||
// object: self,
|
||||
// userInfo: [
|
||||
// "object": object,
|
||||
// "indexPath": indexPath
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didDeleteObject object: TestEntity1, fromIndexPath indexPath: IndexPath) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"),
|
||||
// object: self,
|
||||
// userInfo: [
|
||||
// "object": object,
|
||||
// "indexPath": indexPath
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didUpdateObject object: TestEntity1, atIndexPath indexPath: IndexPath) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"),
|
||||
// object: self,
|
||||
// userInfo: [
|
||||
// "object": object,
|
||||
// "indexPath": indexPath
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
//
|
||||
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didMoveObject object: TestEntity1, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"),
|
||||
// object: self,
|
||||
// userInfo: [
|
||||
// "object": object,
|
||||
// "fromIndexPath": fromIndexPath,
|
||||
// "toIndexPath": toIndexPath
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // MARK: ListSectionObserver
|
||||
//
|
||||
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"),
|
||||
// object: self,
|
||||
// userInfo: [
|
||||
// "sectionInfo": sectionInfo,
|
||||
// "sectionIndex": sectionIndex
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
|
||||
//
|
||||
// NotificationCenter.default.post(
|
||||
// name: Notification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"),
|
||||
// object: self,
|
||||
// userInfo: [
|
||||
// "sectionInfo": sectionInfo,
|
||||
// "sectionIndex": sectionIndex
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
@objc
|
||||
dynamic func test_ThatListPublishers_CanReceiveUpdateNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
let observer = NSObject()
|
||||
let listPublisher = stack.publishList(
|
||||
From<TestEntity1>(),
|
||||
SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
|
||||
|
||||
let didChangeExpectation = self.expectation(description: "didChange")
|
||||
listPublisher.addObserver(observer) { listPublisher in
|
||||
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
|
||||
|
||||
didChangeExpectation.fulfill()
|
||||
}
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
if let object = try transaction.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) {
|
||||
|
||||
object.testNumber = NSNumber(value: 11)
|
||||
object.testDecimal = NSDecimalNumber(string: "11")
|
||||
object.testString = "nil:TestEntity1:11"
|
||||
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
if let object = try transaction.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
|
||||
|
||||
object.testNumber = NSNumber(value: 22)
|
||||
object.testDecimal = NSDecimalNumber(string: "22")
|
||||
object.testString = "nil:TestEntity1:22"
|
||||
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
self.waitAndCheckExpectations()
|
||||
|
||||
withExtendedLifetime(listPublisher, {})
|
||||
withExtendedLifetime(observer, {})
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatListPublishers_CanReceiveMoveNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
let observer = NSObject()
|
||||
let listPublisher = stack.publishList(
|
||||
From<TestEntity1>(),
|
||||
SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
|
||||
|
||||
let didChangeExpectation = self.expectation(description: "didChange")
|
||||
listPublisher.addObserver(observer) { listPublisher in
|
||||
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 1)
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 4)
|
||||
|
||||
didChangeExpectation.fulfill()
|
||||
}
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
if let object = try transaction.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
|
||||
|
||||
object.testBoolean = NSNumber(value: true)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
self.waitAndCheckExpectations()
|
||||
|
||||
withExtendedLifetime(listPublisher, {})
|
||||
withExtendedLifetime(observer, {})
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatListPublishers_CanReceiveDeleteNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
let observer = NSObject()
|
||||
let listPublisher = stack.publishList(
|
||||
From<TestEntity1>(),
|
||||
SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
|
||||
|
||||
let didChangeExpectation = self.expectation(description: "didChange")
|
||||
listPublisher.addObserver(observer) { listPublisher in
|
||||
|
||||
XCTAssertTrue(listPublisher.snapshot.hasSections())
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 1)
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems())
|
||||
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
|
||||
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 3)
|
||||
|
||||
didChangeExpectation.fulfill()
|
||||
}
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
let count = try transaction.deleteAll(
|
||||
From<TestEntity1>(),
|
||||
Where<TestEntity1>(#keyPath(TestEntity1.testBoolean), isEqualTo: false)
|
||||
)
|
||||
XCTAssertEqual(count, 2)
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
154
CoreStoreTests/ObjectPublisherTests.swift
Normal file
154
CoreStoreTests/ObjectPublisherTests.swift
Normal file
@@ -0,0 +1,154 @@
|
||||
//
|
||||
// ObjectPublisherTests.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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - ObjectPublisherTests
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
class ObjectPublisherTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatObjectPublishers_CanReceiveUpdateNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
guard let object = try stack.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
let observer = NSObject()
|
||||
let objectPublisher = stack.publishObject(object)
|
||||
XCTAssertEqual(objectPublisher.object, object)
|
||||
XCTAssertNotNil(objectPublisher.snapshot)
|
||||
|
||||
let didChangeExpectation = self.expectation(description: "didChange")
|
||||
objectPublisher.addObserver(observer) { objectPublisher in
|
||||
|
||||
XCTAssertEqual(objectPublisher.object?.testNumber, NSNumber(value: 10))
|
||||
XCTAssertEqual(objectPublisher.object?.testString, "nil:TestEntity1:10")
|
||||
|
||||
didChangeExpectation.fulfill()
|
||||
}
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
guard let object = transaction.edit(object) else {
|
||||
|
||||
XCTFail()
|
||||
try transaction.cancel()
|
||||
}
|
||||
object.testNumber = NSNumber(value: 10)
|
||||
object.testString = "nil:TestEntity1:10"
|
||||
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
self.waitAndCheckExpectations()
|
||||
|
||||
withExtendedLifetime(objectPublisher, {})
|
||||
withExtendedLifetime(observer, {})
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatObjectPublishers_CanReceiveDeleteNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
guard let object = try stack.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
let observer = NSObject()
|
||||
let objectPublisher = stack.publishObject(object)
|
||||
XCTAssertEqual(objectPublisher.object, object)
|
||||
XCTAssertNotNil(objectPublisher.snapshot)
|
||||
|
||||
let didChangeExpectation = self.expectation(description: "didChange")
|
||||
objectPublisher.addObserver(observer) { objectPublisher in
|
||||
|
||||
XCTAssertNil(objectPublisher.object)
|
||||
XCTAssertNil(objectPublisher.snapshot)
|
||||
|
||||
didChangeExpectation.fulfill()
|
||||
}
|
||||
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
guard let object = transaction.edit(object) else {
|
||||
|
||||
XCTFail()
|
||||
try transaction.cancel()
|
||||
}
|
||||
transaction.delete(object)
|
||||
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
self.waitAndCheckExpectations()
|
||||
|
||||
withExtendedLifetime(objectPublisher, {})
|
||||
withExtendedLifetime(observer, {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,9 @@ let package = Package(
|
||||
.testTarget(
|
||||
name: "CoreStoreTests",
|
||||
dependencies: ["CoreStore"],
|
||||
path: "CoreStoreTests"
|
||||
path: "CoreStoreTests",
|
||||
exclude: ["*.h", "*.m"]
|
||||
)
|
||||
],
|
||||
swiftLanguageVersions: [.v5_1]
|
||||
swiftLanguageVersions: [.v5]
|
||||
)
|
||||
|
||||
16
README.md
16
README.md
@@ -1506,9 +1506,9 @@ Note that the owner instance will not be retained. You may call `ListPublisher.r
|
||||
|
||||
The `ListSnapshot` returned from the `ListPublisher.snapshot` property returns a full-copy `struct` of all sections and `NSManagedObject` items in the list. This is ideal for managing states as they are thread-safe and are not affected by further changes to the result set. `ListPublisher` automatically updates its `snapshot` value to the latest state of the fetch.
|
||||
|
||||
Unlike `ListMonitor`s (See [`ListMonitor` examples](#observe-detailed-list-changes) below), a `ListPublisher` does not track detailed inserts, deletes, and moves. In return, a `ListPublisher` is a lot more lightweight and are designed to work well with `DiffableDataSource.TableView`s and `DiffableDataSource.CollectionView`s:
|
||||
Unlike `ListMonitor`s (See [`ListMonitor` examples](#observe-detailed-list-changes) below), a `ListPublisher` does not track detailed inserts, deletes, and moves. In return, a `ListPublisher` is a lot more lightweight and are designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s:
|
||||
```swift
|
||||
self.dataSource = DiffableDataSource.CollectionView<Person>(
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (collectionView, indexPath, person) in
|
||||
@@ -1552,10 +1552,10 @@ Including `ListObserver`, there are 3 observer protocols you can implement depen
|
||||
|
||||
- `ListObjectObserver`: in addition to `ListObserver` methods, also lets you handle object inserts, updates, and deletes:
|
||||
```swift
|
||||
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didInsertObject object: MyPersonEntity, toIndexPath indexPath: NSIndexPath)
|
||||
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: NSIndexPath)
|
||||
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: NSIndexPath)
|
||||
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didMoveObject object: MyPersonEntity, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
|
||||
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didInsertObject object: MyPersonEntity, toIndexPath indexPath: IndexPath)
|
||||
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: IndexPath)
|
||||
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: IndexPath)
|
||||
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didMoveObject object: MyPersonEntity, fromIndexPath: IndexPath, toIndexPath: IndexPath)
|
||||
```
|
||||
- `ListSectionObserver`: in addition to `ListObjectObserver` methods, also lets you handle section inserts and deletes:
|
||||
```swift
|
||||
@@ -1612,9 +1612,9 @@ func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -
|
||||
}
|
||||
```
|
||||
|
||||
To access the objects of a sectioned list, use an `NSIndexPath` or a tuple:
|
||||
To access the objects of a sectioned list, use an `IndexPath` or a tuple:
|
||||
```swift
|
||||
let indexPath = NSIndexPath(forRow: 2, inSection: 1)
|
||||
let indexPath = IndexPath(row: 2, section: 1)
|
||||
let person1 = self.monitor[indexPath]
|
||||
let person2 = self.monitor[1, 2]
|
||||
// person1 and person2 are the same object
|
||||
|
||||
@@ -43,5 +43,5 @@ internal protocol AttributeProtocol: PropertyProtocol {
|
||||
var rawObject: CoreStoreManagedObject? { get set }
|
||||
var getter: CoreStoreManagedObject.CustomGetter? { get }
|
||||
var setter: CoreStoreManagedObject.CustomSetter? { get }
|
||||
var valueForSnapshot: Any { get }
|
||||
var valueForSnapshot: Any? { get }
|
||||
}
|
||||
|
||||
@@ -373,15 +373,34 @@ fileprivate struct CoreStoreFetchedSectionInfoWrapper: CoreStoreDebugStringConve
|
||||
var coreStoreDumpString: String {
|
||||
|
||||
return createFormattedString(
|
||||
"\"\(self.sectionInfo.name)\" (", ")",
|
||||
("numberOfObjects", self.sectionInfo.numberOfObjects),
|
||||
("indexTitle", self.sectionInfo.indexTitle as Any)
|
||||
"\"\(self.sectionName)\" (", ")",
|
||||
("numberOfObjects", self.numberOfObjects),
|
||||
("indexTitle", self.sectionIndexTitle as Any)
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
let sectionInfo: NSFetchedResultsSectionInfo
|
||||
fileprivate init(_ sectionInfo: NSFetchedResultsSectionInfo) {
|
||||
|
||||
self.sectionName = sectionInfo.name
|
||||
self.numberOfObjects = sectionInfo.numberOfObjects
|
||||
self.sectionIndexTitle = sectionInfo.indexTitle
|
||||
}
|
||||
|
||||
fileprivate init(_ section: Internals.DiffableDataSourceSnapshot.Section) {
|
||||
|
||||
self.sectionName = section.differenceIdentifier
|
||||
self.numberOfObjects = section.elements.count
|
||||
self.sectionIndexTitle = nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let sectionName: String
|
||||
private let sectionIndexTitle: String?
|
||||
private let numberOfObjects: Int
|
||||
}
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
@@ -409,6 +428,56 @@ extension ListMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConvert
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListPublisher
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ListPublisher: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
return formattedDebugDescription(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreDebugStringConvertible
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
("snapshot", self.snapshot)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListSnapshot
|
||||
|
||||
extension ListSnapshot: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
return formattedDebugDescription(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreDebugStringConvertible
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
("numberOfObjects", self.numberOfItems),
|
||||
("sections", self.diffableSnapshot.sections.map(CoreStoreFetchedSectionInfoWrapper.init))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - LocalStorageOptions
|
||||
|
||||
extension LocalStorageOptions: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
@@ -568,6 +637,56 @@ extension ObjectMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConve
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ObjectPublisher
|
||||
|
||||
extension ObjectPublisher: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
return formattedDebugDescription(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreDebugStringConvertible
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
("objectID", self.objectID()),
|
||||
("object", self.object as Any)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ObjectSnapshot
|
||||
|
||||
extension ObjectSnapshot: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
return formattedDebugDescription(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreDebugStringConvertible
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
("objectID", self.objectID()),
|
||||
("dictionaryForValues", self.dictionaryForValues())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - OrderBy
|
||||
|
||||
extension OrderBy: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
@@ -1160,7 +1279,7 @@ extension NSEntityDescription: CoreStoreDebugStringConvertible {
|
||||
|
||||
info.append(("compoundIndexes", self.compoundIndexes))
|
||||
}
|
||||
if #available(macOS 10.11, *) {
|
||||
if #available(macOS 10.11, iOS 9.0, *) {
|
||||
|
||||
info.append(("uniquenessConstraints", self.uniquenessConstraints))
|
||||
}
|
||||
|
||||
@@ -452,7 +452,7 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
}
|
||||
for (entity, entityDescription) in entityDescriptionsByEntity {
|
||||
|
||||
if #available(macOS 10.11, *) {
|
||||
if #available(macOS 10.11, iOS 9.0, *) {
|
||||
|
||||
let uniqueConstraints = entity.uniqueConstraints.filter({ !$0.isEmpty })
|
||||
if !uniqueConstraints.isEmpty {
|
||||
|
||||
@@ -39,9 +39,21 @@ extension DataStack {
|
||||
- parameter object: the `DynamicObject` to observe changes from
|
||||
- returns: an `ObjectPublisher` that broadcasts changes to `object`
|
||||
*/
|
||||
public func objectPublisher<O: DynamicObject>(_ object: O) -> ObjectPublisher<O> {
|
||||
public func publishObject<O: DynamicObject>(_ object: O) -> ObjectPublisher<O> {
|
||||
|
||||
return ObjectPublisher<O>(objectID: object.cs_id(), context: self.unsafeContext())
|
||||
return self.publishObject(object.cs_id())
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an `ObjectPublisher` for a `DynamicObject` with the specified `ObjectID`. Multiple objects may then register themselves to be notified when changes are made to the `DynamicObject`.
|
||||
|
||||
- parameter objectID: the `ObjectID` of the object to observe changes from
|
||||
- returns: an `ObjectPublisher` that broadcasts changes to `object`
|
||||
*/
|
||||
public func publishObject<O: DynamicObject>(_ objectID: O.ObjectID) -> ObjectPublisher<O> {
|
||||
|
||||
let context = self.unsafeContext()
|
||||
return context.objectPublisher(objectID: objectID)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,9 +63,9 @@ extension DataStack {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListPublisher` that broadcasts changes to the fetched results
|
||||
*/
|
||||
public func listPublisher<O>(_ from: From<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
|
||||
public func publishList<O>(_ from: From<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
|
||||
|
||||
return self.listPublisher(from, fetchClauses)
|
||||
return self.publishList(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +75,7 @@ extension DataStack {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListPublisher` that broadcasts changes to the fetched results
|
||||
*/
|
||||
public func listPublisher<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
|
||||
public func publishList<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
|
||||
|
||||
return ListPublisher(
|
||||
dataStack: self,
|
||||
@@ -99,9 +111,9 @@ extension DataStack {
|
||||
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
||||
- returns: a `ListPublisher` that broadcasts changes to the fetched results
|
||||
*/
|
||||
public func listPublisher<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
|
||||
public func publishList<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
|
||||
|
||||
return self.listPublisher(
|
||||
return self.publishList(
|
||||
clauseChain.from,
|
||||
clauseChain.fetchClauses
|
||||
)
|
||||
@@ -115,9 +127,9 @@ extension DataStack {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListPublisher` that broadcasts changes to the fetched results
|
||||
*/
|
||||
public func listPublisher<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
|
||||
public func publishList<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
|
||||
|
||||
return self.listPublisher(
|
||||
return self.publishList(
|
||||
from,
|
||||
sectionBy,
|
||||
fetchClauses
|
||||
@@ -132,7 +144,7 @@ extension DataStack {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListPublisher` that broadcasts changes to the fetched results
|
||||
*/
|
||||
public func listPublisher<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
|
||||
public func publishList<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
|
||||
|
||||
return ListPublisher(
|
||||
dataStack: self,
|
||||
@@ -169,14 +181,59 @@ extension DataStack {
|
||||
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
||||
- returns: a `ListPublisher` that broadcasts changes to the fetched results
|
||||
*/
|
||||
public func listPublisher<B: SectionMonitorBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
|
||||
public func publishList<B: SectionMonitorBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
|
||||
|
||||
return self.listPublisher(
|
||||
return self.publishList(
|
||||
clauseChain.from,
|
||||
clauseChain.sectionBy,
|
||||
clauseChain.fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Deprecated
|
||||
|
||||
@available(*, deprecated, renamed: "publishObject(_:)")
|
||||
public func objectPublisher<O: DynamicObject>(_ object: O) -> ObjectPublisher<O> {
|
||||
|
||||
return self.publishObject(object)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "publishList(_:_:)")
|
||||
public func listPublisher<O>(_ from: From<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
|
||||
|
||||
return self.publishList(from, fetchClauses)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "publishList(_:_:)")
|
||||
public func listPublisher<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
|
||||
|
||||
return self.publishList(from, fetchClauses)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "publishList(_:)")
|
||||
public func listPublisher<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
|
||||
|
||||
return self.publishList(clauseChain)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "publishList(_:_:_:)")
|
||||
public func listPublisher<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
|
||||
|
||||
return self.publishList(from, sectionBy, fetchClauses)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "publishList(_:_:_:)")
|
||||
public func listPublisher<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
|
||||
|
||||
return self.publishList(from, sectionBy, fetchClauses)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "publishList(_:)")
|
||||
public func listPublisher<B: SectionMonitorBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
|
||||
|
||||
return self.publishList(clauseChain)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -614,7 +614,7 @@ extension DataStack {
|
||||
}
|
||||
let fileManager = FileManager.default
|
||||
let systemTemporaryDirectoryURL: URL
|
||||
if #available(macOS 10.12, *) {
|
||||
if #available(macOS 10.12, iOS 10.0, *) {
|
||||
|
||||
systemTemporaryDirectoryURL = fileManager.temporaryDirectory
|
||||
}
|
||||
|
||||
216
Sources/DiffableDataSource.BaseAdapter.swift
Normal file
216
Sources/DiffableDataSource.BaseAdapter.swift
Normal file
@@ -0,0 +1,216 @@
|
||||
//
|
||||
// DiffableDataSource.BaseAdapter.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2018 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: - BaseAdapter
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.BaseAdapter` serves as a superclass for consumers of `ListPublisher` and `ListSnapshot` diffable data.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
|
||||
tableView: self.tableView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (tableView, indexPath, person) in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
|
||||
cell.setPerson(person)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
```
|
||||
The dataSource can then apply changes from a `ListPublisher` as shown:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
|
||||
*/
|
||||
open class BaseAdapter<O: DynamicObject, T: Target>: NSObject {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
The object type represented by this dataSource
|
||||
*/
|
||||
public typealias ObjectType = O
|
||||
|
||||
/**
|
||||
The target to be updated by this dataSource
|
||||
*/
|
||||
public let target: T
|
||||
|
||||
/**
|
||||
The `DataStack` where object fetches are performed
|
||||
*/
|
||||
public let dataStack: DataStack
|
||||
|
||||
/**
|
||||
Initializes the `DiffableDataSource.BaseAdapter` object. This instance needs to be held on (retained) for as long as the target's lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.TableViewAdapterAdapter<Person>(
|
||||
tableView: self.tableView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (tableView, indexPath, person) in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
|
||||
cell.setPerson(person)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
```
|
||||
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableViewAdapter`.
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
|
||||
*/
|
||||
public init(target: T, dataStack: DataStack) {
|
||||
|
||||
self.target = target
|
||||
self.dataStack = dataStack
|
||||
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
|
||||
}
|
||||
|
||||
/**
|
||||
Clears the target.
|
||||
- parameter animatingDifferences: if `true`, animations may be applied accordingly. Defaults to `true`.
|
||||
*/
|
||||
open func purge(animatingDifferences: Bool = true, completion: @escaping () -> Void = {}) {
|
||||
|
||||
self.dispatcher.purge(
|
||||
target: self.target,
|
||||
animatingDifferences: animatingDifferences,
|
||||
performUpdates: { target, changeset, setSections in
|
||||
|
||||
target.reload(
|
||||
using: changeset,
|
||||
animated: animatingDifferences,
|
||||
setData: setSections
|
||||
)
|
||||
},
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Reloads the target using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
``
|
||||
- parameter snapshot: the `ListSnapshot` used to reload the target with. This is typically from the `snapshot` property of a `ListPublisher`.
|
||||
- parameter animatingDifferences: if `true`, animations may be applied accordingly. Defaults to `true`.
|
||||
*/
|
||||
open func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true, completion: @escaping () -> Void = {}) {
|
||||
|
||||
let diffableSnapshot = snapshot.diffableSnapshot
|
||||
self.dispatcher.apply(
|
||||
diffableSnapshot,
|
||||
target: self.target,
|
||||
animatingDifferences: animatingDifferences,
|
||||
performUpdates: { target, changeset, setSections in
|
||||
|
||||
target.reload(
|
||||
using: changeset,
|
||||
animated: animatingDifferences,
|
||||
setData: setSections
|
||||
)
|
||||
},
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of sections
|
||||
|
||||
- parameter indexPath: the `IndexPath` to search for
|
||||
- returns: the number of sections
|
||||
*/
|
||||
public func numberOfSections() -> Int {
|
||||
|
||||
return self.dispatcher.numberOfSections()
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of items at the specified section, or `nil` if the section is not found
|
||||
|
||||
- parameter section: the section index to search for
|
||||
- returns: the number of items at the specified section, or `nil` if the section is not found
|
||||
*/
|
||||
public func numberOfItems(inSection section: Int) -> Int? {
|
||||
|
||||
return self.dispatcher.numberOfItems(inSection: section)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the section identifier at the specified index, or `nil` if not found
|
||||
|
||||
- parameter section: the section index to search for
|
||||
- returns: the section identifier at the specified indec, or `nil` if not found
|
||||
*/
|
||||
public func sectionID(for section: Int) -> String? {
|
||||
|
||||
return self.dispatcher.sectionIdentifier(inSection: section)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
|
||||
- parameter indexPath: the `IndexPath` to search for
|
||||
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
*/
|
||||
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
return self.dispatcher.itemIdentifier(for: indexPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
|
||||
- parameter itemID: the object identifier to search for
|
||||
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
*/
|
||||
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
|
||||
|
||||
return self.dispatcher.indexPath(for: itemID)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let dispatcher: Internals.DiffableDataUIDispatcher<O>
|
||||
}
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
//
|
||||
// DiffableDataSource.CollectionView-UIKit.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) && (os(iOS) || os(tvOS))
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: - CollectionView
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.CollectionView` serves as a `UICollectionViewDataSource` that handles `ListPublisher` snapshots for a `UICollectionView`. Subclasses of `DiffableDataSource.CollectionView` may override some `UICollectionViewDataSource` methods as needed.
|
||||
The `DiffableDataSource.CollectionView` instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.CollectionView<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (collectionView, indexPath, person) in
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell
|
||||
cell.setPerson(person)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
```
|
||||
The dataSource can then apply changes from a `ListPublisher` as shown:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
`DiffableDataSource.CollectionView` fully handles the reload animations.
|
||||
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
|
||||
*/
|
||||
open class CollectionView<O: DynamicObject>: NSObject, UICollectionViewDataSource {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
The object type represented by this dataSource
|
||||
*/
|
||||
public typealias ObjectType = O
|
||||
|
||||
/**
|
||||
Initializes the `DiffableDataSource.CollectionView`. This instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.CollectionView<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (collectionView, indexPath, person) in
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell
|
||||
cell.setPerson(person)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
```
|
||||
- parameter collectionView: the `UICollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionView`.
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter cellProvider: a closure that configures and returns the `UICollectionViewCell` for the object
|
||||
- parameter supplementaryViewProvider: an optional closure for providing `UICollectionReusableView` supplementary views. If not set, defaults to returning `nil`
|
||||
*/
|
||||
@nonobjc
|
||||
public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) {
|
||||
|
||||
self.collectionView = collectionView
|
||||
self.cellProvider = cellProvider
|
||||
self.supplementaryViewProvider = supplementaryViewProvider
|
||||
self.dataStack = dataStack
|
||||
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
|
||||
|
||||
super.init()
|
||||
|
||||
collectionView.dataSource = self
|
||||
}
|
||||
|
||||
/**
|
||||
Reloads the `UICollectionView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
|
||||
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
|
||||
*/
|
||||
public func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true) {
|
||||
|
||||
let diffableSnapshot = snapshot.diffableSnapshot
|
||||
self.dispatcher.apply(
|
||||
diffableSnapshot as! Internals.DiffableDataSourceSnapshot,
|
||||
view: self.collectionView,
|
||||
animatingDifferences: animatingDifferences,
|
||||
performUpdates: { collectionView, changeset, setSections in
|
||||
|
||||
collectionView.reload(
|
||||
using: changeset,
|
||||
setData: setSections
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
|
||||
- parameter indexPath: the `IndexPath` to search for
|
||||
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
*/
|
||||
@nonobjc
|
||||
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
return self.dispatcher.itemIdentifier(for: indexPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
|
||||
- parameter itemID: the object identifier to search for
|
||||
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
*/
|
||||
@nonobjc
|
||||
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
|
||||
|
||||
return self.dispatcher.indexPath(for: itemID)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UICollectionViewDataSource
|
||||
|
||||
@objc
|
||||
public dynamic func numberOfSections(in collectionView: UICollectionView) -> Int {
|
||||
|
||||
return self.dispatcher.numberOfSections()
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
|
||||
return self.dispatcher.numberOfItems(inSection: section)
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
|
||||
guard let objectID = self.dispatcher.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(collectionView, indexPath, object) else {
|
||||
|
||||
Internals.abort("\(Internals.typeName(UICollectionViewDataSource.self)) returned a `nil` cell for \(Internals.typeName(IndexPath.self)) \(indexPath)")
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
|
||||
|
||||
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
|
||||
|
||||
return UICollectionReusableView()
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private weak var collectionView: UICollectionView?
|
||||
|
||||
private let dataStack: DataStack
|
||||
private let cellProvider: (UICollectionView, IndexPath, O) -> UICollectionViewCell?
|
||||
private let supplementaryViewProvider: (UICollectionView, String, IndexPath) -> UICollectionReusableView?
|
||||
private let dispatcher: Internals.DiffableDataUIDispatcher<O>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UICollectionView
|
||||
|
||||
extension UICollectionView {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
@nonobjc
|
||||
fileprivate func reload<C, O>(
|
||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void
|
||||
) {
|
||||
|
||||
if case .none = window, let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
for changeset in stagedChangeset {
|
||||
|
||||
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
self.performBatchUpdates(
|
||||
{
|
||||
setData(changeset.data)
|
||||
|
||||
if !changeset.sectionDeleted.isEmpty {
|
||||
|
||||
self.deleteSections(IndexSet(changeset.sectionDeleted))
|
||||
}
|
||||
if !changeset.sectionInserted.isEmpty {
|
||||
|
||||
self.insertSections(IndexSet(changeset.sectionInserted))
|
||||
}
|
||||
if !changeset.sectionUpdated.isEmpty {
|
||||
|
||||
self.reloadSections(IndexSet(changeset.sectionUpdated))
|
||||
}
|
||||
for (source, target) in changeset.sectionMoved {
|
||||
|
||||
self.moveSection(source, toSection: target)
|
||||
}
|
||||
if !changeset.elementDeleted.isEmpty {
|
||||
|
||||
self.deleteItems(
|
||||
at: changeset.elementDeleted.map { IndexPath(item: $0.element, section: $0.section) }
|
||||
)
|
||||
}
|
||||
if !changeset.elementInserted.isEmpty {
|
||||
|
||||
self.insertItems(
|
||||
at: changeset.elementInserted.map { IndexPath(item: $0.element, section: $0.section) }
|
||||
)
|
||||
}
|
||||
if !changeset.elementUpdated.isEmpty {
|
||||
|
||||
self.reloadItems(
|
||||
at: changeset.elementUpdated.map { IndexPath(item: $0.element, section: $0.section) }
|
||||
)
|
||||
}
|
||||
for (source, target) in changeset.elementMoved {
|
||||
|
||||
self.moveItem(
|
||||
at: IndexPath(item: source.element, section: source.section),
|
||||
to: IndexPath(item: target.element, section: target.section)
|
||||
)
|
||||
}
|
||||
},
|
||||
completion: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
232
Sources/DiffableDataSource.CollectionViewAdapter-AppKit.swift
Normal file
232
Sources/DiffableDataSource.CollectionViewAdapter-AppKit.swift
Normal file
@@ -0,0 +1,232 @@
|
||||
//
|
||||
// DiffableDataSource.CollectionViewAdapter-AppKit.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(AppKit) && os(macOS)
|
||||
|
||||
import AppKit
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: - CollectionView
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.CollectionViewAdapter` serves as a `NSCollectionViewDataSource` that handles `ListPublisher` snapshots for a `NSCollectionView`. Subclasses of `DiffableDataSource.CollectionViewAdapter` may override some `NSCollectionViewDataSource` methods as needed.
|
||||
The `DiffableDataSource.CollectionViewAdapter` instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
itemProvider: { (collectionView, indexPath, person) in
|
||||
let item = collectionView.makeItem(withIdentifier: .collectionViewItem, for: indexPath) as! PersonItem
|
||||
item.setPerson(person)
|
||||
return item
|
||||
}
|
||||
)
|
||||
```
|
||||
The dataSource can then apply changes from a `ListPublisher` as shown:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
`DiffableDataSource.CollectionViewAdapter` fully handles the reload animations.
|
||||
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
|
||||
*/
|
||||
open class CollectionViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultCollectionViewTarget<NSCollectionView>>, NSCollectionViewDataSource {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes the `DiffableDataSource.CollectionViewAdapter`. This instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
itemProvider: { (collectionView, indexPath, person) in
|
||||
let item = collectionView.makeItem(withIdentifier: .collectionViewItem, for: indexPath) as! PersonItem
|
||||
item.setPerson(person)
|
||||
return item
|
||||
}
|
||||
)
|
||||
```
|
||||
- parameter collectionView: the `NSCollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionViewAdapter`.
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
|
||||
*/
|
||||
@nonobjc
|
||||
public init(collectionView: NSCollectionView, dataStack: DataStack, itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?, supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }) {
|
||||
|
||||
self.itemProvider = itemProvider
|
||||
self.supplementaryViewProvider = supplementaryViewProvider
|
||||
|
||||
super.init(target: .init(collectionView), dataStack: dataStack)
|
||||
|
||||
collectionView.dataSource = self
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSCollectionViewDataSource
|
||||
|
||||
@objc
|
||||
public dynamic func numberOfSections(in collectionView: NSCollectionView) -> Int {
|
||||
|
||||
return self.numberOfSections()
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
|
||||
return self.numberOfItems(inSection: section) ?? 0
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
|
||||
|
||||
guard let objectID = self.itemID(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 item = self.itemProvider(collectionView, indexPath, object) else {
|
||||
|
||||
Internals.abort("\(Internals.typeName(NSCollectionViewDataSource.self)) returned a `nil` item for \(Internals.typeName(IndexPath.self)) \(indexPath)")
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
|
||||
|
||||
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
|
||||
|
||||
return NSView()
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let itemProvider: (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?
|
||||
private let supplementaryViewProvider: (NSCollectionView, String, IndexPath) -> NSView?
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DefaultCollectionViewTarget
|
||||
|
||||
public struct DefaultCollectionViewTarget<T: NSCollectionView>: Target {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public typealias Base = T
|
||||
|
||||
public private(set) weak var base: Base?
|
||||
|
||||
public init(_ base: Base) {
|
||||
|
||||
self.base = base
|
||||
}
|
||||
|
||||
|
||||
// MARK: DiffableDataSource.Target:
|
||||
|
||||
public var shouldSuspendBatchUpdates: Bool {
|
||||
|
||||
return self.base?.window == nil
|
||||
}
|
||||
|
||||
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.deleteSections(indices)
|
||||
}
|
||||
|
||||
public func insertSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.insertSections(indices)
|
||||
}
|
||||
|
||||
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.reloadSections(indices)
|
||||
}
|
||||
|
||||
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
||||
|
||||
self.base?.moveSection(index, toSection: newIndex)
|
||||
}
|
||||
|
||||
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.deleteItems(at: Set(indexPaths))
|
||||
}
|
||||
|
||||
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.insertItems(at: Set(indexPaths))
|
||||
}
|
||||
|
||||
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.reloadItems(at: Set(indexPaths))
|
||||
}
|
||||
|
||||
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
||||
|
||||
self.base?.moveItem(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
|
||||
|
||||
self.base?.animator().performBatchUpdates(updates, completionHandler: nil)
|
||||
}
|
||||
|
||||
public func reloadData() {
|
||||
|
||||
self.base?.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Deprecated
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
@available(*, deprecated, renamed: "CollectionViewAdapter")
|
||||
public typealias CollectionView = CollectionViewAdapter
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
231
Sources/DiffableDataSource.CollectionViewAdapter-UIKit.swift
Normal file
231
Sources/DiffableDataSource.CollectionViewAdapter-UIKit.swift
Normal file
@@ -0,0 +1,231 @@
|
||||
//
|
||||
// DiffableDataSource.CollectionViewAdapter-UIKit.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) && (os(iOS) || os(tvOS))
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: - CollectionView
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.CollectionViewAdapter` serves as a `UICollectionViewDataSource` that handles `ListPublisher` snapshots for a `UICollectionView`. Subclasses of `DiffableDataSource.CollectionViewAdapter` may override some `UICollectionViewDataSource` methods as needed.
|
||||
The `DiffableDataSource.CollectionViewAdapter` instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (collectionView, indexPath, person) in
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell
|
||||
cell.setPerson(person)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
```
|
||||
The dataSource can then apply changes from a `ListPublisher` as shown:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
`DiffableDataSource.CollectionViewAdapter` fully handles the reload animations.
|
||||
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
|
||||
*/
|
||||
open class CollectionViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultCollectionViewTarget<UICollectionView>>, UICollectionViewDataSource {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes the `DiffableDataSource.CollectionViewAdapter`. This instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (collectionView, indexPath, person) in
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell
|
||||
cell.setPerson(person)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
```
|
||||
- parameter collectionView: the `UICollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionViewAdapter`.
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter cellProvider: a closure that configures and returns the `UICollectionViewCell` for the object
|
||||
- parameter supplementaryViewProvider: an optional closure for providing `UICollectionReusableView` supplementary views. If not set, defaults to returning `nil`
|
||||
*/
|
||||
public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) {
|
||||
|
||||
self.cellProvider = cellProvider
|
||||
self.supplementaryViewProvider = supplementaryViewProvider
|
||||
|
||||
super.init(target: .init(collectionView), dataStack: dataStack)
|
||||
|
||||
collectionView.dataSource = self
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UICollectionViewDataSource
|
||||
|
||||
@objc
|
||||
public dynamic func numberOfSections(in collectionView: UICollectionView) -> Int {
|
||||
|
||||
return self.numberOfSections()
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
|
||||
return self.numberOfItems(inSection: section) ?? 0
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
|
||||
guard let objectID = self.itemID(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(collectionView, indexPath, object) else {
|
||||
|
||||
Internals.abort("\(Internals.typeName(UICollectionViewDataSource.self)) returned a `nil` cell for \(Internals.typeName(IndexPath.self)) \(indexPath)")
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
|
||||
|
||||
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
|
||||
|
||||
return UICollectionReusableView()
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let cellProvider: (UICollectionView, IndexPath, O) -> UICollectionViewCell?
|
||||
private let supplementaryViewProvider: (UICollectionView, String, IndexPath) -> UICollectionReusableView?
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DefaultCollectionViewTarget
|
||||
|
||||
public struct DefaultCollectionViewTarget<T: UICollectionView>: Target {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public typealias Base = T
|
||||
|
||||
public private(set) weak var base: Base?
|
||||
|
||||
public init(_ base: Base) {
|
||||
|
||||
self.base = base
|
||||
}
|
||||
|
||||
|
||||
// MARK: DiffableDataSource.Target
|
||||
|
||||
public var shouldSuspendBatchUpdates: Bool {
|
||||
|
||||
return self.base?.window == nil
|
||||
}
|
||||
|
||||
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.deleteSections(indices)
|
||||
}
|
||||
|
||||
public func insertSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.insertSections(indices)
|
||||
}
|
||||
|
||||
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.reloadSections(indices)
|
||||
}
|
||||
|
||||
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
||||
|
||||
self.base?.moveSection(index, toSection: newIndex)
|
||||
}
|
||||
|
||||
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.deleteItems(at: indexPaths)
|
||||
}
|
||||
|
||||
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.insertItems(at: indexPaths)
|
||||
}
|
||||
|
||||
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.reloadItems(at: indexPaths)
|
||||
}
|
||||
|
||||
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
||||
|
||||
self.base?.moveItem(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
|
||||
|
||||
self.base?.performBatchUpdates(updates, completion: nil)
|
||||
}
|
||||
|
||||
public func reloadData() {
|
||||
|
||||
self.base?.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Deprecated
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
@available(*, deprecated, renamed: "CollectionViewAdapter")
|
||||
public typealias CollectionView = CollectionViewAdapter
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,372 +0,0 @@
|
||||
//
|
||||
// DiffableDataSource.TableView-UIKit.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) && (os(iOS) || os(tvOS))
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: - TableView
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.TableView` serves as a `UITableViewDataSource` that handles `ListPublisher` snapshots for a `UITableView`. Subclasses of `DiffableDataSource.TableView` may override some `UITableViewDataSource` methods as needed.
|
||||
The `DiffableDataSource.TableView` instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.TableView<Person>(
|
||||
tableView: self.tableView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (tableView, indexPath, person) in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
|
||||
cell.setPerson(person)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
```
|
||||
The dataSource can then apply changes from a `ListPublisher` as shown:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
`DiffableDataSource.TableView` fully handles the reload animations. To turn change the default animation, set the `defaultRowAnimation`.
|
||||
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
|
||||
*/
|
||||
open class TableView<O: DynamicObject>: NSObject, UITableViewDataSource {
|
||||
|
||||
// MARK: Open
|
||||
|
||||
/**
|
||||
The animation style for row changes
|
||||
*/
|
||||
@nonobjc
|
||||
open var defaultRowAnimation: UITableView.RowAnimation = .automatic
|
||||
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
The object type represented by this dataSource
|
||||
*/
|
||||
public typealias ObjectType = O
|
||||
|
||||
/**
|
||||
Initializes the `DiffableDataSource.TableView`. This instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.TableView<Person>(
|
||||
tableView: self.tableView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (tableView, indexPath, person) in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
|
||||
cell.setPerson(person)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
```
|
||||
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableView`.
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
|
||||
*/
|
||||
@nonobjc
|
||||
public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?) {
|
||||
|
||||
self.tableView = tableView
|
||||
self.cellProvider = cellProvider
|
||||
self.dataStack = dataStack
|
||||
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
|
||||
|
||||
super.init()
|
||||
|
||||
tableView.dataSource = self
|
||||
}
|
||||
|
||||
/**
|
||||
Reloads the `UITableView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
If the `defaultRowAnimation` is configured to, animations are also applied accordingly.
|
||||
|
||||
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
|
||||
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
|
||||
*/
|
||||
@nonobjc
|
||||
public func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true) {
|
||||
|
||||
let diffableSnapshot = snapshot.diffableSnapshot
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
//
|
||||
// self.modernDataSource.apply(
|
||||
// diffableSnapshot as! NSDiffableDataSourceSnapshot<String, NSManagedObjectID>,
|
||||
// animatingDifferences: animatingDifferences,
|
||||
// completion: nil
|
||||
// )
|
||||
// }
|
||||
// else {
|
||||
|
||||
self.dispatcher.apply(
|
||||
diffableSnapshot as! Internals.DiffableDataSourceSnapshot,
|
||||
view: self.tableView,
|
||||
animatingDifferences: animatingDifferences,
|
||||
performUpdates: { tableView, changeset, setSections in
|
||||
|
||||
tableView.reload(
|
||||
using: changeset,
|
||||
with: self.defaultRowAnimation,
|
||||
setData: setSections
|
||||
)
|
||||
}
|
||||
)
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
|
||||
- parameter indexPath: the `IndexPath` to search for
|
||||
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
*/
|
||||
@nonobjc
|
||||
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
return self.dispatcher.itemIdentifier(for: indexPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
|
||||
- parameter itemID: the object identifier to search for
|
||||
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
*/
|
||||
@nonobjc
|
||||
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
|
||||
|
||||
return self.dispatcher.indexPath(for: itemID)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
|
||||
@objc
|
||||
public dynamic func numberOfSections(in tableView: UITableView) -> Int {
|
||||
|
||||
return self.dispatcher.numberOfSections()
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return self.dispatcher.numberOfItems(inSection: section)
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
|
||||
return self.dispatcher.sectionIdentifier(inSection: section)
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
guard let objectID = self.dispatcher.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
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
|
||||
|
||||
return .delete
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@nonobjc
|
||||
private weak var tableView: UITableView?
|
||||
|
||||
@nonobjc
|
||||
private let dataStack: DataStack
|
||||
|
||||
@nonobjc
|
||||
private let cellProvider: (UITableView, IndexPath, O) -> UITableViewCell?
|
||||
|
||||
@nonobjc
|
||||
private let dispatcher: Internals.DiffableDataUIDispatcher<O>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UITableView
|
||||
|
||||
extension UITableView {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
@nonobjc
|
||||
fileprivate func reload<C, O>(
|
||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||
with animation: @autoclosure () -> RowAnimation,
|
||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void
|
||||
) {
|
||||
|
||||
self.reload(
|
||||
using: stagedChangeset,
|
||||
deleteSectionsAnimation: animation(),
|
||||
insertSectionsAnimation: animation(),
|
||||
reloadSectionsAnimation: animation(),
|
||||
deleteRowsAnimation: animation(),
|
||||
insertRowsAnimation: animation(),
|
||||
reloadRowsAnimation: animation(),
|
||||
interrupt: interrupt,
|
||||
setData: setData
|
||||
)
|
||||
}
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
@nonobjc
|
||||
fileprivate func reload<C, O>(
|
||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||
deleteSectionsAnimation: @autoclosure () -> RowAnimation,
|
||||
insertSectionsAnimation: @autoclosure () -> RowAnimation,
|
||||
reloadSectionsAnimation: @autoclosure () -> RowAnimation,
|
||||
deleteRowsAnimation: @autoclosure () -> RowAnimation,
|
||||
insertRowsAnimation: @autoclosure () -> RowAnimation,
|
||||
reloadRowsAnimation: @autoclosure () -> RowAnimation,
|
||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void
|
||||
) {
|
||||
|
||||
if case .none = window, let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
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
|
||||
266
Sources/DiffableDataSource.TableViewAdapter-UIKit.swift
Normal file
266
Sources/DiffableDataSource.TableViewAdapter-UIKit.swift
Normal file
@@ -0,0 +1,266 @@
|
||||
//
|
||||
// DiffableDataSource.TableViewAdapter-UIKit.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) && (os(iOS) || os(tvOS))
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: - TableViewAdapter
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.TableViewAdapterAdapter` serves as a `UITableViewDataSource` that handles `ListPublisher` snapshots for a `UITableView`. Subclasses of `DiffableDataSource.TableViewAdapter` may override some `UITableViewDataSource` methods as needed.
|
||||
The `DiffableDataSource.TableViewAdapterAdapter` instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
|
||||
tableView: self.tableView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (tableView, indexPath, person) in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
|
||||
cell.setPerson(person)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
```
|
||||
The dataSource can then apply changes from a `ListPublisher` as shown:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
`DiffableDataSource.TableViewAdapter` fully handles the reload animations.
|
||||
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
|
||||
*/
|
||||
open class TableViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultTableViewTarget<UITableView>>, UITableViewDataSource {
|
||||
|
||||
// MARK: Publi
|
||||
|
||||
/**
|
||||
Initializes the `DiffableDataSource.TableViewAdapter`. This instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
|
||||
tableView: self.tableView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
cellProvider: { (tableView, indexPath, person) in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
|
||||
cell.setPerson(person)
|
||||
return cell
|
||||
}
|
||||
)
|
||||
```
|
||||
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableViewAdapter`.
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
|
||||
*/
|
||||
public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?) {
|
||||
|
||||
self.cellProvider = cellProvider
|
||||
super.init(target: .init(tableView), dataStack: dataStack)
|
||||
|
||||
tableView.dataSource = self
|
||||
}
|
||||
|
||||
/**
|
||||
The target `UITableView`
|
||||
*/
|
||||
public var tableView: UITableView? {
|
||||
|
||||
return self.target.base
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
|
||||
@objc
|
||||
public dynamic func numberOfSections(in tableView: UITableView) -> Int {
|
||||
|
||||
return self.numberOfSections()
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return self.numberOfItems(inSection: section) ?? 0
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
|
||||
return self.sectionID(for: section)
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
guard let objectID = self.itemID(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
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
|
||||
|
||||
return .delete
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@nonobjc
|
||||
private let cellProvider: (UITableView, IndexPath, O) -> UITableViewCell?
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DefaultTableViewTarget
|
||||
|
||||
public struct DefaultTableViewTarget<T: UITableView>: Target {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public typealias Base = T
|
||||
|
||||
public private(set) weak var base: Base?
|
||||
|
||||
public init(_ base: Base) {
|
||||
|
||||
self.base = base
|
||||
}
|
||||
|
||||
|
||||
// MARK: DiffableDataSource.Target
|
||||
|
||||
public var shouldSuspendBatchUpdates: Bool {
|
||||
|
||||
return self.base?.window == nil
|
||||
}
|
||||
|
||||
public func deleteSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.deleteSections(indices, with: .automatic)
|
||||
}
|
||||
|
||||
public func insertSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.insertSections(indices, with: .automatic)
|
||||
}
|
||||
|
||||
public func reloadSections(at indices: IndexSet, animated: Bool) {
|
||||
|
||||
self.base?.reloadSections(indices, with: .automatic)
|
||||
}
|
||||
|
||||
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
|
||||
|
||||
self.base?.moveSection(index, toSection: newIndex)
|
||||
}
|
||||
|
||||
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.deleteRows(at: indexPaths, with: .automatic)
|
||||
}
|
||||
|
||||
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.insertRows(at: indexPaths, with: .automatic)
|
||||
}
|
||||
|
||||
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
|
||||
|
||||
self.base?.reloadRows(at: indexPaths, with: .automatic)
|
||||
}
|
||||
|
||||
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
|
||||
|
||||
self.base?.moveRow(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
|
||||
|
||||
guard let base = self.base else {
|
||||
|
||||
return
|
||||
}
|
||||
if #available(iOS 11.0, tvOS 11.0, *) {
|
||||
|
||||
base.performBatchUpdates(updates)
|
||||
}
|
||||
else {
|
||||
|
||||
base.beginUpdates()
|
||||
updates()
|
||||
base.endUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
public func reloadData() {
|
||||
|
||||
self.base?.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Deprecated
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
@available(*, deprecated, renamed: "TableViewAdapter")
|
||||
public typealias TableView = TableViewAdapter
|
||||
}
|
||||
|
||||
#endif
|
||||
211
Sources/DiffableDataSource.Target.swift
Normal file
211
Sources/DiffableDataSource.Target.swift
Normal file
@@ -0,0 +1,211 @@
|
||||
//
|
||||
// DiffableDataSource.Target.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2018 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - DiffableDataSourc
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: - Target
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.Target` protocol allows custom views to consume `ListSnapshot` diffable data similar to how `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` reloads data for their corresponding views.
|
||||
*/
|
||||
public typealias Target = DiffableDataSourceTarget
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DiffableDataSource.Target
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.Target` protocol allows custom views to consume `ListSnapshot` diffable data similar to how `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` reloads data for their corresponding views.
|
||||
*/
|
||||
public protocol DiffableDataSourceTarget {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Whether `reloadData()` should be executed instead of `performBatchUpdates(updates:animated:)`.
|
||||
*/
|
||||
var shouldSuspendBatchUpdates: Bool { get }
|
||||
|
||||
/**
|
||||
Deletes one or more sections.
|
||||
*/
|
||||
func deleteSections(at indices: IndexSet, animated: Bool)
|
||||
|
||||
/**
|
||||
Inserts one or more sections
|
||||
*/
|
||||
func insertSections(at indices: IndexSet, animated: Bool)
|
||||
|
||||
/**
|
||||
Reloads the specified sections.
|
||||
*/
|
||||
func reloadSections(at indices: IndexSet, animated: Bool)
|
||||
|
||||
/**
|
||||
Moves a section to a new location.
|
||||
*/
|
||||
func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool)
|
||||
|
||||
/**
|
||||
Deletes the items specified by an array of index paths.
|
||||
*/
|
||||
func deleteItems(at indexPaths: [IndexPath], animated: Bool)
|
||||
|
||||
/**
|
||||
Inserts items at the locations identified by an array of index paths.
|
||||
*/
|
||||
func insertItems(at indexPaths: [IndexPath], animated: Bool)
|
||||
|
||||
/**
|
||||
Reloads the specified items.
|
||||
*/
|
||||
func reloadItems(at indexPaths: [IndexPath], animated: Bool)
|
||||
|
||||
/**
|
||||
Moves the item at a specified location to a destination location.
|
||||
*/
|
||||
func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool)
|
||||
|
||||
/**
|
||||
Animates multiple insert, delete, reload, and move operations as a group.
|
||||
*/
|
||||
func performBatchUpdates(updates: () -> Void, animated: Bool)
|
||||
|
||||
/**
|
||||
Reloads all sections and items.
|
||||
*/
|
||||
func reloadData()
|
||||
}
|
||||
|
||||
extension DiffableDataSource.Target {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func reload<C, O>(
|
||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||
animated: Bool,
|
||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void
|
||||
) {
|
||||
|
||||
if self.shouldSuspendBatchUpdates, let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
for changeset in stagedChangeset {
|
||||
|
||||
if let interrupt = interrupt,
|
||||
interrupt(changeset),
|
||||
let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
self.performBatchUpdates(
|
||||
updates: {
|
||||
|
||||
setData(changeset.data)
|
||||
|
||||
if !changeset.sectionDeleted.isEmpty {
|
||||
|
||||
self.deleteSections(
|
||||
at: IndexSet(changeset.sectionDeleted),
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
if !changeset.sectionInserted.isEmpty {
|
||||
|
||||
self.insertSections(
|
||||
at: IndexSet(changeset.sectionInserted),
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
if !changeset.sectionUpdated.isEmpty {
|
||||
|
||||
self.reloadSections(
|
||||
at: IndexSet(changeset.sectionUpdated),
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
for (source, target) in changeset.sectionMoved {
|
||||
|
||||
self.moveSection(
|
||||
at: source,
|
||||
to: target,
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
if !changeset.elementDeleted.isEmpty {
|
||||
|
||||
self.deleteItems(
|
||||
at: changeset.elementDeleted.map {
|
||||
|
||||
IndexPath(item: $0.element, section: $0.section)
|
||||
},
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
if !changeset.elementInserted.isEmpty {
|
||||
|
||||
self.insertItems(
|
||||
at: changeset.elementInserted.map {
|
||||
|
||||
IndexPath(item: $0.element, section: $0.section)
|
||||
},
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
if !changeset.elementUpdated.isEmpty {
|
||||
|
||||
self.reloadItems(
|
||||
at: changeset.elementUpdated.map {
|
||||
|
||||
IndexPath(item: $0.element, section: $0.section)
|
||||
},
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
for (source, target) in changeset.elementMoved {
|
||||
|
||||
self.moveItem(
|
||||
at: IndexPath(item: source.element, section: source.section),
|
||||
to: IndexPath(item: target.element, section: target.section),
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
},
|
||||
animated: animated
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
/**
|
||||
Namespace for diffable data source types. See `DiffableDataSource.TableView` and `DiffableDataSource.CollectionView` for actual implementations
|
||||
Namespace for diffable data source types. See `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` for actual implementations
|
||||
*/
|
||||
public enum DiffableDataSource {}
|
||||
|
||||
|
||||
@@ -47,9 +47,13 @@ extension Internals {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(sections: [NSFetchedResultsSectionInfo]) {
|
||||
init(sections: [NSFetchedResultsSectionInfo], fetchOffset: Int, fetchLimit: Int) {
|
||||
|
||||
self.structure = .init(sections: sections)
|
||||
self.structure = .init(
|
||||
sections: sections,
|
||||
fetchOffset: Swift.max(0, fetchOffset),
|
||||
fetchLimit: (fetchLimit > 0) ? fetchLimit : nil
|
||||
)
|
||||
}
|
||||
|
||||
var sections: [Section] {
|
||||
@@ -92,6 +96,11 @@ extension Internals {
|
||||
return self.structure.allItemIDs
|
||||
}
|
||||
|
||||
var updatedItemIdentifiers: Set<NSManagedObjectID> {
|
||||
|
||||
return self.structure.reloadedItems
|
||||
}
|
||||
|
||||
func numberOfItems(inSection identifier: String) -> Int {
|
||||
|
||||
return self.itemIdentifiers(inSection: identifier).count
|
||||
@@ -268,23 +277,54 @@ extension Internals {
|
||||
// MARK: Internal
|
||||
|
||||
var sections: [Section]
|
||||
private(set) var reloadedItems: Set<NSManagedObjectID>
|
||||
|
||||
init() {
|
||||
|
||||
self.sections = []
|
||||
self.reloadedItems = []
|
||||
}
|
||||
|
||||
init(sections: [NSFetchedResultsSectionInfo]) {
|
||||
init(sections: [NSFetchedResultsSectionInfo], fetchOffset: Int, fetchLimit: Int?) {
|
||||
|
||||
self.sections = sections.map {
|
||||
let sliceItems: (_ array: [Any], _ offset: Int) -> Array<Any>.SubSequence
|
||||
if let fetchLimit = fetchLimit {
|
||||
|
||||
Section(
|
||||
differenceIdentifier: $0.name,
|
||||
items: $0.objects?
|
||||
.compactMap({ ($0 as? NSManagedObject)?.objectID })
|
||||
.map({ Item(differenceIdentifier: $0) }) ?? []
|
||||
var remainingCount = fetchLimit
|
||||
sliceItems = {
|
||||
|
||||
let slice = $0[$1...].prefix(remainingCount)
|
||||
remainingCount -= slice.count
|
||||
return slice
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
sliceItems = { $0[$1...] }
|
||||
}
|
||||
var newSections: [Internals.DiffableDataSourceSnapshot.Section] = []
|
||||
var ignoreCount = fetchOffset
|
||||
for section in sections {
|
||||
|
||||
let objects = section.objects ?? []
|
||||
guard objects.indices.contains(ignoreCount) else {
|
||||
|
||||
ignoreCount -= objects.count
|
||||
continue
|
||||
}
|
||||
let items = sliceItems(objects, ignoreCount)
|
||||
.map({ Item(differenceIdentifier: ($0 as! NSManagedObject).objectID) })
|
||||
ignoreCount = 0
|
||||
guard !items.isEmpty else {
|
||||
|
||||
continue
|
||||
}
|
||||
newSections.append(
|
||||
Section(differenceIdentifier: section.name, items: items)
|
||||
)
|
||||
}
|
||||
self.sections = newSections
|
||||
self.reloadedItems = []
|
||||
}
|
||||
|
||||
var allSectionIDs: [String] {
|
||||
@@ -308,7 +348,7 @@ extension Internals {
|
||||
|
||||
func section(containing itemID: NSManagedObjectID) -> String? {
|
||||
|
||||
return self.itemPositionMap()[itemID]?.section.differenceIdentifier
|
||||
return self.itemPositionMap(itemID)?.section.differenceIdentifier
|
||||
}
|
||||
|
||||
mutating func append(itemIDs: [NSManagedObjectID], to sectionID: String?) {
|
||||
@@ -337,7 +377,7 @@ extension Internals {
|
||||
|
||||
mutating func insert(itemIDs: [NSManagedObjectID], before beforeItemID: NSManagedObjectID) {
|
||||
|
||||
guard let itemPosition = self.itemPositionMap()[beforeItemID] else {
|
||||
guard let itemPosition = self.itemPositionMap(beforeItemID) else {
|
||||
|
||||
Internals.abort("Item \(beforeItemID) does not exist")
|
||||
}
|
||||
@@ -348,7 +388,7 @@ extension Internals {
|
||||
|
||||
mutating func insert(itemIDs: [NSManagedObjectID], after afterItemID: NSManagedObjectID) {
|
||||
|
||||
guard let itemPosition = self.itemPositionMap()[afterItemID] else {
|
||||
guard let itemPosition = self.itemPositionMap(afterItemID) else {
|
||||
|
||||
Internals.abort("Item \(afterItemID) does not exist")
|
||||
}
|
||||
@@ -359,7 +399,7 @@ extension Internals {
|
||||
.insert(contentsOf: items, at: itemIndex)
|
||||
}
|
||||
|
||||
mutating func remove(itemIDs: [NSManagedObjectID]) {
|
||||
mutating func remove<C: Collection>(itemIDs: C) where C.Element == NSManagedObjectID {
|
||||
|
||||
let itemPositionMap = self.itemPositionMap()
|
||||
var removeIndexSetMap: [Int: IndexSet] = [:]
|
||||
@@ -390,13 +430,18 @@ extension Internals {
|
||||
}
|
||||
}
|
||||
|
||||
mutating func removeAllEmptySections() {
|
||||
|
||||
self.sections.removeAll(where: { $0.elements.isEmpty })
|
||||
}
|
||||
|
||||
mutating func move(itemID: NSManagedObjectID, before beforeItemID: NSManagedObjectID) {
|
||||
|
||||
guard let removed = self.remove(itemID: itemID) else {
|
||||
|
||||
Internals.abort("Item \(itemID) does not exist")
|
||||
}
|
||||
guard let itemPosition = self.itemPositionMap()[beforeItemID] else {
|
||||
guard let itemPosition = self.itemPositionMap(beforeItemID) else {
|
||||
|
||||
Internals.abort("Item \(beforeItemID) does not exist")
|
||||
}
|
||||
@@ -410,7 +455,7 @@ extension Internals {
|
||||
|
||||
Internals.abort("Item \(itemID) does not exist")
|
||||
}
|
||||
guard let itemPosition = self.itemPositionMap()[afterItemID] else {
|
||||
guard let itemPosition = self.itemPositionMap(afterItemID) else {
|
||||
|
||||
Internals.abort("Item \(afterItemID) does not exist")
|
||||
}
|
||||
@@ -423,6 +468,7 @@ extension Internals {
|
||||
mutating func update<S: Sequence>(itemIDs: S) where S.Element == NSManagedObjectID {
|
||||
|
||||
let itemPositionMap = self.itemPositionMap()
|
||||
var newItemIDs: Set<NSManagedObjectID> = []
|
||||
for itemID in itemIDs {
|
||||
|
||||
guard let itemPosition = itemPositionMap[itemID] else {
|
||||
@@ -431,7 +477,9 @@ extension Internals {
|
||||
}
|
||||
self.sections[itemPosition.sectionIndex]
|
||||
.elements[itemPosition.itemRelativeIndex].isReloaded = true
|
||||
newItemIDs.insert(itemID)
|
||||
}
|
||||
self.reloadedItems.formUnion(newItemIDs)
|
||||
}
|
||||
|
||||
mutating func append(sectionIDs: [String]) {
|
||||
@@ -519,7 +567,7 @@ extension Internals {
|
||||
@discardableResult
|
||||
private mutating func remove(itemID: NSManagedObjectID) -> Item? {
|
||||
|
||||
guard let itemPosition = self.itemPositionMap()[itemID] else {
|
||||
guard let itemPosition = self.itemPositionMap(itemID) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -537,6 +585,28 @@ extension Internals {
|
||||
return self.sections.remove(at: sectionIndex)
|
||||
}
|
||||
|
||||
private func itemPositionMap(_ itemID: NSManagedObjectID) -> ItemPosition? {
|
||||
|
||||
let sections = self.sections
|
||||
for (sectionIndex, section) in sections.enumerated() {
|
||||
|
||||
for (itemRelativeIndex, item) in section.elements.enumerated() {
|
||||
|
||||
guard item.differenceIdentifier == itemID else {
|
||||
|
||||
continue
|
||||
}
|
||||
return ItemPosition(
|
||||
item: item,
|
||||
itemRelativeIndex: itemRelativeIndex,
|
||||
section: section,
|
||||
sectionIndex: sectionIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func itemPositionMap() -> [NSManagedObjectID: ItemPosition] {
|
||||
|
||||
return self.sections.enumerated().reduce(into: [:]) { result, section in
|
||||
|
||||
@@ -51,8 +51,38 @@ extension Internals {
|
||||
|
||||
self.dataStack = dataStack
|
||||
}
|
||||
|
||||
func purge<Target: DiffableDataSource.Target>(
|
||||
target: Target?,
|
||||
animatingDifferences: Bool,
|
||||
performUpdates: @escaping (
|
||||
Target,
|
||||
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
|
||||
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
||||
) -> Void,
|
||||
completion: @escaping () -> Void
|
||||
) {
|
||||
|
||||
self.apply(
|
||||
.init(),
|
||||
target: target,
|
||||
animatingDifferences: animatingDifferences,
|
||||
performUpdates: performUpdates,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
func apply<View: AnyObject>(_ snapshot: DiffableDataSourceSnapshot, view: View?, animatingDifferences: Bool, performUpdates: @escaping (View, StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>, @escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void) -> Void) {
|
||||
func apply<Target: DiffableDataSource.Target>(
|
||||
_ snapshot: DiffableDataSourceSnapshot,
|
||||
target: Target?,
|
||||
animatingDifferences: Bool,
|
||||
performUpdates: @escaping (
|
||||
Target,
|
||||
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
|
||||
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
||||
) -> Void,
|
||||
completion: @escaping () -> Void
|
||||
) {
|
||||
|
||||
self.dispatcher.dispatch { [weak self] in
|
||||
|
||||
@@ -64,36 +94,42 @@ extension Internals {
|
||||
self.currentSnapshot = snapshot
|
||||
|
||||
let newSections = snapshot.sections
|
||||
guard let view = view else {
|
||||
guard let target = target else {
|
||||
|
||||
return self.sections = newSections
|
||||
self.sections = newSections
|
||||
return
|
||||
}
|
||||
|
||||
let performDiffingUpdates: () -> Void = {
|
||||
|
||||
let changeset = StagedChangeset(source: self.sections, target: newSections)
|
||||
performUpdates(view, changeset) { sections in
|
||||
performUpdates(target, changeset) { sections in
|
||||
|
||||
self.sections = sections
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(QuartzCore)
|
||||
|
||||
|
||||
CATransaction.begin()
|
||||
CATransaction.setCompletionBlock(completion)
|
||||
|
||||
if !animatingDifferences {
|
||||
|
||||
CATransaction.begin()
|
||||
|
||||
CATransaction.setDisableActions(true)
|
||||
|
||||
performDiffingUpdates()
|
||||
|
||||
CATransaction.commit()
|
||||
return
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
performDiffingUpdates()
|
||||
|
||||
CATransaction.commit()
|
||||
|
||||
|
||||
#else
|
||||
|
||||
performDiffingUpdates()
|
||||
completion()
|
||||
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,14 +140,23 @@ extension Internals {
|
||||
return snapshot
|
||||
}
|
||||
|
||||
func sectionIdentifier(inSection section: Int) -> String? {
|
||||
|
||||
guard self.sections.indices.contains(section) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return self.sections[section].differenceIdentifier
|
||||
}
|
||||
|
||||
func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
guard (0 ..< self.sections.endIndex) ~= indexPath.section else {
|
||||
|
||||
guard self.sections.indices.contains(indexPath.section) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let items = self.sections[indexPath.section].elements
|
||||
guard (0 ..< items.endIndex) ~= indexPath.item else {
|
||||
guard items.indices.contains(indexPath.item) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -138,15 +183,14 @@ extension Internals {
|
||||
return self.sections.count
|
||||
}
|
||||
|
||||
func numberOfItems(inSection section: Int) -> Int {
|
||||
|
||||
func numberOfItems(inSection section: Int) -> Int? {
|
||||
|
||||
guard self.sections.indices.contains(section) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return self.sections[section].elements.count
|
||||
}
|
||||
|
||||
func sectionIdentifier(inSection section: Int) -> String {
|
||||
|
||||
return self.sections[section].differenceIdentifier
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@@ -41,9 +41,6 @@ import AppKit
|
||||
|
||||
internal protocol FetchedDiffableDataSourceSnapshotHandler: AnyObject {
|
||||
|
||||
// @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?
|
||||
@@ -80,15 +77,6 @@ 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
|
||||
|
||||
guard let fetchedResultsController = self.fetchedResultsController else {
|
||||
|
||||
return
|
||||
@@ -98,26 +86,14 @@ 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<String, NSManagedObjectID>
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// #endif
|
||||
|
||||
@objc
|
||||
dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
|
||||
var snapshot = Internals.DiffableDataSourceSnapshot(
|
||||
sections: controller.sections ?? []
|
||||
sections: controller.sections ?? [],
|
||||
fetchOffset: controller.fetchRequest.fetchOffset,
|
||||
fetchLimit: controller.fetchRequest.fetchLimit
|
||||
)
|
||||
snapshot.reloadSections(self.reloadedSectionIDs)
|
||||
snapshot.reloadItems(self.reloadedItemIDs)
|
||||
|
||||
@@ -62,7 +62,7 @@ import SwiftUI
|
||||
.orderBy(.ascending(\.lastName))
|
||||
)
|
||||
```
|
||||
All access to the `ListPublisher` items should be done via its `snapshot` value, which is a `struct` of type `ListSnapshot<O>`. `ListSnapshot`s are also designed to work well with `DiffableDataSource.TableView`s and `DiffableDataSource.CollectionView`s. For detailed examples, refer to the documentation for `DiffableDataSource.TableView` and `DiffableDataSource.CollectionView`.
|
||||
All access to the `ListPublisher` items should be done via its `snapshot` value, which is a `struct` of type `ListSnapshot<O>`. `ListSnapshot`s are also designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s. For detailed examples, refer to the documentation for `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter`.
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
@@ -397,15 +397,6 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
extension ListPublisher: 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: Internals.DiffableDataSourceSnapshot) {
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ import AppKit
|
||||
// MARK: - ListSnapshot
|
||||
|
||||
/**
|
||||
A `ListSnapshot` holds a stable list of `DynamicObject` identifiers. This is typically created by a `ListPublisher` and are designed to work well with `DiffableDataSource.TableView`s and `DiffableDataSource.CollectionView`s. For detailed examples, see the documentation on `DiffableDataSource.TableView` and `DiffableDataSource.CollectionView`.
|
||||
A `ListSnapshot` holds a stable list of `DynamicObject` identifiers. This is typically created by a `ListPublisher` and are designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s. For detailed examples, see the documentation on `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter`.
|
||||
|
||||
While the `ListSnapshot` stores only object identifiers, all accessors to its items return `ObjectPublisher`s, which are lazily created. For more details, see the documentation on `ListObject`.
|
||||
|
||||
@@ -83,7 +83,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
*/
|
||||
public subscript(safeIndex index: Index) -> ObjectPublisher<O>? {
|
||||
|
||||
let context = self.context!
|
||||
guard let context = self.context else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let itemIDs = self.diffableSnapshot.itemIdentifiers
|
||||
guard itemIDs.indices.contains(index) else {
|
||||
|
||||
@@ -118,7 +121,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
*/
|
||||
public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> ObjectPublisher<O>? {
|
||||
|
||||
let context = self.context!
|
||||
guard let context = self.context else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let snapshot = self.diffableSnapshot
|
||||
let sectionIDs = snapshot.sectionIdentifiers
|
||||
guard sectionIDs.indices.contains(sectionIndex) else {
|
||||
@@ -214,6 +220,14 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
return snapshot.numberOfItems(inSection: sectionID) > 0
|
||||
}
|
||||
|
||||
/**
|
||||
Returns item identifiers for updated objects. This is mainly useful for Data Source adapters such as `UICollectionViewDiffableDataSource` or `UITableViewDiffableDataSource` which work on collection diffs when reloading. Since objects with same IDs resolve as "equal" in their old and new states, adapters may need extra heuristics to determine which row items need reloading. If your row items are all observing changes from each corresponding `ObjectPublisher`, or if you are using CoreStore's built-in `DiffableDataSource`s, there is no need to inspect this property.
|
||||
*/
|
||||
public var updatedItemIdentifiers: Set<NSManagedObjectID> {
|
||||
|
||||
return self.diffableSnapshot.updatedItemIdentifiers
|
||||
}
|
||||
|
||||
/**
|
||||
The number of items in all sections in the `ListSnapshot`
|
||||
*/
|
||||
@@ -340,7 +354,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
return indices.map { position in
|
||||
|
||||
let itemID = itemIDs[position]
|
||||
return ObjectPublisher<O>(objectID: itemID, context: context)
|
||||
return context.objectPublisher(objectID: itemID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,10 +368,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
let context = self.context!
|
||||
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
|
||||
return itemIDs.map {
|
||||
|
||||
return ObjectPublisher<O>(objectID: $0, context: context)
|
||||
}
|
||||
return itemIDs.map(context.objectPublisher(objectID:))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,7 +385,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
return itemIndices.map { position in
|
||||
|
||||
let itemID = itemIDs[position]
|
||||
return ObjectPublisher<O>(objectID: itemID, context: context)
|
||||
return context.objectPublisher(objectID: itemID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +402,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
return indices.lazy.map { position in
|
||||
|
||||
let itemID = itemIDs[position]
|
||||
return ObjectPublisher<O>(objectID: itemID, context: context)
|
||||
return context.objectPublisher(objectID: itemID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,10 +416,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
let context = self.context!
|
||||
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
|
||||
return itemIDs.lazy.map {
|
||||
|
||||
return ObjectPublisher<O>(objectID: $0, context: context)
|
||||
}
|
||||
return itemIDs.lazy.map(context.objectPublisher(objectID:))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -425,7 +433,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
return itemIndices.lazy.map { position in
|
||||
|
||||
let itemID = itemIDs[position]
|
||||
return ObjectPublisher<O>(objectID: itemID, context: context)
|
||||
return context.objectPublisher(objectID: itemID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -629,28 +637,14 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal private(set) var diffableSnapshot: DiffableDataSourceSnapshotProtocol
|
||||
internal private(set) var diffableSnapshot: Internals.DiffableDataSourceSnapshot
|
||||
|
||||
internal init() {
|
||||
|
||||
// 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.diffableSnapshot = Internals.DiffableDataSourceSnapshot()
|
||||
self.context = nil
|
||||
}
|
||||
|
||||
// @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
|
||||
|
||||
@@ -99,7 +99,7 @@ extension NSManagedObjectContext {
|
||||
|
||||
return objectPublisher
|
||||
}
|
||||
let objectPublisher = ObjectPublisher<O>(objectID: objectID, context: self)
|
||||
let objectPublisher = ObjectPublisher<O>.createUncached(objectID: objectID, context: self)
|
||||
cache.setObject(objectPublisher, forKey: objectID)
|
||||
return objectPublisher
|
||||
}
|
||||
@@ -120,21 +120,37 @@ extension NSManagedObjectContext {
|
||||
|
||||
return (updated: [], deleted: [])
|
||||
}
|
||||
if userInfo[NSInvalidatedAllObjectsKey] != nil {
|
||||
|
||||
let context = notification.object as! NSManagedObjectContext
|
||||
return (updated: Set(context.registeredObjects.map({ $0.objectID })), deleted: [])
|
||||
}
|
||||
|
||||
var updatedObjectIDs: Set<NSManagedObjectID> = []
|
||||
if let updatedObjects = userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObjectID> {
|
||||
if let updatedObjects = userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject> {
|
||||
|
||||
updatedObjectIDs.formUnion(updatedObjects)
|
||||
updatedObjectIDs.formUnion(updatedObjects.map({ $0.objectID }))
|
||||
}
|
||||
if let mergedObjects = userInfo[NSRefreshedObjectsKey] as? Set<NSManagedObjectID> {
|
||||
if let mergedObjects = userInfo[NSRefreshedObjectsKey] as? Set<NSManagedObject> {
|
||||
|
||||
updatedObjectIDs.formUnion(mergedObjects)
|
||||
updatedObjectIDs.formUnion(mergedObjects.map({ $0.objectID }))
|
||||
}
|
||||
let deletedObjectIDs: Set<NSManagedObjectID> = (userInfo[NSDeletedObjectsKey] as? Set<NSManagedObjectID>) ?? []
|
||||
return (updated: updatedObjectIDs, deleted: deletedObjectIDs)
|
||||
if let mergedObjects = userInfo[NSInvalidatedObjectsKey] as? Set<NSManagedObject> {
|
||||
|
||||
updatedObjectIDs.formUnion(mergedObjects.map({ $0.objectID }))
|
||||
}
|
||||
let deletedObjectIDs: Set<NSManagedObject> = (userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>) ?? []
|
||||
return (updated: updatedObjectIDs, deleted: Set(deletedObjectIDs.map({ $0.objectID })))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func objectsDidChangeObserver<U: AnyObject>(remove: U) {
|
||||
|
||||
_ = self.userInfo(for: .objectsChangeObserver(U.self), initialize: { nil as Any? })
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
@@ -162,9 +178,9 @@ extension NSManagedObjectContext {
|
||||
private func userInfo<T>(for key: UserInfoKeys, initialize: @escaping () -> T) -> T {
|
||||
|
||||
let keyString = key.keyString
|
||||
if let value = self.userInfo[keyString] {
|
||||
if let value = self.userInfo[keyString] as? T {
|
||||
|
||||
return value as! T
|
||||
return value
|
||||
}
|
||||
let value = initialize()
|
||||
self.userInfo[keyString] = value
|
||||
|
||||
@@ -130,7 +130,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
return self
|
||||
}
|
||||
return Self.init(objectID: self.id, context: context)
|
||||
return context.objectPublisher(objectID: self.id)
|
||||
}
|
||||
|
||||
public func asReadOnly(in dataStack: DataStack) -> O? {
|
||||
@@ -184,9 +184,9 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal convenience init(objectID: O.ObjectID, context: NSManagedObjectContext) {
|
||||
internal static func createUncached(objectID: O.ObjectID, context: NSManagedObjectContext) -> ObjectPublisher<O> {
|
||||
|
||||
self.init(
|
||||
return self.init(
|
||||
objectID: objectID,
|
||||
context: context,
|
||||
initializer: ObjectSnapshot<O>.init(objectID:context:)
|
||||
@@ -195,6 +195,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
deinit {
|
||||
|
||||
self.context.objectsDidChangeObserver(remove: self)
|
||||
self.observers.removeAllObjects()
|
||||
}
|
||||
|
||||
@@ -221,30 +222,36 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
self.rawObjectWillChange = nil
|
||||
}
|
||||
self.$lazySnapshot.initialize({ initializer(objectID, context) })
|
||||
|
||||
context.objectsDidChangeObserver(for: self).addObserver(self) { [weak self] (updatedIDs, deletedIDs) in
|
||||
self.$lazySnapshot.initialize { [weak self] in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
return initializer(objectID, context)
|
||||
}
|
||||
if deletedIDs.contains(objectID) {
|
||||
context.objectsDidChangeObserver(for: self).addObserver(self) { [weak self] (updatedIDs, deletedIDs) in
|
||||
|
||||
self.object = nil
|
||||
guard let self = self else {
|
||||
|
||||
self.willChange()
|
||||
self.$lazySnapshot.reset({ nil })
|
||||
self.didChange()
|
||||
self.notifyObservers()
|
||||
}
|
||||
else if updatedIDs.contains(objectID) {
|
||||
|
||||
self.willChange()
|
||||
self.$lazySnapshot.reset({ initializer(objectID, context) })
|
||||
self.didChange()
|
||||
self.notifyObservers()
|
||||
return
|
||||
}
|
||||
if deletedIDs.contains(objectID) {
|
||||
|
||||
self.object = nil
|
||||
|
||||
self.willChange()
|
||||
self.$lazySnapshot.reset({ nil })
|
||||
self.didChange()
|
||||
self.notifyObservers()
|
||||
}
|
||||
else if updatedIDs.contains(objectID) {
|
||||
|
||||
self.willChange()
|
||||
self.$lazySnapshot.reset({ initializer(objectID, context) })
|
||||
self.didChange()
|
||||
self.notifyObservers()
|
||||
}
|
||||
}
|
||||
return initializer(objectID, context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +336,6 @@ extension ObjectPublisher {
|
||||
|
||||
// MARK: - ObjectPublisher where O: NSManagedObject
|
||||
|
||||
@available(*, unavailable, message: "KeyPaths accessed from @dynamicMemberLookup types can't generate KVC keys yet (https://bugs.swift.org/browse/SR-11351)")
|
||||
extension ObjectPublisher where O: NSManagedObject {
|
||||
|
||||
// MARK: Public
|
||||
@@ -337,11 +343,21 @@ extension ObjectPublisher where O: NSManagedObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
@available(*, unavailable, message: "KeyPaths accessed from @dynamicMemberLookup types can't generate KVC keys yet (https://bugs.swift.org/browse/SR-11351)")
|
||||
public subscript<V: AllowedObjectiveCKeyPathValue>(dynamicMember member: KeyPath<O, V>) -> V {
|
||||
|
||||
fatalError()
|
||||
// return self.snapshot[dynamicMember: member]
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public func value<V: AllowedObjectiveCKeyPathValue>(forKeyPath keyPath: KeyPath<O, V>) -> V! {
|
||||
|
||||
let key = String(keyPath: keyPath)
|
||||
return self.snapshot?.dictionaryForValues()[key] as! V?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -354,7 +370,7 @@ extension ObjectPublisher where O: CoreStoreObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V>(dynamicMember member: KeyPath<O, ValueContainer<O>.Required<V>>) -> V? {
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, ValueContainer<OBase>.Required<V>>) -> V? {
|
||||
|
||||
return self.object?[keyPath: member].value
|
||||
}
|
||||
@@ -362,7 +378,7 @@ extension ObjectPublisher where O: CoreStoreObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V>(dynamicMember member: KeyPath<O, ValueContainer<O>.Optional<V>>) -> V? {
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, ValueContainer<OBase>.Optional<V>>) -> V? {
|
||||
|
||||
return self.object?[keyPath: member].value
|
||||
}
|
||||
@@ -370,7 +386,7 @@ extension ObjectPublisher where O: CoreStoreObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V>(dynamicMember member: KeyPath<O, TransformableContainer<O>.Required<V>>) -> V? {
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, TransformableContainer<OBase>.Required<V>>) -> V? {
|
||||
|
||||
return self.object?[keyPath: member].value
|
||||
}
|
||||
@@ -378,7 +394,7 @@ extension ObjectPublisher where O: CoreStoreObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V>(dynamicMember member: KeyPath<O, TransformableContainer<O>.Optional<V>>) -> V? {
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, TransformableContainer<OBase>.Optional<V>>) -> V? {
|
||||
|
||||
return self.object?[keyPath: member].value
|
||||
}
|
||||
@@ -386,7 +402,7 @@ extension ObjectPublisher where O: CoreStoreObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<D>(dynamicMember member: KeyPath<O, RelationshipContainer<O>.ToOne<D>>) -> D? {
|
||||
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToOne<D>>) -> D? {
|
||||
|
||||
return self.object?[keyPath: member].value
|
||||
}
|
||||
@@ -394,7 +410,7 @@ extension ObjectPublisher where O: CoreStoreObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<D>(dynamicMember member: KeyPath<O, RelationshipContainer<O>.ToManyOrdered<D>>) -> [D]? {
|
||||
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToManyOrdered<D>>) -> [D]? {
|
||||
|
||||
return self.object?[keyPath: member].value
|
||||
}
|
||||
@@ -402,7 +418,7 @@ extension ObjectPublisher where O: CoreStoreObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<D>(dynamicMember member: KeyPath<O, RelationshipContainer<O>.ToManyUnordered<D>>) -> Set<D>? {
|
||||
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToManyUnordered<D>>) -> Set<D>? {
|
||||
|
||||
return self.object?[keyPath: member].value
|
||||
}
|
||||
|
||||
@@ -75,6 +75,29 @@ extension CoreStoreObject: ObjectRepresentation {}
|
||||
|
||||
extension DynamicObject where Self: ObjectRepresentation {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
An `ObjectPublisher` wrapper for the exact same object
|
||||
*/
|
||||
public func asPublisher() -> ObjectPublisher<Self>? {
|
||||
|
||||
return self.cs_toRaw()
|
||||
.managedObjectContext
|
||||
.map({ $0.objectPublisher(objectID: self.cs_id()) })
|
||||
}
|
||||
|
||||
/**
|
||||
A thread-safe `struct` that is a full-copy of the object's properties
|
||||
*/
|
||||
public func asSnapshot() -> ObjectSnapshot<Self>? {
|
||||
|
||||
return self.cs_toRaw()
|
||||
.managedObjectContext
|
||||
.flatMap({ ObjectSnapshot<Self>(objectID: self.cs_id(), context: $0) })
|
||||
}
|
||||
|
||||
|
||||
// MARK: ObjectRepresentation
|
||||
|
||||
public func objectID() -> Self.ObjectID {
|
||||
@@ -85,7 +108,7 @@ extension DynamicObject where Self: ObjectRepresentation {
|
||||
public func asPublisher(in dataStack: DataStack) -> ObjectPublisher<Self> {
|
||||
|
||||
let context = dataStack.unsafeContext()
|
||||
return ObjectPublisher<Self>(objectID: self.cs_id(), context: context)
|
||||
return context.objectPublisher(objectID: self.cs_id())
|
||||
}
|
||||
|
||||
public func asReadOnly(in dataStack: DataStack) -> Self? {
|
||||
|
||||
@@ -41,6 +41,14 @@ import AppKit
|
||||
*/
|
||||
@dynamicMemberLookup
|
||||
public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public func dictionaryForValues() -> [String: Any] {
|
||||
|
||||
return self.values
|
||||
}
|
||||
|
||||
|
||||
// MARK: ObjectRepresentation
|
||||
|
||||
@@ -54,7 +62,7 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable {
|
||||
public func asPublisher(in dataStack: DataStack) -> ObjectPublisher<O> {
|
||||
|
||||
let context = dataStack.unsafeContext()
|
||||
return ObjectPublisher<O>(objectID: self.id, context: context)
|
||||
return context.objectPublisher(objectID: self.id)
|
||||
}
|
||||
|
||||
public func asReadOnly(in dataStack: DataStack) -> O? {
|
||||
@@ -107,14 +115,20 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable {
|
||||
return nil
|
||||
}
|
||||
self.id = objectID
|
||||
self.context = context
|
||||
self.values = values
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate var values: [String: Any]
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let id: O.ObjectID
|
||||
private var values: [String: Any]
|
||||
private let context: NSManagedObjectContext
|
||||
|
||||
private var valuesRef: NSDictionary {
|
||||
|
||||
@@ -125,17 +139,35 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable {
|
||||
|
||||
// MARK: - ObjectSnapshot where O: NSManagedObject
|
||||
|
||||
@available(*, unavailable, message: "KeyPaths accessed from @dynamicMemberLookup types can't generate KVC keys yet (https://bugs.swift.org/browse/SR-11351)")
|
||||
extension ObjectSnapshot where O: NSManagedObject {
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
@available(*, unavailable, message: "KeyPaths accessed from @dynamicMemberLookup types can't generate KVC keys yet (https://bugs.swift.org/browse/SR-11351)")
|
||||
public subscript<V: AllowedObjectiveCKeyPathValue>(dynamicMember member: KeyPath<O, V>) -> V {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
return self.values[key] as! V
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public func value<V: AllowedObjectiveCKeyPathValue>(forKeyPath keyPath: KeyPath<O, V>) -> V! {
|
||||
|
||||
let key = String(keyPath: keyPath)
|
||||
return self.values[key] as! V?
|
||||
}
|
||||
|
||||
/**
|
||||
Mutates the value for the property identified by a given key.
|
||||
*/
|
||||
public mutating func setValue<V: AllowedObjectiveCKeyPathValue>(_ value: V!, forKeyPath keyPath: KeyPath<O, V>) {
|
||||
|
||||
let key = String(keyPath: keyPath)
|
||||
self.values[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,7 +178,7 @@ extension ObjectSnapshot where O: CoreStoreObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V>(dynamicMember member: KeyPath<O, ValueContainer<O>.Required<V>>) -> V {
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, ValueContainer<OBase>.Required<V>>) -> V {
|
||||
|
||||
get {
|
||||
|
||||
@@ -163,12 +195,12 @@ extension ObjectSnapshot where O: CoreStoreObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V>(dynamicMember member: KeyPath<O, ValueContainer<O>.Optional<V>>) -> V? {
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, ValueContainer<OBase>.Optional<V>>) -> V? {
|
||||
|
||||
get {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
return self.values[key] as! V?
|
||||
return self.values[key] as? V
|
||||
}
|
||||
set {
|
||||
|
||||
@@ -180,7 +212,7 @@ extension ObjectSnapshot where O: CoreStoreObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V>(dynamicMember member: KeyPath<O, TransformableContainer<O>.Required<V>>) -> V {
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, TransformableContainer<OBase>.Required<V>>) -> V {
|
||||
|
||||
get {
|
||||
|
||||
@@ -197,12 +229,12 @@ extension ObjectSnapshot where O: CoreStoreObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<V>(dynamicMember member: KeyPath<O, TransformableContainer<O>.Optional<V>>) -> V? {
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, TransformableContainer<OBase>.Optional<V>>) -> V? {
|
||||
|
||||
get {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
return self.values[key] as! V?
|
||||
return self.values[key] as? V
|
||||
}
|
||||
set {
|
||||
|
||||
@@ -214,51 +246,59 @@ extension ObjectSnapshot where O: CoreStoreObject {
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<D>(dynamicMember member: KeyPath<O, RelationshipContainer<O>.ToOne<D>>) -> D.ObjectID? {
|
||||
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToOne<D>>) -> ObjectPublisher<D>? {
|
||||
|
||||
get {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
return self.values[key] as! D.ObjectID?
|
||||
guard let id = self.values[key] as? D.ObjectID else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return self.context.objectPublisher(objectID: id)
|
||||
}
|
||||
set {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
self.values[key] = newValue
|
||||
self.values[key] = newValue?.objectID()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<D>(dynamicMember member: KeyPath<O, RelationshipContainer<O>.ToManyOrdered<D>>) -> [D.ObjectID] {
|
||||
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToManyOrdered<D>>) -> [ObjectPublisher<D>] {
|
||||
|
||||
get {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
return self.values[key] as! [D.ObjectID]
|
||||
let context = self.context
|
||||
let ids = self.values[key] as! [D.ObjectID]
|
||||
return ids.map(context.objectPublisher(objectID:))
|
||||
}
|
||||
set {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
self.values[key] = newValue
|
||||
self.values[key] = newValue.map({ $0.objectID() })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<D>(dynamicMember member: KeyPath<O, RelationshipContainer<O>.ToManyUnordered<D>>) -> Set<D.ObjectID> {
|
||||
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToManyUnordered<D>>) -> Set<ObjectPublisher<D>> {
|
||||
|
||||
get {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
return self.values[key] as! Set<D.ObjectID>
|
||||
let context = self.context
|
||||
let ids = self.values[key] as! Set<D.ObjectID>
|
||||
return Set(ids.map(context.objectPublisher(objectID:)))
|
||||
}
|
||||
set {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
self.values[key] = newValue
|
||||
self.values[key] = Set(newValue.map({ $0.objectID() }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,9 +313,9 @@ public enum RelationshipContainer<O: CoreStoreObject> {
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any {
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value?.objectID() as Any
|
||||
return self.value?.objectID()
|
||||
}
|
||||
|
||||
|
||||
@@ -609,9 +609,9 @@ public enum RelationshipContainer<O: CoreStoreObject> {
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any {
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value.map({ $0.objectID() }) as Any
|
||||
return self.value.map({ $0.objectID() })
|
||||
}
|
||||
|
||||
|
||||
@@ -910,9 +910,9 @@ public enum RelationshipContainer<O: CoreStoreObject> {
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any {
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return Set(self.value.map({ $0.objectID() })) as Any
|
||||
return Set(self.value.map({ $0.objectID() }))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -41,5 +41,5 @@ internal protocol RelationshipProtocol: PropertyProtocol {
|
||||
var renamingIdentifier: () -> String? { get }
|
||||
var minCount: Int { get }
|
||||
var maxCount: Int { get }
|
||||
var valueForSnapshot: Any { get }
|
||||
var valueForSnapshot: Any? { get }
|
||||
}
|
||||
|
||||
@@ -272,9 +272,9 @@ public enum TransformableContainer<O: CoreStoreObject> {
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any {
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value as Any
|
||||
return self.value
|
||||
}
|
||||
|
||||
|
||||
@@ -494,9 +494,9 @@ public enum TransformableContainer<O: CoreStoreObject> {
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any {
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value as Any
|
||||
return self.value
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -267,9 +267,9 @@ public enum ValueContainer<O: CoreStoreObject> {
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any {
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value as Any
|
||||
return self.value
|
||||
}
|
||||
|
||||
|
||||
@@ -489,9 +489,9 @@ public enum ValueContainer<O: CoreStoreObject> {
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any {
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value as Any
|
||||
return self.value
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
//
|
||||
// DiffableDataSource.CollectionView-AppKit.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(AppKit) && os(macOS)
|
||||
|
||||
import AppKit
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
extension DiffableDataSource {
|
||||
|
||||
// MARK: - CollectionView
|
||||
|
||||
/**
|
||||
The `DiffableDataSource.CollectionView` serves as a `NSCollectionViewDataSource` that handles `ListPublisher` snapshots for a `NSCollectionView`. Subclasses of `DiffableDataSource.CollectionView` may override some `NSCollectionViewDataSource` methods as needed.
|
||||
The `DiffableDataSource.CollectionView` instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.CollectionView<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
itemProvider: { (collectionView, indexPath, person) in
|
||||
let item = collectionView.makeItem(withIdentifier: .collectionViewItem, for: indexPath) as! PersonItem
|
||||
item.setPerson(person)
|
||||
return item
|
||||
}
|
||||
)
|
||||
```
|
||||
The dataSource can then apply changes from a `ListPublisher` as shown:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
`DiffableDataSource.CollectionView` fully handles the reload animations.
|
||||
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
|
||||
*/
|
||||
open class CollectionView<O: DynamicObject>: NSObject, NSCollectionViewDataSource {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
The object type represented by this dataSource
|
||||
*/
|
||||
public typealias ObjectType = O
|
||||
|
||||
/**
|
||||
Initializes the `DiffableDataSource.CollectionView`. This instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
|
||||
```
|
||||
self.dataSource = DiffableDataSource.CollectionView<Person>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: CoreStoreDefaults.dataStack,
|
||||
itemProvider: { (collectionView, indexPath, person) in
|
||||
let item = collectionView.makeItem(withIdentifier: .collectionViewItem, for: indexPath) as! PersonItem
|
||||
item.setPerson(person)
|
||||
return item
|
||||
}
|
||||
)
|
||||
```
|
||||
- parameter collectionView: the `NSCollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionView`.
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
|
||||
*/
|
||||
@nonobjc
|
||||
public init(collectionView: NSCollectionView, dataStack: DataStack, itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?, supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }) {
|
||||
|
||||
self.collectionView = collectionView
|
||||
self.itemProvider = itemProvider
|
||||
self.supplementaryViewProvider = supplementaryViewProvider
|
||||
self.dataStack = dataStack
|
||||
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
|
||||
|
||||
super.init()
|
||||
|
||||
collectionView.dataSource = self
|
||||
}
|
||||
|
||||
/**
|
||||
Reloads the `NSCollectionView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
|
||||
```
|
||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
||||
self?.dataSource?.apply(
|
||||
listPublisher.snapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
|
||||
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
|
||||
*/
|
||||
public func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true) {
|
||||
|
||||
let diffableSnapshot = snapshot.diffableSnapshot
|
||||
self.dispatcher.apply(
|
||||
diffableSnapshot as! Internals.DiffableDataSourceSnapshot,
|
||||
view: self.collectionView,
|
||||
animatingDifferences: animatingDifferences,
|
||||
performUpdates: { collectionView, changeset, setSections in
|
||||
|
||||
collectionView.reload(
|
||||
using: changeset,
|
||||
setData: setSections
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
|
||||
- parameter indexPath: the `IndexPath` to search for
|
||||
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
|
||||
*/
|
||||
@nonobjc
|
||||
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
|
||||
|
||||
return self.dispatcher.itemIdentifier(for: indexPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
|
||||
- parameter itemID: the object identifier to search for
|
||||
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
|
||||
*/
|
||||
@nonobjc
|
||||
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
|
||||
|
||||
return self.dispatcher.indexPath(for: itemID)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSCollectionViewDataSource
|
||||
|
||||
@objc
|
||||
public dynamic func numberOfSections(in collectionView: NSCollectionView) -> Int {
|
||||
|
||||
return self.dispatcher.numberOfSections()
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
|
||||
return self.dispatcher.numberOfItems(inSection: section)
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
|
||||
|
||||
guard let objectID = self.dispatcher.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 item = self.itemProvider(collectionView, indexPath, object) else {
|
||||
|
||||
Internals.abort("\(Internals.typeName(NSCollectionViewDataSource.self)) returned a `nil` item for \(Internals.typeName(IndexPath.self)) \(indexPath)")
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
|
||||
|
||||
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
|
||||
|
||||
return NSView()
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private weak var collectionView: NSCollectionView?
|
||||
|
||||
private let dataStack: DataStack
|
||||
private let itemProvider: (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?
|
||||
private let supplementaryViewProvider: (NSCollectionView, String, IndexPath) -> NSView?
|
||||
private let dispatcher: Internals.DiffableDataUIDispatcher<O>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSCollectionView
|
||||
|
||||
extension NSCollectionView {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
@nonobjc
|
||||
fileprivate func reload<C, O>(
|
||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void
|
||||
) {
|
||||
|
||||
if case .none = window, let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
for changeset in stagedChangeset {
|
||||
|
||||
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
self.animator().performBatchUpdates(
|
||||
{
|
||||
setData(changeset.data)
|
||||
|
||||
if !changeset.sectionDeleted.isEmpty {
|
||||
|
||||
self.deleteSections(IndexSet(changeset.sectionDeleted))
|
||||
}
|
||||
if !changeset.sectionInserted.isEmpty {
|
||||
|
||||
self.insertSections(IndexSet(changeset.sectionInserted))
|
||||
}
|
||||
if !changeset.sectionUpdated.isEmpty {
|
||||
|
||||
self.reloadSections(IndexSet(changeset.sectionUpdated))
|
||||
}
|
||||
for (source, target) in changeset.sectionMoved {
|
||||
|
||||
self.moveSection(source, toSection: target)
|
||||
}
|
||||
if !changeset.elementDeleted.isEmpty {
|
||||
|
||||
self.deleteItems(
|
||||
at: Set(changeset.elementDeleted.map { IndexPath(item: $0.element, section: $0.section) })
|
||||
)
|
||||
}
|
||||
if !changeset.elementInserted.isEmpty {
|
||||
|
||||
self.insertItems(
|
||||
at: Set(changeset.elementInserted.map { IndexPath(item: $0.element, section: $0.section) })
|
||||
)
|
||||
}
|
||||
if !changeset.elementUpdated.isEmpty {
|
||||
|
||||
self.reloadItems(
|
||||
at: Set(changeset.elementUpdated.map { IndexPath(item: $0.element, section: $0.section) })
|
||||
)
|
||||
}
|
||||
for (source, target) in changeset.elementMoved {
|
||||
|
||||
self.moveItem(
|
||||
at: IndexPath(item: source.element, section: source.section),
|
||||
to: IndexPath(item: target.element, section: target.section)
|
||||
)
|
||||
}
|
||||
},
|
||||
completionHandler: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user