Allow custom views to consume ListSnapshot diffable data

This commit is contained in:
John Estropia
2019-12-17 19:45:53 +09:00
parent 9a19919392
commit eef1c99f11
13 changed files with 764 additions and 536 deletions

View File

@@ -0,0 +1,197 @@
//
// DiffableDataSource.BaseAdapter.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - BaseAdapter
/**
The `DiffableDataSource.BaseAdapter` serves as a superclass for consumers of `ListPublisher` and `ListSnapshot` diffable data.
```
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
The dataSource can then apply changes from a `ListPublisher` as shown:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class BaseAdapter<O: DynamicObject, T: Target>: NSObject {
// MARK: Public
/**
The object type represented by this dataSource
*/
public typealias ObjectType = O
/**
The target to be updated by this dataSource
*/
public let target: T
/**
The `DataStack` where object fetches are performed
*/
public let dataStack: DataStack
/**
Initializes the `DiffableDataSource.BaseAdapter` object. This instance needs to be held on (retained) for as long as the target's lifecycle.
```
self.dataSource = DiffableDataSource.TableViewAdapterAdapter<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableViewAdapter`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
*/
public init(target: T, dataStack: DataStack) {
self.target = target
self.dataStack = dataStack
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
}
/**
Reloads the `UITableView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
If the `defaultRowAnimation` is configured to, animations are also applied accordingly.
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
*/
open func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true, completion: @escaping () -> Void = {}) {
let diffableSnapshot = snapshot.diffableSnapshot
self.dispatcher.apply(
diffableSnapshot,
target: self.target,
animatingDifferences: animatingDifferences,
performUpdates: { target, changeset, setSections in
target.reload(
using: changeset,
animated: animatingDifferences,
setData: setSections
)
},
completion: completion
)
}
/**
Returns the number of sections
- parameter indexPath: the `IndexPath` to search for
- returns: the number of sections
*/
public func numberOfSections() -> Int {
return self.dispatcher.numberOfSections()
}
/**
Returns the number of items at the specified section, or `nil` if the section is not found
- parameter section: the section index to search for
- returns: the number of items at the specified section, or `nil` if the section is not found
*/
public func numberOfItems(inSection section: Int) -> Int? {
return self.dispatcher.numberOfItems(inSection: section)
}
/**
Returns the section identifier at the specified index, or `nil` if not found
- parameter section: the section index to search for
- returns: the section identifier at the specified indec, or `nil` if not found
*/
public func sectionID(for section: Int) -> String? {
return self.dispatcher.sectionIdentifier(inSection: section)
}
/**
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
- parameter indexPath: the `IndexPath` to search for
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
*/
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
return self.dispatcher.itemIdentifier(for: indexPath)
}
/**
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
- parameter itemID: the object identifier to search for
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
*/
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
return self.dispatcher.indexPath(for: itemID)
}
// MARK: Internal
internal let dispatcher: Internals.DiffableDataUIDispatcher<O>
}
}

View File

