diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 124a5b8..828e458 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -141,6 +141,13 @@ B51260941E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */; }; B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */; }; B51260961E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */; }; + B514EF0E23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; }; + B514EF0F23A8DB180093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; }; + B514EF1023A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; }; + B514EF1123A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; }; + B514EF1223A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; }; + B514EF1323A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; }; + B514EF1423A8DB1E0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; }; B51B5C2B22D43931009FA3BA /* String+KeyPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51B5C2A22D43931009FA3BA /* String+KeyPaths.swift */; }; B51B5C2D22D43E38009FA3BA /* KeyPath+KeyPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51B5C2C22D43E38009FA3BA /* KeyPath+KeyPaths.swift */; }; B51FE5AB1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */; }; @@ -668,6 +675,7 @@ B5D33A041E96012400C880DE /* Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D33A001E96012400C880DE /* Relationship.swift */; }; B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; }; B5D39A0219FD00C9000E91BB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0119FD00C9000E91BB /* Foundation.framework */; }; + B5D4A6B723A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; }; B5D7A5B61CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; }; B5D7A5B81CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; }; B5D7A5B91CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; }; @@ -906,6 +914,7 @@ B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStoreObject+Convenience.swift"; sourceTree = ""; }; B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSEntityDescription+DynamicModel.swift"; sourceTree = ""; }; B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Internals.EntityIdentifier.swift; sourceTree = ""; }; + B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.Target.swift; sourceTree = ""; }; B51B5C2A22D43931009FA3BA /* String+KeyPaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+KeyPaths.swift"; sourceTree = ""; }; B51B5C2C22D43E38009FA3BA /* KeyPath+KeyPaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyPath+KeyPaths.swift"; sourceTree = ""; }; B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+CustomDebugStringConvertible.swift"; sourceTree = ""; }; @@ -1042,6 +1051,7 @@ B5D33A001E96012400C880DE /* Relationship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Relationship.swift; sourceTree = ""; }; B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; 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 = ""; }; B5D7A5B51CA3BF8F005C752B /* CSInto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSInto.swift; sourceTree = ""; }; B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataStack+DataSources.swift"; sourceTree = ""; }; B5D8CA7A2346EC550055D7D1 /* ListPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListPublisherTests.swift; sourceTree = ""; }; @@ -1607,6 +1617,8 @@ B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */, B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */, B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */, + B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */, + B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */, B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */, B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */, B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionView-AppKit.swift */, @@ -2089,6 +2101,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */, B5DE5230230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */, B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */, B5ECDBF91CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */, @@ -2126,7 +2139,6 @@ B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */, B5DE522B230BD7CC00A22534 /* Internals.swift in Sources */, B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */, - B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */, B5ECDC1D1CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */, B5C976E31C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */, B53FBA121CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */, @@ -2224,6 +2236,7 @@ B5BF7FBC234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */, B5215CA41FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */, B5D33A011E96012400C880DE /* Relationship.swift in Sources */, + B514EF0E23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift in Sources */, B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */, B56923C91EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */, B56923E41EB827F5007C4DC9 /* CustomSchemaMappingProvider.swift in Sources */, @@ -2240,6 +2253,7 @@ B512607F1E97A18000402229 /* CoreStoreObject+Convenience.swift in Sources */, B5E84EF81AFF846E0064E85B /* CoreStore+Transaction.swift in Sources */, B5E84F301AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift in Sources */, + B5D4A6B723A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift in Sources */, B5831B7A1F34ACBA00A9F647 /* Transformable.swift in Sources */, B5BF7FB2234C97910070E741 /* DiffableDataSource.swift in Sources */, B546F9691C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, @@ -2387,6 +2401,7 @@ B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, B5DBE2D31C991B3E00B5CEFA /* CSDataStack.swift in Sources */, 82BA18B41C4BBD3900A0916E /* BaseDataTransaction+Importing.swift in Sources */, + B514EF1223A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */, B53FBA1A1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */, 82BA18CA1C4BBD5900A0916E /* MigrationResult.swift in Sources */, B5519A5A1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */, @@ -2435,6 +2450,7 @@ B5215CAA1FA4810300139E3A /* QueryChainBuilder.swift in Sources */, B50E175823517DE4004F033C /* Differentiable.swift in Sources */, 82BA18BA1C4BBD4A00A0916E /* Select.swift in Sources */, + B514EF0F23A8DB180093DBA4 /* DiffableDataSource.Target.swift in Sources */, B52F744B1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */, B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */, B5E2222C1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */, @@ -2538,9 +2554,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B5BF7FB5234C97910070E741 /* DiffableDataSource.swift in Sources */, B5220E1E1D13080D009BC71E /* CSListMonitor.swift in Sources */, - B5DBE2D01C9914A900B5CEFA /* CSCoreStore.swift in Sources */, + B5AA37F4235C28EE00FFD4B9 /* DiffableDataSource.CollectionView-AppKit.swift in Sources */, + B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */, B5635D172356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */, + B5DBE2D01C9914A900B5CEFA /* CSCoreStore.swift in Sources */, B5CA2B0B1F7E5ACA004B1936 /* WhereClauseType.swift in Sources */, B56923F81EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */, B52DD1BE1BE1F94300949AFE /* Progress+Convenience.swift in Sources */, @@ -2581,7 +2600,6 @@ B50564D62350CC3100482308 /* PropertyProtocol.swift in Sources */, B5831B781F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */, B52DD1981BE1F92500949AFE /* CoreStore+Setup.swift in Sources */, - B5BF7FB5234C97910070E741 /* DiffableDataSource.swift in Sources */, B5D339F41E94AF5800C880DE /* CoreStoreStrings.swift in Sources */, B5220E241D13085E009BC71E /* NSFetchedResultsController+Convenience.swift in Sources */, B559CD471CAA8B6300E4D58B /* CSSetupResult.swift in Sources */, @@ -2609,6 +2627,7 @@ B53FBA1C1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */, B5F8496F234898240029D57B /* ListSnapshot.swift in Sources */, B5831F452212700500D8604C /* Where.Expression.swift in Sources */, + B514EF1423A8DB1E0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */, B51260961E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */, B5ECDBE31CA6BB2B00C7F112 /* CSBaseDataTransaction+Querying.swift in Sources */, B5ECDC031CA80CBA00C7F112 /* CSWhere.swift in Sources */, @@ -2657,6 +2676,7 @@ B5220E1A1D130791009BC71E /* Internals.CoreStoreFetchedResultsController.swift in Sources */, B5215CAC1FA4810300139E3A /* QueryChainBuilder.swift in Sources */, B53FBA0F1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */, + B514EF1123A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */, B50E175A23517DE4004F033C /* Differentiable.swift in Sources */, B52F744D1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */, B52DD19A1BE1F92800949AFE /* CoreStore+Logging.swift in Sources */, @@ -2698,7 +2718,6 @@ B52F74401E9B8724005F3DAC /* DynamicSchema.swift in Sources */, 18166886232B9ED20097C275 /* KeyPath+KeyPaths.swift in Sources */, B5E8A72321C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */, - B5AA37F4235C28EE00FFD4B9 /* DiffableDataSource.CollectionView-AppKit.swift in Sources */, B50E175F2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */, B5474D182227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */, B501322E2346A9B100FC238B /* ListPublisher.swift in Sources */, @@ -2716,7 +2735,6 @@ B5D339EA1E9493A500C880DE /* Entity.swift in Sources */, B5BF7FC9234D7E460070E741 /* ObjectSnapshot.swift in Sources */, B52DD1AA1BE1F93500949AFE /* TypeErasedClauses.swift in Sources */, - B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */, B53FBA021CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, B51FE5AF1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, ); @@ -2835,6 +2853,7 @@ B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */, B53FBA011CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, B5DBE2D41C991B3E00B5CEFA /* CSDataStack.swift in Sources */, + B514EF1323A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */, B50392FA1C47963F009900CA /* NSManagedObject+Transaction.swift in Sources */, B53FBA1B1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */, B5519A5B1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */, @@ -2883,6 +2902,7 @@ B56321A21BD65216006C9394 /* ListObserver.swift in Sources */, B50E175923517DE4004F033C /* Differentiable.swift in Sources */, B5215CAB1FA4810300139E3A /* QueryChainBuilder.swift in Sources */, + B514EF1023A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */, B563218A1BD65216006C9394 /* SynchronousDataTransaction.swift in Sources */, B52F744C1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */, B5AEFAB71C9962AE00AD137F /* CoreStoreBridge.swift in Sources */, diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/CollectionViewDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/CollectionViewDemoViewController.swift index abc664f..4fe5637 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/CollectionViewDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/CollectionViewDemoViewController.swift @@ -51,7 +51,7 @@ final class CollectionViewDemoViewController: UICollectionViewController { ] self.filterBarButton = filterBarButton - self.dataSource = DiffableDataSource.CollectionView( + self.dataSource = DiffableDataSource.CollectionViewAdapter( 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? + private var dataSource: DiffableDataSource.CollectionViewAdapter? deinit { diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift index 3df9d0e..f1b1907 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift @@ -16,7 +16,7 @@ final class ListObserverDemoViewController: UITableViewController { // MARK: - EditableDataSource - final class EditableDataSource: DiffableDataSource.TableView { + final class EditableDataSource: DiffableDataSource.TableViewAdapter { 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? + private var dataSource: DiffableDataSource.TableViewAdapter? deinit { diff --git a/README.md b/README.md index 04de883..e679af5 100644 --- a/README.md +++ b/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( +self.dataSource = DiffableDataSource.CollectionViewAdapter( collectionView: self.collectionView, dataStack: CoreStoreDefaults.dataStack, cellProvider: { (collectionView, indexPath, person) in diff --git a/Sources/DiffableDataSource.BaseAdapter.swift b/Sources/DiffableDataSource.BaseAdapter.swift new file mode 100644 index 0000000..3886031 --- /dev/null +++ b/Sources/DiffableDataSource.BaseAdapter.swift @@ -0,0 +1,197 @@ +// +// DiffableDataSource.BaseAdapter.swift +// CoreStore +// +// Copyright © 2018 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation +import CoreData + + +// MARK: - DiffableDataSource + +extension DiffableDataSource { + + // MARK: - BaseAdapter + + /** + The `DiffableDataSource.BaseAdapter` serves as a superclass for consumers of `ListPublisher` and `ListSnapshot` diffable data. + ``` + self.dataSource = DiffableDataSource.TableViewAdapter( + 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: 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( + 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(dataStack: dataStack) + } + + /** + Reloads the `UITableView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`: + ``` + listPublisher.addObserver(self) { [weak self] (listPublisher) in + self?.dataSource?.apply( + listPublisher.snapshot, + animatingDifferences: true + ) + } + ``` + If the `defaultRowAnimation` is configured to, animations are also applied accordingly. + + - parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`. + - parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`. + */ + open func apply(_ snapshot: ListSnapshot, 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 + } +} diff --git a/Sources/DiffableDataSource.CollectionView-AppKit.swift b/Sources/DiffableDataSource.CollectionView-AppKit.swift index 4baafe2..25a65df 100644 --- a/Sources/DiffableDataSource.CollectionView-AppKit.swift +++ b/Sources/DiffableDataSource.CollectionView-AppKit.swift @@ -1,5 +1,5 @@ // -// DiffableDataSource.CollectionView-AppKit.swift +// DiffableDataSource.CollectionViewAdapter-AppKit.swift // CoreStore // // Copyright © 2018 John Rommel Estropia @@ -36,10 +36,10 @@ extension DiffableDataSource { // MARK: - CollectionView /** - The `DiffableDataSource.CollectionView` serves as a `NSCollectionViewDataSource` that handles `ListPublisher` snapshots for a `NSCollectionView`. Subclasses of `DiffableDataSource.CollectionView` may override some `NSCollectionViewDataSource` methods as needed. - The `DiffableDataSource.CollectionView` instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle. + The `DiffableDataSource.CollectionViewAdapter` serves as a `NSCollectionViewDataSource` that handles `ListPublisher` snapshots for a `NSCollectionView`. Subclasses of `DiffableDataSource.CollectionViewAdapter` may override some `NSCollectionViewDataSource` methods as needed. + The `DiffableDataSource.CollectionViewAdapter` instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle. ``` - self.dataSource = DiffableDataSource.CollectionView( + self.dataSource = DiffableDataSource.CollectionViewAdapter( collectionView: self.collectionView, dataStack: CoreStoreDefaults.dataStack, itemProvider: { (collectionView, indexPath, person) in @@ -58,22 +58,17 @@ extension DiffableDataSource { ) } ``` - `DiffableDataSource.CollectionView` fully handles the reload animations. + `DiffableDataSource.CollectionViewAdapter` fully handles the reload animations. - SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources */ - open class CollectionView: NSObject, NSCollectionViewDataSource { + open class CollectionViewAdapter: BaseAdapter>, NSCollectionViewDataSource { // MARK: Public /** - The object type represented by this dataSource - */ - public typealias ObjectType = O - - /** - Initializes the `DiffableDataSource.CollectionView`. This instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle. + Initializes the `DiffableDataSource.CollectionViewAdapter`. This instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle. ``` - self.dataSource = DiffableDataSource.CollectionView( + self.dataSource = DiffableDataSource.CollectionViewAdapter( collectionView: self.collectionView, dataStack: CoreStoreDefaults.dataStack, itemProvider: { (collectionView, indexPath, person) in @@ -83,78 +78,20 @@ extension DiffableDataSource { } ) ``` - - parameter collectionView: the `NSCollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionView`. + - parameter collectionView: the `NSCollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionViewAdapter`. - parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from - parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object */ @nonobjc public init(collectionView: NSCollectionView, dataStack: DataStack, itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?, supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }) { - self.collectionView = collectionView self.itemProvider = itemProvider self.supplementaryViewProvider = supplementaryViewProvider - self.dataStack = dataStack - self.dispatcher = Internals.DiffableDataUIDispatcher(dataStack: dataStack) - super.init() + super.init(target: .init(collectionView), dataStack: dataStack) collectionView.dataSource = self } - - /** - Reloads the `NSCollectionView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`: - ``` - listPublisher.addObserver(self) { [weak self] (listPublisher) in - self?.dataSource?.apply( - listPublisher.snapshot, - animatingDifferences: true - ) - } - ``` - - - parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`. - - parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`. - */ - public func apply(_ snapshot: ListSnapshot, animatingDifferences: Bool = true) { - - let diffableSnapshot = snapshot.diffableSnapshot - self.dispatcher.apply( - diffableSnapshot as! Internals.DiffableDataSourceSnapshot, - view: self.collectionView, - animatingDifferences: animatingDifferences, - performUpdates: { collectionView, changeset, setSections in - - collectionView.reload( - using: changeset, - setData: setSections - ) - } - ) - } - - /** - Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found - - - parameter indexPath: the `IndexPath` to search for - - returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found - */ - @nonobjc - public func itemID(for indexPath: IndexPath) -> O.ObjectID? { - - return self.dispatcher.itemIdentifier(for: indexPath) - } - - /** - Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found - - - parameter itemID: the object identifier to search for - - returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found - */ - @nonobjc - public func indexPath(for itemID: O.ObjectID) -> IndexPath? { - - return self.dispatcher.indexPath(for: itemID) - } // MARK: - NSCollectionViewDataSource @@ -162,19 +99,19 @@ extension DiffableDataSource { @objc public dynamic func numberOfSections(in collectionView: NSCollectionView) -> Int { - return self.dispatcher.numberOfSections() + return self.numberOfSections() } @objc public dynamic func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { - return self.dispatcher.numberOfItems(inSection: section) + return self.numberOfItems(inSection: section) ?? 0 } @objc open dynamic func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { - guard let objectID = self.dispatcher.itemIdentifier(for: indexPath) else { + guard let objectID = self.itemID(for: indexPath) else { Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list") } @@ -202,94 +139,93 @@ extension DiffableDataSource { // MARK: Private - private weak var collectionView: NSCollectionView? - - private let dataStack: DataStack private let itemProvider: (NSCollectionView, IndexPath, O) -> NSCollectionViewItem? private let supplementaryViewProvider: (NSCollectionView, String, IndexPath) -> NSView? - private let dispatcher: Internals.DiffableDataUIDispatcher + } + + + // MARK: - DefaultCollectionViewTarget + + public struct DefaultCollectionViewTarget: Target { + + // MARK: Public + + public typealias Base = T + + public internal(set) weak var base: Base? + + public init(_ base: Base) { + + self.base = base + } + + + // MARK: DiffableDataSource.Target: + + public var shouldSuspendBatchUpdates: Bool { + + return self.base?.window == nil + } + + public func deleteSections(at indices: IndexSet, animated: Bool) { + + self.base?.deleteSections(indices) + } + + public func insertSections(at indices: IndexSet, animated: Bool) { + + self.base?.insertSections(indices) + } + + public func reloadSections(at indices: IndexSet, animated: Bool) { + + self.base?.reloadSections(indices) + } + + public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) { + + self.base?.moveSection(index, toSection: newIndex) + } + + public func deleteItems(at indexPaths: [IndexPath], animated: Bool) { + + self.base?.deleteItems(at: Set(indexPaths)) + } + + public func insertItems(at indexPaths: [IndexPath], animated: Bool) { + + self.base?.insertItems(at: Set(indexPaths)) + } + + public func reloadItems(at indexPaths: [IndexPath], animated: Bool) { + + self.base?.reloadItems(at: Set(indexPaths)) + } + + public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) { + + self.base?.moveItem(at: indexPath, to: newIndexPath) + } + + public func performBatchUpdates(updates: () -> Void, animated: Bool) { + + self.base?.animator().performBatchUpdates(updates, completionHandler: nil) + } + + public func reloadData() { + + self.base?.reloadData() + } } } -// MARK: - NSCollectionView +// MARK: - Deprecated -extension NSCollectionView { +extension DiffableDataSource { - // MARK: FilePrivate - - // Implementation based on https://github.com/ra1028/DiffableDataSources - @nonobjc - fileprivate func reload( - using stagedChangeset: Internals.DiffableDataUIDispatcher.StagedChangeset, - interrupt: ((Internals.DiffableDataUIDispatcher.Changeset) -> Bool)? = nil, - setData: (C) -> Void - ) { - - if case .none = window, let data = stagedChangeset.last?.data { - - setData(data) - self.reloadData() - return - } - for changeset in stagedChangeset { - - if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data { - - setData(data) - self.reloadData() - return - } - self.animator().performBatchUpdates( - { - setData(changeset.data) - - if !changeset.sectionDeleted.isEmpty { - - self.deleteSections(IndexSet(changeset.sectionDeleted)) - } - if !changeset.sectionInserted.isEmpty { - - self.insertSections(IndexSet(changeset.sectionInserted)) - } - if !changeset.sectionUpdated.isEmpty { - - self.reloadSections(IndexSet(changeset.sectionUpdated)) - } - for (source, target) in changeset.sectionMoved { - - self.moveSection(source, toSection: target) - } - if !changeset.elementDeleted.isEmpty { - - self.deleteItems( - at: Set(changeset.elementDeleted.map { IndexPath(item: $0.element, section: $0.section) }) - ) - } - if !changeset.elementInserted.isEmpty { - - self.insertItems( - at: Set(changeset.elementInserted.map { IndexPath(item: $0.element, section: $0.section) }) - ) - } - if !changeset.elementUpdated.isEmpty { - - self.reloadItems( - at: Set(changeset.elementUpdated.map { IndexPath(item: $0.element, section: $0.section) }) - ) - } - for (source, target) in changeset.elementMoved { - - self.moveItem( - at: IndexPath(item: source.element, section: source.section), - to: IndexPath(item: target.element, section: target.section) - ) - } - }, - completionHandler: nil - ) - } - } + @available(*, deprecated, renamed: "CollectionViewAdapter") + public typealias CollectionView = CollectionViewAdapter } diff --git a/Sources/DiffableDataSource.CollectionView-UIKit.swift b/Sources/DiffableDataSource.CollectionView-UIKit.swift index 04943f0..b1573a9 100644 --- a/Sources/DiffableDataSource.CollectionView-UIKit.swift +++ b/Sources/DiffableDataSource.CollectionView-UIKit.swift @@ -1,5 +1,5 @@ // -// DiffableDataSource.CollectionView-UIKit.swift +// DiffableDataSource.CollectionViewAdapter-UIKit.swift // CoreStore // // Copyright © 2018 John Rommel Estropia @@ -36,10 +36,10 @@ extension DiffableDataSource { // MARK: - CollectionView /** - The `DiffableDataSource.CollectionView` serves as a `UICollectionViewDataSource` that handles `ListPublisher` snapshots for a `UICollectionView`. Subclasses of `DiffableDataSource.CollectionView` may override some `UICollectionViewDataSource` methods as needed. - The `DiffableDataSource.CollectionView` instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle. + The `DiffableDataSource.CollectionViewAdapter` serves as a `UICollectionViewDataSource` that handles `ListPublisher` snapshots for a `UICollectionView`. Subclasses of `DiffableDataSource.CollectionViewAdapter` may override some `UICollectionViewDataSource` methods as needed. + The `DiffableDataSource.CollectionViewAdapter` instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle. ``` - self.dataSource = DiffableDataSource.CollectionView( + self.dataSource = DiffableDataSource.CollectionViewAdapter( collectionView: self.collectionView, dataStack: CoreStoreDefaults.dataStack, cellProvider: { (collectionView, indexPath, person) in @@ -58,22 +58,17 @@ extension DiffableDataSource { ) } ``` - `DiffableDataSource.CollectionView` fully handles the reload animations. + `DiffableDataSource.CollectionViewAdapter` fully handles the reload animations. - SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources */ - open class CollectionView: NSObject, UICollectionViewDataSource { + open class CollectionViewAdapter: BaseAdapter>, UICollectionViewDataSource { // MARK: Public /** - The object type represented by this dataSource - */ - public typealias ObjectType = O - - /** - Initializes the `DiffableDataSource.CollectionView`. This instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle. + Initializes the `DiffableDataSource.CollectionViewAdapter`. This instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle. ``` - self.dataSource = DiffableDataSource.CollectionView( + self.dataSource = DiffableDataSource.CollectionViewAdapter( collectionView: self.collectionView, dataStack: CoreStoreDefaults.dataStack, cellProvider: { (collectionView, indexPath, person) in @@ -83,79 +78,20 @@ extension DiffableDataSource { } ) ``` - - parameter collectionView: the `UICollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionView`. + - parameter collectionView: the `UICollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionViewAdapter`. - parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from - parameter cellProvider: a closure that configures and returns the `UICollectionViewCell` for the object - parameter supplementaryViewProvider: an optional closure for providing `UICollectionReusableView` supplementary views. If not set, defaults to returning `nil` */ - @nonobjc public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) { - self.collectionView = collectionView self.cellProvider = cellProvider self.supplementaryViewProvider = supplementaryViewProvider - self.dataStack = dataStack - self.dispatcher = Internals.DiffableDataUIDispatcher(dataStack: dataStack) - super.init() + super.init(target: .init(collectionView), dataStack: dataStack) collectionView.dataSource = self } - - /** - Reloads the `UICollectionView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`: - ``` - listPublisher.addObserver(self) { [weak self] (listPublisher) in - self?.dataSource?.apply( - listPublisher.snapshot, - animatingDifferences: true - ) - } - ``` - - - parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`. - - parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`. - */ - public func apply(_ snapshot: ListSnapshot, animatingDifferences: Bool = true) { - - let diffableSnapshot = snapshot.diffableSnapshot - self.dispatcher.apply( - diffableSnapshot, - view: self.collectionView, - animatingDifferences: animatingDifferences, - performUpdates: { collectionView, changeset, setSections in - - collectionView.reload( - using: changeset, - setData: setSections - ) - } - ) - } - - /** - Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found - - - parameter indexPath: the `IndexPath` to search for - - returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found - */ - @nonobjc - public func itemID(for indexPath: IndexPath) -> O.ObjectID? { - - return self.dispatcher.itemIdentifier(for: indexPath) - } - - /** - Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found - - - parameter itemID: the object identifier to search for - - returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found - */ - @nonobjc - public func indexPath(for itemID: O.ObjectID) -> IndexPath? { - - return self.dispatcher.indexPath(for: itemID) - } // MARK: - UICollectionViewDataSource @@ -163,19 +99,19 @@ extension DiffableDataSource { @objc public dynamic func numberOfSections(in collectionView: UICollectionView) -> Int { - return self.dispatcher.numberOfSections() + return self.numberOfSections() } @objc public dynamic func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return self.dispatcher.numberOfItems(inSection: section) + return self.numberOfItems(inSection: section) ?? 0 } @objc open dynamic func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let objectID = self.dispatcher.itemIdentifier(for: indexPath) else { + guard let objectID = self.itemID(for: indexPath) else { Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list") } @@ -203,95 +139,93 @@ extension DiffableDataSource { // MARK: Private - private weak var collectionView: UICollectionView? - - private let dataStack: DataStack private let cellProvider: (UICollectionView, IndexPath, O) -> UICollectionViewCell? private let supplementaryViewProvider: (UICollectionView, String, IndexPath) -> UICollectionReusableView? - private let dispatcher: Internals.DiffableDataUIDispatcher } -} -// MARK: - UICollectionView + // MARK: - DefaultCollectionViewTarget -extension UICollectionView { + public struct DefaultCollectionViewTarget: Target { - // MARK: FilePrivate - - // Implementation based on https://github.com/ra1028/DiffableDataSources - @nonobjc - fileprivate func reload( - using stagedChangeset: Internals.DiffableDataUIDispatcher.StagedChangeset, - interrupt: ((Internals.DiffableDataUIDispatcher.Changeset) -> Bool)? = nil, - setData: (C) -> Void - ) { + // MARK: Public - if case .none = window, let data = stagedChangeset.last?.data { + public typealias Base = T - setData(data) - self.reloadData() - return + public internal(set) weak var base: Base? + + public init(_ base: Base) { + + self.base = base } - for changeset in stagedChangeset { - if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data { - setData(data) - self.reloadData() - return - } - self.performBatchUpdates( - { - setData(changeset.data) + // MARK: DiffableDataSource.Target - if !changeset.sectionDeleted.isEmpty { + public var shouldSuspendBatchUpdates: Bool { - self.deleteSections(IndexSet(changeset.sectionDeleted)) - } - if !changeset.sectionInserted.isEmpty { + return self.base?.window == nil + } - self.insertSections(IndexSet(changeset.sectionInserted)) - } - if !changeset.sectionUpdated.isEmpty { + public func deleteSections(at indices: IndexSet, animated: Bool) { - self.reloadSections(IndexSet(changeset.sectionUpdated)) - } - for (source, target) in changeset.sectionMoved { + self.base?.deleteSections(indices) + } - self.moveSection(source, toSection: target) - } - if !changeset.elementDeleted.isEmpty { + public func insertSections(at indices: IndexSet, animated: Bool) { - self.deleteItems( - at: changeset.elementDeleted.map { IndexPath(item: $0.element, section: $0.section) } - ) - } - if !changeset.elementInserted.isEmpty { + self.base?.insertSections(indices) + } - self.insertItems( - at: changeset.elementInserted.map { IndexPath(item: $0.element, section: $0.section) } - ) - } - if !changeset.elementUpdated.isEmpty { + public func reloadSections(at indices: IndexSet, animated: Bool) { - self.reloadItems( - at: changeset.elementUpdated.map { IndexPath(item: $0.element, section: $0.section) } - ) - } - for (source, target) in changeset.elementMoved { + self.base?.reloadSections(indices) + } - self.moveItem( - at: IndexPath(item: source.element, section: source.section), - to: IndexPath(item: target.element, section: target.section) - ) - } - }, - completion: nil - ) + public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) { + + self.base?.moveSection(index, toSection: newIndex) + } + + public func deleteItems(at indexPaths: [IndexPath], animated: Bool) { + + self.base?.deleteItems(at: indexPaths) + } + + public func insertItems(at indexPaths: [IndexPath], animated: Bool) { + + self.base?.insertItems(at: indexPaths) + } + + public func reloadItems(at indexPaths: [IndexPath], animated: Bool) { + + self.base?.reloadItems(at: indexPaths) + } + + public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) { + + self.base?.moveItem(at: indexPath, to: newIndexPath) + } + + public func performBatchUpdates(updates: () -> Void, animated: Bool) { + + self.base?.performBatchUpdates(updates, completion: nil) + } + + public func reloadData() { + + self.base?.reloadData() } } } +// MARK: - Deprecated + +extension DiffableDataSource { + + @available(*, deprecated, renamed: "CollectionViewAdapter") + public typealias CollectionView = CollectionViewAdapter +} + #endif diff --git a/Sources/DiffableDataSource.TableView-UIKit.swift b/Sources/DiffableDataSource.TableView-UIKit.swift index 35928b2..7dbe104 100644 --- a/Sources/DiffableDataSource.TableView-UIKit.swift +++ b/Sources/DiffableDataSource.TableView-UIKit.swift @@ -1,5 +1,5 @@ // -// DiffableDataSource.TableView-UIKit.swift +// DiffableDataSource.TableViewAdapter-UIKit.swift // CoreStore // // Copyright © 2018 John Rommel Estropia @@ -33,13 +33,13 @@ import CoreData extension DiffableDataSource { - // MARK: - TableView + // MARK: - TableViewAdapter /** - The `DiffableDataSource.TableView` serves as a `UITableViewDataSource` that handles `ListPublisher` snapshots for a `UITableView`. Subclasses of `DiffableDataSource.TableView` may override some `UITableViewDataSource` methods as needed. - The `DiffableDataSource.TableView` instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle. + The `DiffableDataSource.TableViewAdapterAdapter` serves as a `UITableViewDataSource` that handles `ListPublisher` snapshots for a `UITableView`. Subclasses of `DiffableDataSource.TableViewAdapter` may override some `UITableViewDataSource` methods as needed. + The `DiffableDataSource.TableViewAdapterAdapter` instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle. ``` - self.dataSource = DiffableDataSource.TableView( + self.dataSource = DiffableDataSource.TableViewAdapter( tableView: self.tableView, dataStack: CoreStoreDefaults.dataStack, cellProvider: { (tableView, indexPath, person) in @@ -58,31 +58,17 @@ extension DiffableDataSource { ) } ``` - `DiffableDataSource.TableView` fully handles the reload animations. To turn change the default animation, set the `defaultRowAnimation`. + `DiffableDataSource.TableViewAdapter` fully handles the reload animations. - SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources */ - open class TableView: NSObject, UITableViewDataSource { - - // MARK: Open + open class TableViewAdapter: BaseAdapter>, UITableViewDataSource { + + // MARK: Publi /** - The animation style for row changes - */ - @nonobjc - open var defaultRowAnimation: UITableView.RowAnimation = .automatic - - - // MARK: Public - - /** - The object type represented by this dataSource - */ - public typealias ObjectType = O - - /** - Initializes the `DiffableDataSource.TableView`. This instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle. + Initializes the `DiffableDataSource.TableViewAdapter`. This instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle. ``` - self.dataSource = DiffableDataSource.TableView( + self.dataSource = DiffableDataSource.TableViewAdapter( tableView: self.tableView, dataStack: CoreStoreDefaults.dataStack, cellProvider: { (tableView, indexPath, person) in @@ -92,78 +78,24 @@ extension DiffableDataSource { } ) ``` - - parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableView`. + - parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableViewAdapter`. - parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from - parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object */ - @nonobjc public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?) { - self.tableView = tableView self.cellProvider = cellProvider - self.dataStack = dataStack - self.dispatcher = Internals.DiffableDataUIDispatcher(dataStack: dataStack) - - super.init() + super.init(target: .init(tableView), dataStack: dataStack) tableView.dataSource = self } - - /** - Reloads the `UITableView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`: - ``` - listPublisher.addObserver(self) { [weak self] (listPublisher) in - self?.dataSource?.apply( - listPublisher.snapshot, - animatingDifferences: true - ) - } - ``` - If the `defaultRowAnimation` is configured to, animations are also applied accordingly. - - - parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`. - - parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`. - */ - @nonobjc - public func apply(_ snapshot: ListSnapshot, animatingDifferences: Bool = true) { - self.dispatcher.apply( - snapshot.diffableSnapshot, - view: self.tableView, - animatingDifferences: animatingDifferences, - performUpdates: { tableView, changeset, setSections in + /** + The target `UITableView` + */ + public var tableView: UITableView? { - tableView.reload( - using: changeset, - with: self.defaultRowAnimation, - setData: setSections - ) - } - ) - } - - /** - Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found - - - parameter indexPath: the `IndexPath` to search for - - returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found - */ - @nonobjc - public func itemID(for indexPath: IndexPath) -> O.ObjectID? { - - return self.dispatcher.itemIdentifier(for: indexPath) - } - - /** - Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found - - - parameter itemID: the object identifier to search for - - returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found - */ - @nonobjc - public func indexPath(for itemID: O.ObjectID) -> IndexPath? { - - return self.dispatcher.indexPath(for: itemID) + return self.target.base } @@ -172,19 +104,19 @@ extension DiffableDataSource { @objc public dynamic func numberOfSections(in tableView: UITableView) -> Int { - return self.dispatcher.numberOfSections() + return self.numberOfSections() } @objc public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return self.dispatcher.numberOfItems(inSection: section) + return self.numberOfItems(inSection: section) ?? 0 } @objc open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return self.dispatcher.sectionIdentifier(inSection: section) + return self.sectionID(for: section) } @objc @@ -196,7 +128,7 @@ extension DiffableDataSource { @objc open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let objectID = self.dispatcher.itemIdentifier(for: indexPath) else { + guard let objectID = self.itemID(for: indexPath) else { Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list") } @@ -229,132 +161,106 @@ extension DiffableDataSource { // MARK: Private - @nonobjc - private weak var tableView: UITableView? - - @nonobjc - private let dataStack: DataStack - @nonobjc private let cellProvider: (UITableView, IndexPath, O) -> UITableViewCell? - - @nonobjc - private let dispatcher: Internals.DiffableDataUIDispatcher } -} -// MARK: - UITableView + // MARK: - DefaultTableViewTarget -extension UITableView { + public struct DefaultTableViewTarget: Target { - // MARK: FilePrivate - - // Implementation based on https://github.com/ra1028/DiffableDataSources - @nonobjc - fileprivate func reload( - using stagedChangeset: Internals.DiffableDataUIDispatcher.StagedChangeset, - with animation: @autoclosure () -> RowAnimation, - interrupt: ((Internals.DiffableDataUIDispatcher.Changeset) -> 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( - using stagedChangeset: Internals.DiffableDataUIDispatcher.StagedChangeset, - deleteSectionsAnimation: @autoclosure () -> RowAnimation, - insertSectionsAnimation: @autoclosure () -> RowAnimation, - reloadSectionsAnimation: @autoclosure () -> RowAnimation, - deleteRowsAnimation: @autoclosure () -> RowAnimation, - insertRowsAnimation: @autoclosure () -> RowAnimation, - reloadRowsAnimation: @autoclosure () -> RowAnimation, - interrupt: ((Internals.DiffableDataUIDispatcher.Changeset) -> Bool)? = nil, - setData: (C) -> Void - ) { - - if case .none = window, let data = stagedChangeset.last?.data { - - setData(data) - self.reloadData() - return + // MARK: Public + + public typealias Base = T + + public internal(set) weak var base: Base? + + public init(_ base: Base) { + + self.base = base } - for changeset in stagedChangeset { - - if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data { - - setData(data) - self.reloadData() + + + // MARK: DiffableDataSource.Target + + public var shouldSuspendBatchUpdates: Bool { + + return self.base?.window == nil + } + + public func deleteSections(at indices: IndexSet, animated: Bool) { + + self.base?.deleteSections(indices, with: .automatic) + } + + public func insertSections(at indices: IndexSet, animated: Bool) { + + self.base?.insertSections(indices, with: .automatic) + } + + public func reloadSections(at indices: IndexSet, animated: Bool) { + + self.base?.reloadSections(indices, with: .automatic) + } + + public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) { + + self.base?.moveSection(index, toSection: newIndex) + } + + public func deleteItems(at indexPaths: [IndexPath], animated: Bool) { + + self.base?.deleteRows(at: indexPaths, with: .automatic) + } + + public func insertItems(at indexPaths: [IndexPath], animated: Bool) { + + self.base?.insertRows(at: indexPaths, with: .automatic) + } + + public func reloadItems(at indexPaths: [IndexPath], animated: Bool) { + + self.base?.reloadRows(at: indexPaths, with: .automatic) + } + + public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) { + + self.base?.moveRow(at: indexPath, to: newIndexPath) + } + + public func performBatchUpdates(updates: () -> Void, animated: Bool) { + + guard let base = self.base else { + return } - self.cs_performBatchUpdates { - - setData(changeset.data) + if #available(iOS 11.0, tvOS 11.0, *) { - if !changeset.sectionDeleted.isEmpty { - - self.deleteSections(IndexSet(changeset.sectionDeleted), with: deleteSectionsAnimation()) - } - if !changeset.sectionInserted.isEmpty { - - self.insertSections(IndexSet(changeset.sectionInserted), with: insertSectionsAnimation()) - } - if !changeset.sectionUpdated.isEmpty { - - self.reloadSections(IndexSet(changeset.sectionUpdated), with: reloadSectionsAnimation()) - } - for (source, target) in changeset.sectionMoved { - - self.moveSection(source, toSection: target) - } - if !changeset.elementDeleted.isEmpty { - - self.deleteRows(at: changeset.elementDeleted.map { IndexPath(row: $0.element, section: $0.section) }, with: deleteRowsAnimation()) - } - if !changeset.elementInserted.isEmpty { - - self.insertRows(at: changeset.elementInserted.map { IndexPath(row: $0.element, section: $0.section) }, with: insertRowsAnimation()) - } - if !changeset.elementUpdated.isEmpty { - - self.reloadRows(at: changeset.elementUpdated.map { IndexPath(row: $0.element, section: $0.section) }, with: reloadRowsAnimation()) - } - for (source, target) in changeset.elementMoved { - - self.moveRow(at: IndexPath(row: source.element, section: source.section), to: IndexPath(row: target.element, section: target.section)) - } + base.performBatchUpdates(updates) + } + else { + + base.beginUpdates() + updates() + base.endUpdates() } } - } - @nonobjc - private func cs_performBatchUpdates(_ updates: () -> Void) { - - if #available(iOS 11.0, tvOS 11.0, *) { - - self.performBatchUpdates(updates) - } - else { - - self.beginUpdates() - updates() - self.endUpdates() + public func reloadData() { + + self.base?.reloadData() } } } +// MARK: - Deprecated + +extension DiffableDataSource { + + @available(*, deprecated, renamed: "TableViewAdapter") + public typealias TableView = TableViewAdapter +} + #endif diff --git a/Sources/DiffableDataSource.Target.swift b/Sources/DiffableDataSource.Target.swift new file mode 100644 index 0000000..e4696e1 --- /dev/null +++ b/Sources/DiffableDataSource.Target.swift @@ -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( + using stagedChangeset: Internals.DiffableDataUIDispatcher.StagedChangeset, + animated: Bool, + interrupt: ((Internals.DiffableDataUIDispatcher.Changeset) -> 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 + ) + } + } +} diff --git a/Sources/DiffableDataSource.swift b/Sources/DiffableDataSource.swift index c111e6b..c0c3e9d 100644 --- a/Sources/DiffableDataSource.swift +++ b/Sources/DiffableDataSource.swift @@ -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 {} diff --git a/Sources/Internals.DiffableDataUIDispatcher.swift b/Sources/Internals.DiffableDataUIDispatcher.swift index de370fb..6b32515 100644 --- a/Sources/Internals.DiffableDataUIDispatcher.swift +++ b/Sources/Internals.DiffableDataUIDispatcher.swift @@ -52,7 +52,17 @@ extension Internals { self.dataStack = dataStack } - func apply(_ snapshot: DiffableDataSourceSnapshot, view: View?, animatingDifferences: Bool, performUpdates: @escaping (View, StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>, @escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void) -> Void) { + func apply( + _ snapshot: DiffableDataSourceSnapshot, + target: Target?, + animatingDifferences: Bool, + performUpdates: @escaping ( + Target, + StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>, + @escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void + ) -> Void, + completion: @escaping () -> Void + ) { self.dispatcher.dispatch { [weak self] in @@ -64,36 +74,42 @@ extension Internals { self.currentSnapshot = snapshot let newSections = snapshot.sections - guard let view = view else { + guard let target = target else { - return self.sections = newSections + self.sections = newSections + return } let performDiffingUpdates: () -> Void = { let changeset = StagedChangeset(source: self.sections, target: newSections) - performUpdates(view, changeset) { sections in + performUpdates(target, changeset) { sections in self.sections = sections } } #if canImport(QuartzCore) - + + CATransaction.begin() + CATransaction.setCompletionBlock(completion) + if !animatingDifferences { - - CATransaction.begin() + CATransaction.setDisableActions(true) - - performDiffingUpdates() - - CATransaction.commit() - return } - - #endif - performDiffingUpdates() + + CATransaction.commit() + + + #else + + performDiffingUpdates() + completion() + + + #endif } } @@ -104,14 +120,23 @@ extension Internals { return snapshot } + func sectionIdentifier(inSection section: Int) -> String? { + + guard self.sections.indices.contains(section) else { + + return nil + } + return self.sections[section].differenceIdentifier + } + func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? { - - guard (0 ..< self.sections.endIndex) ~= indexPath.section else { + + guard self.sections.indices.contains(indexPath.section) else { return nil } let items = self.sections[indexPath.section].elements - guard (0 ..< items.endIndex) ~= indexPath.item else { + guard items.indices.contains(indexPath.item) else { return nil } @@ -138,15 +163,14 @@ extension Internals { return self.sections.count } - func numberOfItems(inSection section: Int) -> Int { - + func numberOfItems(inSection section: Int) -> Int? { + + guard self.sections.indices.contains(section) else { + + return nil + } return self.sections[section].elements.count } - - func sectionIdentifier(inSection section: Int) -> String { - - return self.sections[section].differenceIdentifier - } // MARK: Private diff --git a/Sources/ListPublisher.swift b/Sources/ListPublisher.swift index 71881e1..7d078b7 100644 --- a/Sources/ListPublisher.swift +++ b/Sources/ListPublisher.swift @@ -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`. `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`. `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: Hashable { diff --git a/Sources/ListSnapshot.swift b/Sources/ListSnapshot.swift index 1d02178..59ec4c0 100644 --- a/Sources/ListSnapshot.swift +++ b/Sources/ListSnapshot.swift @@ -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`.