@@ -1,5 +1,5 @@
//
// DiffableDataSource.CollectionView-AppKit.swift
// DiffableDataSource.CollectionViewAdapter-AppKit.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
@@ -36,10 +36,10 @@ extension DiffableDataSource {
// MARK: - CollectionView
/**
The `DiffableDataSource.CollectionView` serves as a `NSCollectionViewDataSource` that handles `ListPublisher` snapshots for a `NSCollectionView`. Subclasses of `DiffableDataSource.CollectionView` may override some `NSCollectionViewDataSource` methods as needed.
The `DiffableDataSource.CollectionView` instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
The `DiffableDataSource.CollectionViewAdapter` serves as a `NSCollectionViewDataSource` that handles `ListPublisher` snapshots for a `NSCollectionView`. Subclasses of `DiffableDataSource.CollectionViewAdapter` may override some `NSCollectionViewDataSource` methods as needed.
The `DiffableDataSource.CollectionViewAdapter` instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionView<Person>(
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
itemProvider: { (collectionView, indexPath, person) in
@@ -58,22 +58,17 @@ extension DiffableDataSource {
)
}
```
`DiffableDataSource.CollectionView` fully handles the reload animations.
`DiffableDataSource.CollectionViewAdapter` fully handles the reload animations.
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class CollectionView<O: DynamicObject>: NSObject, NSCollectionViewDataSource {
open class CollectionViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultCollectionViewTarget<NSCollectionView>>, NSCollectionViewDataSource {
// MARK: Public
/**
The object type represented by this dataSource
*/
public typealias ObjectType = O
/**
Initializes the `DiffableDataSource.CollectionView`. This instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
Initializes the `DiffableDataSource.CollectionViewAdapter`. This instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionView<Person>(
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
itemProvider: { (collectionView, indexPath, person) in
@@ -83,78 +78,20 @@ extension DiffableDataSource {
}
)
```
- parameter collectionView: the `NSCollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionView`.
- parameter collectionView: the `NSCollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionViewAdapter`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
*/
@nonobjc
public init(collectionView: NSCollectionView, dataStack: DataStack, itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?, supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }) {
self.collectionView = collectionView
self.itemProvider = itemProvider
self.supplementaryViewProvider = supplementaryViewProvider
self.dataStack = dataStack
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
super.init()
super.init(target: .init(collectionView), dataStack: dataStack)
collectionView.dataSource = self
}
/**
Reloads the `NSCollectionView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
*/
public func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true) {
let diffableSnapshot = snapshot.diffableSnapshot
self.dispatcher.apply(
diffableSnapshot as! Internals.DiffableDataSourceSnapshot,
view: self.collectionView,
animatingDifferences: animatingDifferences,
performUpdates: { collectionView, changeset, setSections in
collectionView.reload(
using: changeset,
setData: setSections
)
}
)
}
/**
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
- parameter indexPath: the `IndexPath` to search for
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
*/
@nonobjc
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
return self.dispatcher.itemIdentifier(for: indexPath)
}
/**
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
- parameter itemID: the object identifier to search for
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
*/
@nonobjc
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
return self.dispatcher.indexPath(for: itemID)
}
// MARK: - NSCollectionViewDataSource
@@ -162,19 +99,19 @@ extension DiffableDataSource {
@objc
public dynamic func numberOfSections(in collectionView: NSCollectionView) -> Int {
return self.dispatcher.numberOfSections()
return self.numberOfSections()
}
@objc
public dynamic func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dispatcher.numberOfItems(inSection: section)
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
guard let objectID = self.dispatcher.itemIdentifier(for: indexPath) else {
guard let objectID = self.itemID(for: indexPath) else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
}
@@ -202,94 +139,93 @@ extension DiffableDataSource {
// MARK: Private
private weak var collectionView: NSCollectionView?
private let dataStack: DataStack
private let itemProvider: (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?
private let supplementaryViewProvider: (NSCollectionView, String, IndexPath) -> NSView?
private let dispatcher: Internals.DiffableDataUIDispatcher<O>
}
// MARK: - DefaultCollectionViewTarget
public struct DefaultCollectionViewTarget<T: NSCollectionView>: Target {
// MARK: Public
public typealias Base = T
public internal(set) weak var base: Base?
public init(_ base: Base) {
self.base = base
}
// MARK: DiffableDataSource.Target:
public var shouldSuspendBatchUpdates: Bool {
return self.base?.window == nil
}
public func deleteSections(at indices: IndexSet, animated: Bool) {
self.base?.deleteSections(indices)
}
public func insertSections(at indices: IndexSet, animated: Bool) {
self.base?.insertSections(indices)
}
public func reloadSections(at indices: IndexSet, animated: Bool) {
self.base?.reloadSections(indices)
}
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
self.base?.moveSection(index, toSection: newIndex)
}
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.deleteItems(at: Set(indexPaths))
}
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.insertItems(at: Set(indexPaths))
}
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.reloadItems(at: Set(indexPaths))
}
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
self.base?.moveItem(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
self.base?.animator().performBatchUpdates(updates, completionHandler: nil)
}
public func reloadData() {
self.base?.reloadData()
}
}
}
// MARK: - NSCollectionView
// MARK: - Deprecated
extension NSCollectionView {
extension DiffableDataSource {
// MARK: FilePrivate
// Implementation based on https://github.com/ra1028/DiffableDataSources
@nonobjc
fileprivate func reload<C, O>(
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
) {
if case .none = window, let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
for changeset in stagedChangeset {
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
self.animator().performBatchUpdates(
{
setData(changeset.data)
if !changeset.sectionDeleted.isEmpty {
self.deleteSections(IndexSet(changeset.sectionDeleted))
}
if !changeset.sectionInserted.isEmpty {
self.insertSections(IndexSet(changeset.sectionInserted))
}
if !changeset.sectionUpdated.isEmpty {
self.reloadSections(IndexSet(changeset.sectionUpdated))
}
for (source, target) in changeset.sectionMoved {
self.moveSection(source, toSection: target)
}
if !changeset.elementDeleted.isEmpty {
self.deleteItems(
at: Set(changeset.elementDeleted.map { IndexPath(item: $0.element, section: $0.section) })
)
}
if !changeset.elementInserted.isEmpty {
self.insertItems(
at: Set(changeset.elementInserted.map { IndexPath(item: $0.element, section: $0.section) })
)
}
if !changeset.elementUpdated.isEmpty {
self.reloadItems(
at: Set(changeset.elementUpdated.map { IndexPath(item: $0.element, section: $0.section) })
)
}
for (source, target) in changeset.elementMoved {
self.moveItem(
at: IndexPath(item: source.element, section: source.section),
to: IndexPath(item: target.element, section: target.section)
)
}
},
completionHandler: nil
)
}
}
@available(*, deprecated, renamed: "CollectionViewAdapter")
public typealias CollectionView = CollectionViewAdapter
}

View File

@@ -1,5 +1,5 @@
//
// DiffableDataSource.CollectionView-UIKit.swift
// DiffableDataSource.CollectionViewAdapter-UIKit.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
@@ -36,10 +36,10 @@ extension DiffableDataSource {
// MARK: - CollectionView
/**
The `DiffableDataSource.CollectionView` serves as a `UICollectionViewDataSource` that handles `ListPublisher` snapshots for a `UICollectionView`. Subclasses of `DiffableDataSource.CollectionView` may override some `UICollectionViewDataSource` methods as needed.
The `DiffableDataSource.CollectionView` instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
The `DiffableDataSource.CollectionViewAdapter` serves as a `UICollectionViewDataSource` that handles `ListPublisher` snapshots for a `UICollectionView`. Subclasses of `DiffableDataSource.CollectionViewAdapter` may override some `UICollectionViewDataSource` methods as needed.
The `DiffableDataSource.CollectionViewAdapter` instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionView<Person>(
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (collectionView, indexPath, person) in
@@ -58,22 +58,17 @@ extension DiffableDataSource {
)
}
```
`DiffableDataSource.CollectionView` fully handles the reload animations.
`DiffableDataSource.CollectionViewAdapter` fully handles the reload animations.
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class CollectionView<O: DynamicObject>: NSObject, UICollectionViewDataSource {
open class CollectionViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultCollectionViewTarget<UICollectionView>>, UICollectionViewDataSource {
// MARK: Public
/**
The object type represented by this dataSource
*/
public typealias ObjectType = O
/**
Initializes the `DiffableDataSource.CollectionView`. This instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
Initializes the `DiffableDataSource.CollectionViewAdapter`. This instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionView<Person>(
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (collectionView, indexPath, person) in
@@ -83,79 +78,20 @@ extension DiffableDataSource {
}
)
```
- parameter collectionView: the `UICollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionView`.
- parameter collectionView: the `UICollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionViewAdapter`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UICollectionViewCell` for the object
- parameter supplementaryViewProvider: an optional closure for providing `UICollectionReusableView` supplementary views. If not set, defaults to returning `nil`
*/
@nonobjc
public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) {
self.collectionView = collectionView
self.cellProvider = cellProvider
self.supplementaryViewProvider = supplementaryViewProvider
self.dataStack = dataStack
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
super.init()
super.init(target: .init(collectionView), dataStack: dataStack)
collectionView.dataSource = self
}
/**
Reloads the `UICollectionView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
*/
public func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true) {
let diffableSnapshot = snapshot.diffableSnapshot
self.dispatcher.apply(
diffableSnapshot,
view: self.collectionView,
animatingDifferences: animatingDifferences,
performUpdates: { collectionView, changeset, setSections in
collectionView.reload(
using: changeset,
setData: setSections
)
}
)
}
/**
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
- parameter indexPath: the `IndexPath` to search for
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
*/
@nonobjc
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
return self.dispatcher.itemIdentifier(for: indexPath)
}
/**
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
- parameter itemID: the object identifier to search for
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
*/
@nonobjc
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
return self.dispatcher.indexPath(for: itemID)
}
// MARK: - UICollectionViewDataSource
@@ -163,19 +99,19 @@ extension DiffableDataSource {
@objc
public dynamic func numberOfSections(in collectionView: UICollectionView) -> Int {
return self.dispatcher.numberOfSections()
return self.numberOfSections()
}
@objc
public dynamic func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dispatcher.numberOfItems(inSection: section)
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let objectID = self.dispatcher.itemIdentifier(for: indexPath) else {
guard let objectID = self.itemID(for: indexPath) else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
}
@@ -203,95 +139,93 @@ extension DiffableDataSource {
// MARK: Private
private weak var collectionView: UICollectionView?
private let dataStack: DataStack
private let cellProvider: (UICollectionView, IndexPath, O) -> UICollectionViewCell?
private let supplementaryViewProvider: (UICollectionView, String, IndexPath) -> UICollectionReusableView?
private let dispatcher: Internals.DiffableDataUIDispatcher<O>
}
}
// MARK: - UICollectionView
// MARK: - DefaultCollectionViewTarget
extension UICollectionView {
public struct DefaultCollectionViewTarget<T: UICollectionView>: Target {
// MARK: FilePrivate
// Implementation based on https://github.com/ra1028/DiffableDataSources
@nonobjc
fileprivate func reload<C, O>(
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
) {
// MARK: Public
if case .none = window, let data = stagedChangeset.last?.data {
public typealias Base = T
setData(data)
self.reloadData()
return
public internal(set) weak var base: Base?
public init(_ base: Base) {
self.base = base
}
for changeset in stagedChangeset {
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
self.performBatchUpdates(
{
setData(changeset.data)
// MARK: DiffableDataSource.Target
if !changeset.sectionDeleted.isEmpty {
public var shouldSuspendBatchUpdates: Bool {
self.deleteSections(IndexSet(changeset.sectionDeleted))
}
if !changeset.sectionInserted.isEmpty {
return self.base?.window == nil
}
self.insertSections(IndexSet(changeset.sectionInserted))
}
if !changeset.sectionUpdated.isEmpty {
public func deleteSections(at indices: IndexSet, animated: Bool) {
self.reloadSections(IndexSet(changeset.sectionUpdated))
}
for (source, target) in changeset.sectionMoved {
self.base?.deleteSections(indices)
}
self.moveSection(source, toSection: target)
}
if !changeset.elementDeleted.isEmpty {
public func insertSections(at indices: IndexSet, animated: Bool) {
self.deleteItems(
at: changeset.elementDeleted.map { IndexPath(item: $0.element, section: $0.section) }
)
}
if !changeset.elementInserted.isEmpty {
self.base?.insertSections(indices)
}
self.insertItems(
at: changeset.elementInserted.map { IndexPath(item: $0.element, section: $0.section) }
)
}
if !changeset.elementUpdated.isEmpty {
public func reloadSections(at indices: IndexSet, animated: Bool) {
self.reloadItems(
at: changeset.elementUpdated.map { IndexPath(item: $0.element, section: $0.section) }
)
}
for (source, target) in changeset.elementMoved {
self.base?.reloadSections(indices)
}
self.moveItem(
at: IndexPath(item: source.element, section: source.section),
to: IndexPath(item: target.element, section: target.section)
)
}
},
completion: nil
)
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
self.base?.moveSection(index, toSection: newIndex)
}
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.deleteItems(at: indexPaths)
}
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.insertItems(at: indexPaths)
}
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.reloadItems(at: indexPaths)
}
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
self.base?.moveItem(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
self.base?.performBatchUpdates(updates, completion: nil)
}
public func reloadData() {
self.base?.reloadData()
}
}
}
// MARK: - Deprecated
extension DiffableDataSource {
@available(*, deprecated, renamed: "CollectionViewAdapter")
public typealias CollectionView = CollectionViewAdapter
}
#endif

View File

@@ -1,5 +1,5 @@
//
// DiffableDataSource.TableView-UIKit.swift
// DiffableDataSource.TableViewAdapter-UIKit.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
@@ -33,13 +33,13 @@ import CoreData
extension DiffableDataSource {
// MARK: - TableView
// MARK: - TableViewAdapter
/**
The `DiffableDataSource.TableView` serves as a `UITableViewDataSource` that handles `ListPublisher` snapshots for a `UITableView`. Subclasses of `DiffableDataSource.TableView` may override some `UITableViewDataSource` methods as needed.
The `DiffableDataSource.TableView` instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
The `DiffableDataSource.TableViewAdapterAdapter` serves as a `UITableViewDataSource` that handles `ListPublisher` snapshots for a `UITableView`. Subclasses of `DiffableDataSource.TableViewAdapter` may override some `UITableViewDataSource` methods as needed.
The `DiffableDataSource.TableViewAdapterAdapter` instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
```
self.dataSource = DiffableDataSource.TableView<Person>(
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
@@ -58,31 +58,17 @@ extension DiffableDataSource {
)
}
```
`DiffableDataSource.TableView` fully handles the reload animations. To turn change the default animation, set the `defaultRowAnimation`.
`DiffableDataSource.TableViewAdapter` fully handles the reload animations.
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class TableView<O: DynamicObject>: NSObject, UITableViewDataSource {
// MARK: Open
open class TableViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultTableViewTarget<UITableView>>, UITableViewDataSource {
// MARK: Publi
/**
The animation style for row changes
*/
@nonobjc
open var defaultRowAnimation: UITableView.RowAnimation = .automatic
// MARK: Public
/**
The object type represented by this dataSource
*/
public typealias ObjectType = O
/**
Initializes the `DiffableDataSource.TableView`. This instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
Initializes the `DiffableDataSource.TableViewAdapter`. This instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
```
self.dataSource = DiffableDataSource.TableView<Person>(
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
@@ -92,78 +78,24 @@ extension DiffableDataSource {
}
)
```
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableView`.
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableViewAdapter`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
*/
@nonobjc
public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?) {
self.tableView = tableView
self.cellProvider = cellProvider
self.dataStack = dataStack
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
super.init()
super.init(target: .init(tableView), dataStack: dataStack)
tableView.dataSource = self
}
/**
Reloads the `UITableView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
If the `defaultRowAnimation` is configured to, animations are also applied accordingly.
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
*/
@nonobjc
public func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true) {
self.dispatcher.apply(
snapshot.diffableSnapshot,
view: self.tableView,
animatingDifferences: animatingDifferences,
performUpdates: { tableView, changeset, setSections in
/**
The target `UITableView`
*/
public var tableView: UITableView? {
tableView.reload(
using: changeset,
with: self.defaultRowAnimation,
setData: setSections
)
}
)
}
/**
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
- parameter indexPath: the `IndexPath` to search for
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
*/
@nonobjc
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
return self.dispatcher.itemIdentifier(for: indexPath)
}
/**
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
- parameter itemID: the object identifier to search for
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
*/
@nonobjc
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
return self.dispatcher.indexPath(for: itemID)
return self.target.base
}
@@ -172,19 +104,19 @@ extension DiffableDataSource {
@objc
public dynamic func numberOfSections(in tableView: UITableView) -> Int {
return self.dispatcher.numberOfSections()
return self.numberOfSections()
}
@objc
public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dispatcher.numberOfItems(inSection: section)
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.dispatcher.sectionIdentifier(inSection: section)
return self.sectionID(for: section)
}
@objc
@@ -196,7 +128,7 @@ extension DiffableDataSource {
@objc
open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let objectID = self.dispatcher.itemIdentifier(for: indexPath) else {
guard let objectID = self.itemID(for: indexPath) else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
}
@@ -229,132 +161,106 @@ extension DiffableDataSource {
// MARK: Private
@nonobjc
private weak var tableView: UITableView?
@nonobjc
private let dataStack: DataStack
@nonobjc
private let cellProvider: (UITableView, IndexPath, O) -> UITableViewCell?
@nonobjc
private let dispatcher: Internals.DiffableDataUIDispatcher<O>
}
}
// MARK: - UITableView
// MARK: - DefaultTableViewTarget
extension UITableView {
public struct DefaultTableViewTarget<T: UITableView>: Target {
// MARK: FilePrivate
// Implementation based on https://github.com/ra1028/DiffableDataSources
@nonobjc
fileprivate func reload<C, O>(
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
with animation: @autoclosure () -> RowAnimation,
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
) {
self.reload(
using: stagedChangeset,
deleteSectionsAnimation: animation(),
insertSectionsAnimation: animation(),
reloadSectionsAnimation: animation(),
deleteRowsAnimation: animation(),
insertRowsAnimation: animation(),
reloadRowsAnimation: animation(),
interrupt: interrupt,
setData: setData
)
}
// Implementation based on https://github.com/ra1028/DiffableDataSources
@nonobjc
fileprivate func reload<C, O>(
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
deleteSectionsAnimation: @autoclosure () -> RowAnimation,
insertSectionsAnimation: @autoclosure () -> RowAnimation,
reloadSectionsAnimation: @autoclosure () -> RowAnimation,
deleteRowsAnimation: @autoclosure () -> RowAnimation,
insertRowsAnimation: @autoclosure () -> RowAnimation,
reloadRowsAnimation: @autoclosure () -> RowAnimation,
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
) {
if case .none = window, let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
// MARK: Public
public typealias Base = T
public internal(set) weak var base: Base?
public init(_ base: Base) {
self.base = base
}
for changeset in stagedChangeset {
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
// MARK: DiffableDataSource.Target
public var shouldSuspendBatchUpdates: Bool {
return self.base?.window == nil
}
public func deleteSections(at indices: IndexSet, animated: Bool) {
self.base?.deleteSections(indices, with: .automatic)
}
public func insertSections(at indices: IndexSet, animated: Bool) {
self.base?.insertSections(indices, with: .automatic)
}
public func reloadSections(at indices: IndexSet, animated: Bool) {
self.base?.reloadSections(indices, with: .automatic)
}
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
self.base?.moveSection(index, toSection: newIndex)
}
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.deleteRows(at: indexPaths, with: .automatic)
}
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.insertRows(at: indexPaths, with: .automatic)
}
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.reloadRows(at: indexPaths, with: .automatic)
}
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
self.base?.moveRow(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
guard let base = self.base else {
return
}
self.cs_performBatchUpdates {
setData(changeset.data)
if #available(iOS 11.0, tvOS 11.0, *) {
if !changeset.sectionDeleted.isEmpty {
self.deleteSections(IndexSet(changeset.sectionDeleted), with: deleteSectionsAnimation())
}
if !changeset.sectionInserted.isEmpty {
self.insertSections(IndexSet(changeset.sectionInserted), with: insertSectionsAnimation())
}
if !changeset.sectionUpdated.isEmpty {
self.reloadSections(IndexSet(changeset.sectionUpdated), with: reloadSectionsAnimation())
}
for (source, target) in changeset.sectionMoved {
self.moveSection(source, toSection: target)
}
if !changeset.elementDeleted.isEmpty {
self.deleteRows(at: changeset.elementDeleted.map { IndexPath(row: $0.element, section: $0.section) }, with: deleteRowsAnimation())
}
if !changeset.elementInserted.isEmpty {
self.insertRows(at: changeset.elementInserted.map { IndexPath(row: $0.element, section: $0.section) }, with: insertRowsAnimation())
}
if !changeset.elementUpdated.isEmpty {
self.reloadRows(at: changeset.elementUpdated.map { IndexPath(row: $0.element, section: $0.section) }, with: reloadRowsAnimation())
}
for (source, target) in changeset.elementMoved {
self.moveRow(at: IndexPath(row: source.element, section: source.section), to: IndexPath(row: target.element, section: target.section))
}
base.performBatchUpdates(updates)
}
else {
base.beginUpdates()
updates()
base.endUpdates()
}
}
}
@nonobjc
private func cs_performBatchUpdates(_ updates: () -> Void) {
if #available(iOS 11.0, tvOS 11.0, *) {
self.performBatchUpdates(updates)
}
else {
self.beginUpdates()
updates()
self.endUpdates()
public func reloadData() {
self.base?.reloadData()
}
}
}
// MARK: - Deprecated
extension DiffableDataSource {
@available(*, deprecated, renamed: "TableViewAdapter")
public typealias TableView = TableViewAdapter
}
#endif

View 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
)
}
}
}

View File

@@ -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 {}

View File

@@ -52,7 +52,17 @@ extension Internals {
self.dataStack = dataStack
}
func apply<View: AnyObject>(_ snapshot: DiffableDataSourceSnapshot, view: View?, animatingDifferences: Bool, performUpdates: @escaping (View, StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>, @escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void) -> Void) {
func apply<Target: DiffableDataSource.Target>(
_ snapshot: DiffableDataSourceSnapshot,
target: Target?,
animatingDifferences: Bool,
performUpdates: @escaping (
Target,
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
) -> Void,
completion: @escaping () -> Void
) {
self.dispatcher.dispatch { [weak self] in
@@ -64,36 +74,42 @@ extension Internals {
self.currentSnapshot = snapshot
let newSections = snapshot.sections
guard let view = view else {
guard let target = target else {
return self.sections = newSections
self.sections = newSections
return
}
let performDiffingUpdates: () -> Void = {
let changeset = StagedChangeset(source: self.sections, target: newSections)
performUpdates(view, changeset) { sections in
performUpdates(target, changeset) { sections in
self.sections = sections
}
}
#if canImport(QuartzCore)
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
if !animatingDifferences {
CATransaction.begin()
CATransaction.setDisableActions(true)
performDiffingUpdates()
CATransaction.commit()
return
}
#endif
performDiffingUpdates()
CATransaction.commit()
#else
performDiffingUpdates()
completion()
#endif
}
}
@@ -104,14 +120,23 @@ extension Internals {
return snapshot
}
func sectionIdentifier(inSection section: Int) -> String? {
guard self.sections.indices.contains(section) else {
return nil
}
return self.sections[section].differenceIdentifier
}
func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? {
guard (0 ..< self.sections.endIndex) ~= indexPath.section else {
guard self.sections.indices.contains(indexPath.section) else {
return nil
}
let items = self.sections[indexPath.section].elements
guard (0 ..< items.endIndex) ~= indexPath.item else {
guard items.indices.contains(indexPath.item) else {
return nil
}
@@ -138,15 +163,14 @@ extension Internals {
return self.sections.count
}
func numberOfItems(inSection section: Int) -> Int {
func numberOfItems(inSection section: Int) -> Int? {
guard self.sections.indices.contains(section) else {
return nil
}
return self.sections[section].elements.count
}
func sectionIdentifier(inSection section: Int) -> String {
return self.sections[section].differenceIdentifier
}
// MARK: Private

View File

@@ -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 {

View File

@@ -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`.