mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-24 10:21:17 +01:00
WIP
This commit is contained in:
107
Sources/DiffableDataSource.TableView.swift
Normal file
107
Sources/DiffableDataSource.TableView.swift
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// DiffableDataSource.TableView.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2018 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(UIKit)
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
|
||||
//// MARK: - DiffableDataSource
|
||||
//
|
||||
//extension DiffableDataSource {
|
||||
//
|
||||
// // MARK: - TableView
|
||||
//
|
||||
// public open class TableView<D: DynamicObject>: NSObject, UITableViewDataSource {
|
||||
//
|
||||
// // MARK: Public
|
||||
//
|
||||
// public typealias ObjectType = D
|
||||
//
|
||||
// public var defaultRowAnimation: UITableView.RowAnimation = .automatic
|
||||
//
|
||||
// public init(tableView: UITableView, cellProvider: @escaping (UITableView, IndexPath, ObjectType) -> UITableViewCell?) {
|
||||
//
|
||||
// self.tableView = tableView
|
||||
// self.cellProvider = cellProvider
|
||||
//
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) {
|
||||
//
|
||||
// self.rawDataSource = UITableViewDiffableDataSource<String, D.ObjectID>(
|
||||
// tableView: tableView,
|
||||
// cellProvider: { (tableView, indexPath, managedObjectID) -> UITableViewCell? in
|
||||
//
|
||||
// cellProvider(
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// self.rawDataSource = nil
|
||||
// }
|
||||
//
|
||||
// super.init()
|
||||
//
|
||||
// tableView.dataSource = self
|
||||
// }
|
||||
//
|
||||
// public func apply(_ snapshot: ListSnapshot<ObjectType>, animatingDifferences: Bool = true) {
|
||||
//
|
||||
// let dataSource = UITableViewDiffableDataSource<String, D>.
|
||||
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) {
|
||||
//
|
||||
// self.rawDataSource! as! UITableViewDiffableDataSource<String, D>
|
||||
//
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// }
|
||||
// core.apply(
|
||||
// snapshot,
|
||||
// view: tableView,
|
||||
// animatingDifferences: animatingDifferences,
|
||||
// performUpdates: { tableView, changeset, setSections in
|
||||
// tableView.reload(using: changeset, with: self.defaultRowAnimation, setData: setSections)
|
||||
// })
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // MARK: Private
|
||||
//
|
||||
// private weak var tableView: UITableView?
|
||||
// private let cellProvider: (UITableView, IndexPath, ObjectType) -> UITableViewCell?
|
||||
// private let rawDataSource: Any?
|
||||
//
|
||||
// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
// private var diffableDataSource: UITableViewDiffableDataSource<String, D.ObjectID> {
|
||||
//
|
||||
// return self.rawDataSource! as! UITableViewDiffableDataSource<String, D.ObjectID>
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
#endif
|
||||
29
Sources/DiffableDataSource.swift
Normal file
29
Sources/DiffableDataSource.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// File.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.
|
||||
//
|
||||
|
||||
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
public enum DiffableDataSource {}
|
||||
@@ -33,7 +33,12 @@ import CoreData
|
||||
All CoreStore's utilities are designed around `DynamicObject` instances. `NSManagedObject` and `CoreStoreObject` instances all conform to `DynamicObject`.
|
||||
*/
|
||||
public protocol DynamicObject: AnyObject {
|
||||
|
||||
|
||||
/**
|
||||
The object ID for this instance
|
||||
*/
|
||||
typealias ObjectID = NSManagedObjectID
|
||||
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@@ -52,7 +57,7 @@ public protocol DynamicObject: AnyObject {
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
func cs_id() -> NSManagedObjectID
|
||||
func cs_id() -> ObjectID
|
||||
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
@@ -103,7 +108,7 @@ extension NSManagedObject: DynamicObject {
|
||||
return object.isKind(of: self)
|
||||
}
|
||||
|
||||
public func cs_id() -> NSManagedObjectID {
|
||||
public func cs_id() -> ObjectID {
|
||||
|
||||
return self.objectID
|
||||
}
|
||||
@@ -166,7 +171,7 @@ extension CoreStoreObject {
|
||||
return (self as AnyClass).isSubclass(of: type as AnyClass)
|
||||
}
|
||||
|
||||
public func cs_id() -> NSManagedObjectID {
|
||||
public func cs_id() -> ObjectID {
|
||||
|
||||
return self.rawObject!.objectID
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@ public struct DataStackEnvironmentKey: EnvironmentKey {
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
extension EnvironmentValues {
|
||||
|
||||
|
||||
// MARK: Public
|
||||
|
||||
|
||||
public var dataStack: DataStack {
|
||||
get {
|
||||
return self[DataStackEnvironmentKey.self]
|
||||
|
||||
@@ -82,6 +82,11 @@ extension Internals {
|
||||
|
||||
try self.reapplyAffectedStores(self.typedFetchRequest, self.managedObjectContext)
|
||||
try self.performFetch()
|
||||
|
||||
if case let delegate as FetchedDiffableDataSourceSnapshotDelegate = self.delegate {
|
||||
|
||||
delegate.initialFetch()
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
|
||||
606
Sources/Internals.DiffableDataSourceSnapshot.swift
Normal file
606
Sources/Internals.DiffableDataSourceSnapshot.swift
Normal file
@@ -0,0 +1,606 @@
|
||||
//
|
||||
// Internals.DiffableDataSourceSnapshot.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2018 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
import CoreData
|
||||
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
|
||||
#elseif canImport(AppKit)
|
||||
import AppKit
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
extension Internals {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal typealias DiffableDataSourceSnapshot = _Internal_DiffableDataSourceSnapshot
|
||||
|
||||
|
||||
// MARK: - FallbackDiffableDataSourceSnapshot
|
||||
|
||||
// Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
internal struct FallbackDiffableDataSourceSnapshot: DiffableDataSourceSnapshot {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
init(sections: [NSFetchedResultsSectionInfo]) {
|
||||
|
||||
self.structure = .init(sections: sections)
|
||||
}
|
||||
|
||||
// MARK: DiffableDataSourceSnapshot
|
||||
|
||||
init() {
|
||||
|
||||
self.structure = .init()
|
||||
}
|
||||
|
||||
var numberOfItems: Int {
|
||||
|
||||
return self.itemIdentifiers.count
|
||||
}
|
||||
|
||||
var numberOfSections: Int {
|
||||
|
||||
return self.sectionIdentifiers.count
|
||||
}
|
||||
|
||||
var sectionIdentifiers: [NSString] {
|
||||
|
||||
return self.structure.allSectionIDs
|
||||
}
|
||||
|
||||
var itemIdentifiers: [NSManagedObjectID] {
|
||||
|
||||
self.structure.allItemIDs
|
||||
}
|
||||
|
||||
func numberOfItems(inSection identifier: NSString) -> Int {
|
||||
|
||||
return self.itemIdentifiers(inSection: identifier).count
|
||||
}
|
||||
|
||||
func itemIdentifiers(inSection identifier: NSString) -> [NSManagedObjectID] {
|
||||
|
||||
return self.structure.items(in: identifier)
|
||||
}
|
||||
|
||||
func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> NSString? {
|
||||
|
||||
return self.structure.section(containing: identifier)
|
||||
}
|
||||
|
||||
func indexOfItem(_ identifier: NSManagedObjectID) -> Int? {
|
||||
|
||||
return self.itemIdentifiers.firstIndex(of: identifier)
|
||||
}
|
||||
|
||||
func indexOfSection(_ identifier: NSString) -> Int? {
|
||||
|
||||
return self.sectionIdentifiers.firstIndex(of: identifier)
|
||||
}
|
||||
|
||||
mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: NSString?) {
|
||||
|
||||
self.structure.append(itemIDs: identifiers, to: sectionIdentifier)
|
||||
}
|
||||
|
||||
mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID) {
|
||||
|
||||
self.structure.insert(itemIDs: identifiers, before: beforeIdentifier)
|
||||
}
|
||||
|
||||
mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID) {
|
||||
|
||||
self.structure.insert(itemIDs: identifiers, after: afterIdentifier)
|
||||
}
|
||||
|
||||
mutating func deleteItems(_ identifiers: [NSManagedObjectID]) {
|
||||
|
||||
self.structure.remove(itemIDs: identifiers)
|
||||
}
|
||||
|
||||
mutating func deleteAllItems() {
|
||||
|
||||
self.structure.removeAllItems()
|
||||
}
|
||||
|
||||
mutating func moveItem(_ identifier: NSManagedObjectID, beforeItem toIdentifier: NSManagedObjectID) {
|
||||
|
||||
self.structure.move(itemID: identifier, before: toIdentifier)
|
||||
}
|
||||
|
||||
mutating func moveItem(_ identifier: NSManagedObjectID, afterItem toIdentifier: NSManagedObjectID) {
|
||||
|
||||
self.structure.move(itemID: identifier, after: toIdentifier)
|
||||
}
|
||||
|
||||
mutating func reloadItems(_ identifiers: [NSManagedObjectID]) {
|
||||
|
||||
self.structure.update(itemIDs: identifiers)
|
||||
}
|
||||
|
||||
mutating func appendSections(_ identifiers: [NSString]) {
|
||||
|
||||
self.structure.append(sectionIDs: identifiers)
|
||||
}
|
||||
|
||||
mutating func insertSections(_ identifiers: [NSString], beforeSection toIdentifier: NSString) {
|
||||
|
||||
self.structure.insert(sectionIDs: identifiers, before: toIdentifier)
|
||||
}
|
||||
|
||||
mutating func insertSections(_ identifiers: [NSString], afterSection toIdentifier: NSString) {
|
||||
|
||||
self.structure.insert(sectionIDs: identifiers, after: toIdentifier)
|
||||
}
|
||||
|
||||
mutating func deleteSections(_ identifiers: [NSString]) {
|
||||
|
||||
self.structure.remove(sectionIDs: identifiers)
|
||||
}
|
||||
|
||||
mutating func moveSection(_ identifier: NSString, beforeSection toIdentifier: NSString) {
|
||||
|
||||
self.structure.move(sectionID: identifier, before: toIdentifier)
|
||||
}
|
||||
|
||||
mutating func moveSection(_ identifier: NSString, afterSection toIdentifier: NSString) {
|
||||
|
||||
self.structure.move(sectionID: identifier, after: toIdentifier)
|
||||
}
|
||||
|
||||
mutating func reloadSections(_ identifiers: [NSString]) {
|
||||
|
||||
self.structure.update(sectionIDs: identifiers)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var structure: BackingStructure
|
||||
|
||||
|
||||
// MARK: - BackingStructure
|
||||
|
||||
internal struct BackingStructure {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
var sections: [Section]
|
||||
|
||||
init() {
|
||||
|
||||
self.sections = []
|
||||
}
|
||||
|
||||
init(sections: [NSFetchedResultsSectionInfo]) {
|
||||
|
||||
self.sections = sections.map {
|
||||
|
||||
Section(
|
||||
id: $0.name as NSString,
|
||||
items: $0.objects?
|
||||
.compactMap({ ($0 as? NSManagedObject)?.objectID })
|
||||
.map(Item.init(id:)) ?? [],
|
||||
isReloaded: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var allSectionIDs: [NSString] {
|
||||
|
||||
return self.sections.map({ $0.id })
|
||||
}
|
||||
|
||||
var allItemIDs: [NSManagedObjectID] {
|
||||
|
||||
return self.sections.lazy.flatMap({ $0.elements }).map({ $0.id })
|
||||
}
|
||||
|
||||
func items(in sectionID: NSString) -> [NSManagedObjectID] {
|
||||
|
||||
guard let sectionIndex = self.sectionIndex(of: sectionID) else {
|
||||
|
||||
Internals.abort("Section \"\(sectionID)\" does not exist")
|
||||
}
|
||||
return self.sections[sectionIndex].elements.map({ $0.id })
|
||||
}
|
||||
|
||||
func section(containing itemID: NSManagedObjectID) -> NSString? {
|
||||
|
||||
return self.itemPositionMap()[itemID]?.section.id
|
||||
}
|
||||
|
||||
mutating func append(itemIDs: [NSManagedObjectID], to sectionID: NSString?) {
|
||||
|
||||
let index: Array<Section>.Index
|
||||
if let sectionID = sectionID {
|
||||
|
||||
guard let sectionIndex = self.sectionIndex(of: sectionID) else {
|
||||
|
||||
Internals.abort("Section \"\(sectionID)\" does not exist")
|
||||
}
|
||||
index = sectionIndex
|
||||
}
|
||||
else {
|
||||
|
||||
let section = self.sections
|
||||
guard !section.isEmpty else {
|
||||
|
||||
Internals.abort("No sections exist")
|
||||
}
|
||||
index = section.index(before: section.endIndex)
|
||||
}
|
||||
|
||||
let items = itemIDs.lazy.map(Item.init)
|
||||
self.sections[index].elements.append(contentsOf: items)
|
||||
}
|
||||
|
||||
mutating func insert(itemIDs: [NSManagedObjectID], before beforeItemID: NSManagedObjectID) {
|
||||
|
||||
guard let itemPosition = self.itemPositionMap()[beforeItemID] else {
|
||||
|
||||
Internals.abort("Item \(beforeItemID) does not exist")
|
||||
}
|
||||
let items = itemIDs.lazy.map(Item.init)
|
||||
self.sections[itemPosition.sectionIndex].elements
|
||||
.insert(contentsOf: items, at: itemPosition.itemRelativeIndex)
|
||||
}
|
||||
|
||||
mutating func insert(itemIDs: [NSManagedObjectID], after afterItemID: NSManagedObjectID) {
|
||||
|
||||
guard let itemPosition = self.itemPositionMap()[afterItemID] else {
|
||||
|
||||
Internals.abort("Item \(afterItemID) does not exist")
|
||||
}
|
||||
let itemIndex = self.sections[itemPosition.sectionIndex].elements
|
||||
.index(after: itemPosition.itemRelativeIndex)
|
||||
let items = itemIDs.lazy.map(Item.init)
|
||||
self.sections[itemPosition.sectionIndex].elements
|
||||
.insert(contentsOf: items, at: itemIndex)
|
||||
}
|
||||
|
||||
mutating func remove(itemIDs: [NSManagedObjectID]) {
|
||||
|
||||
let itemPositionMap = self.itemPositionMap()
|
||||
var removeIndexSetMap: [Int: IndexSet] = [:]
|
||||
|
||||
for itemID in itemIDs {
|
||||
|
||||
guard let itemPosition = itemPositionMap[itemID] else {
|
||||
|
||||
continue
|
||||
}
|
||||
removeIndexSetMap[itemPosition.sectionIndex, default: []]
|
||||
.insert(itemPosition.itemRelativeIndex)
|
||||
}
|
||||
for (sectionIndex, removeIndexSet) in removeIndexSetMap {
|
||||
|
||||
for range in removeIndexSet.rangeView.reversed() {
|
||||
|
||||
self.sections[sectionIndex].elements.removeSubrange(range)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutating func removeAllItems() {
|
||||
|
||||
for sectionIndex in self.sections.indices {
|
||||
|
||||
self.sections[sectionIndex].elements.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
mutating func move(itemID: NSManagedObjectID, before beforeItemID: NSManagedObjectID) {
|
||||
|
||||
guard let removed = self.remove(itemID: itemID) else {
|
||||
|
||||
Internals.abort("Item \(itemID) does not exist")
|
||||
}
|
||||
guard let itemPosition = self.itemPositionMap()[beforeItemID] else {
|
||||
|
||||
Internals.abort("Item \(beforeItemID) does not exist")
|
||||
}
|
||||
self.sections[itemPosition.sectionIndex].elements
|
||||
.insert(removed, at: itemPosition.itemRelativeIndex)
|
||||
}
|
||||
|
||||
mutating func move(itemID: NSManagedObjectID, after afterItemID: NSManagedObjectID) {
|
||||
|
||||
guard let removed = self.remove(itemID: itemID) else {
|
||||
|
||||
Internals.abort("Item \(itemID) does not exist")
|
||||
}
|
||||
guard let itemPosition = self.itemPositionMap()[afterItemID] else {
|
||||
|
||||
Internals.abort("Item \(afterItemID) does not exist")
|
||||
}
|
||||
let itemIndex = self.sections[itemPosition.sectionIndex].elements
|
||||
.index(after: itemPosition.itemRelativeIndex)
|
||||
self.sections[itemPosition.sectionIndex].elements
|
||||
.insert(removed, at: itemIndex)
|
||||
}
|
||||
|
||||
mutating func update(itemIDs: [NSManagedObjectID]) {
|
||||
|
||||
let itemPositionMap = self.itemPositionMap()
|
||||
for itemID in itemIDs {
|
||||
|
||||
guard let itemPosition = itemPositionMap[itemID] else {
|
||||
|
||||
Internals.abort("Item \(itemID) does not exist")
|
||||
}
|
||||
self.sections[itemPosition.sectionIndex].elements[itemPosition.itemRelativeIndex].isReloaded = true
|
||||
}
|
||||
}
|
||||
|
||||
mutating func append(sectionIDs: [NSString]) {
|
||||
|
||||
let newSections = sectionIDs.lazy.map(Section.init)
|
||||
self.sections.append(contentsOf: newSections)
|
||||
}
|
||||
|
||||
mutating func insert(sectionIDs: [NSString], before beforeSectionID: NSString) {
|
||||
|
||||
guard let sectionIndex = self.sectionIndex(of: beforeSectionID) else {
|
||||
|
||||
Internals.abort("Section \"\(beforeSectionID)\" does not exist")
|
||||
}
|
||||
let newSections = sectionIDs.lazy.map(Section.init)
|
||||
self.sections.insert(contentsOf: newSections, at: sectionIndex)
|
||||
}
|
||||
|
||||
mutating func insert(sectionIDs: [NSString], after afterSectionID: NSString) {
|
||||
|
||||
guard let beforeIndex = self.sectionIndex(of: afterSectionID) else {
|
||||
|
||||
Internals.abort("Section \"\(afterSectionID)\" does not exist")
|
||||
}
|
||||
let sectionIndex = self.sections.index(after: beforeIndex)
|
||||
let newSections = sectionIDs.lazy.map(Section.init)
|
||||
self.sections.insert(contentsOf: newSections, at: sectionIndex)
|
||||
}
|
||||
|
||||
mutating func remove(sectionIDs: [NSString]) {
|
||||
|
||||
for sectionID in sectionIDs {
|
||||
|
||||
self.remove(sectionID: sectionID)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func move(sectionID: NSString, before beforeSectionID: NSString) {
|
||||
|
||||
guard let removed = self.remove(sectionID: sectionID) else {
|
||||
|
||||
Internals.abort("Section \"\(sectionID)\" does not exist")
|
||||
}
|
||||
guard let sectionIndex = self.sectionIndex(of: beforeSectionID) else {
|
||||
|
||||
Internals.abort("Section \"\(beforeSectionID)\" does not exist")
|
||||
}
|
||||
self.sections.insert(removed, at: sectionIndex)
|
||||
}
|
||||
|
||||
mutating func move(sectionID: NSString, after afterSectionID: NSString) {
|
||||
|
||||
guard let removed = self.remove(sectionID: sectionID) else {
|
||||
|
||||
Internals.abort("Section \"\(sectionID)\" does not exist")
|
||||
}
|
||||
guard let beforeIndex = self.sectionIndex(of: afterSectionID) else {
|
||||
|
||||
Internals.abort("Section \"\(afterSectionID)\" does not exist")
|
||||
}
|
||||
let sectionIndex = self.sections.index(after: beforeIndex)
|
||||
self.sections.insert(removed, at: sectionIndex)
|
||||
}
|
||||
|
||||
mutating func update(sectionIDs: [NSString]) {
|
||||
|
||||
for sectionID in sectionIDs {
|
||||
|
||||
guard let sectionIndex = self.sectionIndex(of: sectionID) else {
|
||||
|
||||
continue
|
||||
}
|
||||
self.sections[sectionIndex].isReloaded = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func sectionIndex(of sectionID: NSString) -> Array<Section>.Index? {
|
||||
|
||||
return self.sections.firstIndex(where: { $0.id == sectionID })
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private mutating func remove(itemID: NSManagedObjectID) -> Item? {
|
||||
|
||||
guard let itemPosition = self.itemPositionMap()[itemID] else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return self.sections[itemPosition.sectionIndex].elements
|
||||
.remove(at: itemPosition.itemRelativeIndex)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private mutating func remove(sectionID: NSString) -> Section? {
|
||||
|
||||
guard let sectionIndex = self.sectionIndex(of: sectionID) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return self.sections.remove(at: sectionIndex)
|
||||
}
|
||||
|
||||
private func itemPositionMap() -> [NSManagedObjectID: ItemPosition] {
|
||||
|
||||
return self.sections.enumerated().reduce(into: [:]) { result, section in
|
||||
|
||||
for (itemRelativeIndex, item) in section.element.elements.enumerated() {
|
||||
|
||||
result[item.id] = ItemPosition(
|
||||
item: item,
|
||||
itemRelativeIndex: itemRelativeIndex,
|
||||
section: section.element,
|
||||
sectionIndex: section.offset
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Item
|
||||
|
||||
internal struct Item: Identifiable, Equatable {
|
||||
|
||||
var isReloaded: Bool
|
||||
|
||||
init(id: NSManagedObjectID, isReloaded: Bool) {
|
||||
|
||||
self.id = id
|
||||
self.isReloaded = isReloaded
|
||||
}
|
||||
|
||||
init(id: NSManagedObjectID) {
|
||||
|
||||
self.init(id: id, isReloaded: false)
|
||||
}
|
||||
|
||||
func isContentEqual(to source: Item) -> Bool {
|
||||
|
||||
return !self.isReloaded && self.id == source.id
|
||||
}
|
||||
|
||||
// MARK: Identifiable
|
||||
|
||||
var id: NSManagedObjectID
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Section
|
||||
|
||||
internal struct Section: Identifiable, Equatable {
|
||||
|
||||
var elements: [Item] = []
|
||||
var isReloaded: Bool
|
||||
|
||||
init(id: NSString, items: [Item], isReloaded: Bool) {
|
||||
self.id = id
|
||||
self.elements = items
|
||||
self.isReloaded = isReloaded
|
||||
}
|
||||
|
||||
init(id: NSString) {
|
||||
|
||||
self.init(id: id, items: [], isReloaded: false)
|
||||
}
|
||||
|
||||
init<S: Sequence>(source: Section, elements: S) where S.Element == Item {
|
||||
|
||||
self.init(id: source.id, items: Array(elements), isReloaded: source.isReloaded)
|
||||
}
|
||||
|
||||
func isContentEqual(to source: Section) -> Bool {
|
||||
|
||||
return !self.isReloaded && self.id == source.id
|
||||
}
|
||||
|
||||
// MARK: Identifiable
|
||||
|
||||
var id: NSString
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ItemPosition
|
||||
|
||||
fileprivate struct ItemPosition {
|
||||
|
||||
var item: Item
|
||||
var itemRelativeIndex: Int
|
||||
var section: Section
|
||||
var sectionIndex: Int
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSDiffableDataSourceSnapshot: Internals.DiffableDataSourceSnapshot
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
extension NSDiffableDataSourceSnapshot: Internals.DiffableDataSourceSnapshot where SectionIdentifierType == NSString, ItemIdentifierType == NSManagedObjectID {}
|
||||
|
||||
|
||||
// MARK: - Internals.DiffableDataSourceSnapshot
|
||||
|
||||
internal protocol _Internal_DiffableDataSourceSnapshot {
|
||||
|
||||
init()
|
||||
|
||||
var numberOfItems: Int { get }
|
||||
var numberOfSections: Int { get }
|
||||
var sectionIdentifiers: [NSString] { get }
|
||||
var itemIdentifiers: [NSManagedObjectID] { get }
|
||||
|
||||
func numberOfItems(inSection identifier: NSString) -> Int
|
||||
func itemIdentifiers(inSection identifier: NSString) -> [NSManagedObjectID]
|
||||
func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> NSString?
|
||||
func indexOfItem(_ identifier: NSManagedObjectID) -> Int?
|
||||
func indexOfSection(_ identifier: NSString) -> Int?
|
||||
|
||||
mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: NSString?)
|
||||
mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID)
|
||||
mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID)
|
||||
mutating func deleteItems(_ identifiers: [NSManagedObjectID])
|
||||
mutating func deleteAllItems()
|
||||
mutating func moveItem(_ identifier: NSManagedObjectID, beforeItem toIdentifier: NSManagedObjectID)
|
||||
mutating func moveItem(_ identifier: NSManagedObjectID, afterItem toIdentifier: NSManagedObjectID)
|
||||
mutating func reloadItems(_ identifiers: [NSManagedObjectID])
|
||||
mutating func appendSections(_ identifiers: [NSString])
|
||||
mutating func insertSections(_ identifiers: [NSString], beforeSection toIdentifier: NSString)
|
||||
mutating func insertSections(_ identifiers: [NSString], afterSection toIdentifier: NSString)
|
||||
mutating func deleteSections(_ identifiers: [NSString])
|
||||
mutating func moveSection(_ identifier: NSString, beforeSection toIdentifier: NSString)
|
||||
mutating func moveSection(_ identifier: NSString, afterSection toIdentifier: NSString)
|
||||
mutating func reloadSections(_ identifiers: [NSString])
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
157
Sources/Internals.FallbackDiffableDataUIDispatcher.swift
Normal file
157
Sources/Internals.FallbackDiffableDataUIDispatcher.swift
Normal file
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// Internals.FallbackDiffableDataUIDispatcher.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2018 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
import CoreData
|
||||
|
||||
#if canImport(QuartzCore)
|
||||
import QuartzCore
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
extension Internals {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
// // Implementation based on https://github.com/ra1028/DiffableDataSources
|
||||
// internal final class FallbackDiffableDataUIDispatcher {
|
||||
//
|
||||
// // MARK: Internal
|
||||
//
|
||||
// internal func apply() {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// func snapshot() -> DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType> {
|
||||
// var snapshot = DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>()
|
||||
// snapshot.structure.sections = currentSnapshot.structure.sections
|
||||
// return snapshot
|
||||
// }
|
||||
//
|
||||
// func itemIdentifier(for indexPath: IndexPath) -> ItemIdentifierType? {
|
||||
// guard 0..<sections.endIndex ~= indexPath.section else {
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// let items = sections[indexPath.section].elements
|
||||
//
|
||||
// guard 0..<items.endIndex ~= indexPath.item else {
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// return items[indexPath.item].differenceIdentifier
|
||||
// }
|
||||
//
|
||||
// func unsafeItemIdentifier(for indexPath: IndexPath, file: StaticString = #file, line: UInt = #line) -> ItemIdentifierType {
|
||||
// guard let itemIdentifier = itemIdentifier(for: indexPath) else {
|
||||
// universalError("Item not found at the specified index path(\(indexPath)).")
|
||||
// }
|
||||
//
|
||||
// return itemIdentifier
|
||||
// }
|
||||
//
|
||||
// func indexPath(for itemIdentifier: ItemIdentifierType) -> IndexPath? {
|
||||
// let indexPathMap: [ItemIdentifierType: IndexPath] = sections.enumerated()
|
||||
// .reduce(into: [:]) { result, section in
|
||||
// for (itemIndex, item) in section.element.elements.enumerated() {
|
||||
// result[item.differenceIdentifier] = IndexPath(
|
||||
// item: itemIndex,
|
||||
// section: section.offset
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// return indexPathMap[itemIdentifier]
|
||||
// }
|
||||
//
|
||||
// func numberOfSections() -> Int {
|
||||
// return sections.count
|
||||
// }
|
||||
//
|
||||
// func numberOfItems(inSection section: Int) -> Int {
|
||||
// return sections[section].elements.count
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// // MARK: Private
|
||||
//
|
||||
// private let dispatcher: MainThreadSerialDispatcher = .init()
|
||||
//
|
||||
// private var currentSnapshot: Internals.DiffableDataSourceSnapshot = FallbackDiffableDataSourceSnapshot()
|
||||
// private var sections: [FallbackDiffableDataSourceSnapshot.BackingStructure.Section] = []
|
||||
//
|
||||
//
|
||||
// // MARK: - MainThreadSerialDispatcher
|
||||
//
|
||||
// fileprivate final class MainThreadSerialDispatcher {
|
||||
//
|
||||
// // MARK: FilePrivate
|
||||
//
|
||||
// fileprivate init() {
|
||||
//
|
||||
// self.executingCount.initialize(to: 0)
|
||||
// }
|
||||
//
|
||||
// deinit {
|
||||
//
|
||||
// self.executingCount.deinitialize(count: 1)
|
||||
// self.executingCount.deallocate()
|
||||
// }
|
||||
//
|
||||
// fileprivate func dispatch(_ action: @escaping () -> Void) {
|
||||
//
|
||||
// let count = OSAtomicIncrement32(self.executingCount)
|
||||
// if Thread.isMainThread && count == 1 {
|
||||
//
|
||||
// action()
|
||||
// OSAtomicDecrement32(executingCount)
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// DispatchQueue.main.async { [weak self] in
|
||||
//
|
||||
// guard let self = self else {
|
||||
//
|
||||
// return
|
||||
// }
|
||||
// action()
|
||||
// OSAtomicDecrement32(self.executingCount)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // MARK: Private
|
||||
//
|
||||
// private let executingCount: UnsafeMutablePointer<Int32> = .allocate(capacity: 1)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -23,8 +23,6 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
@@ -41,10 +39,9 @@ import AppKit
|
||||
|
||||
// MARK: - FetchedDiffableDataSourceSnapshot
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
internal protocol FetchedDiffableDataSourceSnapshotHandler: AnyObject {
|
||||
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangContentWith snapshot: NSDiffableDataSourceSnapshotReference)
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangContentWith snapshot: Internals.DiffableDataSourceSnapshot)
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +51,6 @@ extension Internals {
|
||||
|
||||
// MARK: - FetchedDiffableDataSourceSnapshotDelegate
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
internal final class FetchedDiffableDataSourceSnapshotDelegate: NSObject, NSFetchedResultsControllerDelegate {
|
||||
|
||||
// MARK: Internal
|
||||
@@ -77,18 +73,71 @@ extension Internals {
|
||||
self.fetchedResultsController?.delegate = nil
|
||||
}
|
||||
|
||||
internal func initialFetch() {
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
guard let fetchedResultsController = self.fetchedResultsController else {
|
||||
|
||||
return
|
||||
}
|
||||
self.controllerDidChangeContent(fetchedResultsController.dynamicCast())
|
||||
}
|
||||
|
||||
|
||||
// MARK: NSFetchedResultsControllerDelegate
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
@objc
|
||||
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
|
||||
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangContentWith: snapshot
|
||||
didChangContentWith: snapshot as NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
|
||||
)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@objc
|
||||
dynamic func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
|
||||
self.reloadedIDs = []
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangContentWith: FallbackDiffableDataSourceSnapshot(
|
||||
sections: controller.sections ?? []
|
||||
)
|
||||
)
|
||||
self.reloadedIDs = []
|
||||
}
|
||||
|
||||
// @objc
|
||||
// dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
|
||||
//
|
||||
//
|
||||
//// [1].difference(from: <#T##BidirectionalCollection#>)
|
||||
// let managedObject = anObject as! NSManagedObject
|
||||
// self.reloadedIDs.insert(managedObject.objectID)
|
||||
// }
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var reloadedIDs: Set<NSManagedObjectID> = []
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
import CoreData
|
||||
|
||||
#if canImport(UIKit)
|
||||
@@ -38,21 +36,170 @@ import AppKit
|
||||
|
||||
// MARK: - LiveList
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
public struct ListSnapshot<D: DynamicObject>: SnapshotResult, RandomAccessCollection, Hashable {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public subscript<S: Sequence>(indices: S) -> [ObjectType] where S.Element == Index {
|
||||
|
||||
|
||||
public typealias SectionID = String
|
||||
public typealias ItemID = D.ObjectID
|
||||
|
||||
public subscript<S: Sequence>(indices indices: S) -> [ObjectType] where S.Element == Index {
|
||||
|
||||
let context = self.context!
|
||||
let objectIDs = self.snapshotStruct.itemIdentifiers
|
||||
let objectIDs = self.diffableSnapshot.itemIdentifiers
|
||||
return indices.map { position in
|
||||
|
||||
|
||||
let objectID = objectIDs[position]
|
||||
return context.fetchExisting(objectID)!
|
||||
}
|
||||
}
|
||||
|
||||
public subscript(section sectionID: SectionID) -> [ObjectType] {
|
||||
|
||||
let context = self.context!
|
||||
let objectIDs = self.itemIdentifiers(inSection: sectionID)
|
||||
return objectIDs.map {
|
||||
|
||||
return context.fetchExisting($0)!
|
||||
}
|
||||
}
|
||||
|
||||
public subscript<S: Sequence>(section sectionID: SectionID, itemIndices itemIndices: S) -> [ObjectType] where S.Element == Int {
|
||||
|
||||
let context = self.context!
|
||||
let objectIDs = self.itemIdentifiers(inSection: sectionID)
|
||||
return itemIndices.map { position in
|
||||
|
||||
let objectID = objectIDs[position]
|
||||
return context.fetchExisting(objectID)!
|
||||
}
|
||||
}
|
||||
|
||||
public var numberOfItems: Int {
|
||||
|
||||
return self.diffableSnapshot.numberOfItems
|
||||
}
|
||||
|
||||
public var numberOfSections: Int {
|
||||
|
||||
return self.diffableSnapshot.numberOfSections
|
||||
}
|
||||
|
||||
public var sectionIdentifiers: [String] {
|
||||
|
||||
return self.diffableSnapshot.sectionIdentifiers as [String]
|
||||
}
|
||||
|
||||
public var itemIdentifiers: [ItemID] {
|
||||
|
||||
return self.diffableSnapshot.itemIdentifiers as [ItemID]
|
||||
}
|
||||
|
||||
public func numberOfItems(inSection identifier: SectionID) -> Int {
|
||||
|
||||
return self.diffableSnapshot.numberOfItems(inSection: identifier as NSString)
|
||||
}
|
||||
|
||||
public func itemIdentifiers(inSection identifier: SectionID) -> [ItemID] {
|
||||
|
||||
return self.diffableSnapshot.itemIdentifiers(inSection: identifier as NSString)
|
||||
}
|
||||
|
||||
public func itemIdentifiers(inSection identifier: SectionID, atIndices indices: IndexSet) -> [ItemID] {
|
||||
|
||||
let itemIDs = self.itemIdentifiers(inSection: identifier)
|
||||
return indices.map({ itemIDs[$0] })
|
||||
}
|
||||
|
||||
public func sectionIdentifier(containingItem identifier: ItemID) -> SectionID? {
|
||||
|
||||
return self.diffableSnapshot.sectionIdentifier(containingItem: identifier) as SectionID?
|
||||
}
|
||||
|
||||
public func indexOfItem(_ identifier: ItemID) -> Index? {
|
||||
|
||||
return self.diffableSnapshot.indexOfItem(identifier)
|
||||
}
|
||||
|
||||
public func indexOfSection(_ identifier: SectionID) -> Int? {
|
||||
|
||||
return self.diffableSnapshot.indexOfSection(identifier as NSString)
|
||||
}
|
||||
|
||||
public mutating func appendItems(_ identifiers: [ItemID], toSection sectionIdentifier: SectionID? = nil) {
|
||||
|
||||
self.diffableSnapshot.appendItems(identifiers, toSection: sectionIdentifier as NSString?)
|
||||
}
|
||||
|
||||
public mutating func insertItems(_ identifiers: [ItemID], beforeItem beforeIdentifier: ItemID) {
|
||||
|
||||
self.diffableSnapshot.insertItems(identifiers, beforeItem: beforeIdentifier)
|
||||
}
|
||||
|
||||
public mutating func insertItems(_ identifiers: [ItemID], afterItem afterIdentifier: ItemID) {
|
||||
|
||||
self.diffableSnapshot.insertItems(identifiers, afterItem: afterIdentifier)
|
||||
}
|
||||
|
||||
public mutating func deleteItems(_ identifiers: [ItemID]) {
|
||||
|
||||
self.diffableSnapshot.deleteItems(identifiers)
|
||||
}
|
||||
|
||||
public mutating func deleteAllItems() {
|
||||
|
||||
self.diffableSnapshot.deleteAllItems()
|
||||
}
|
||||
|
||||
public mutating func moveItem(_ identifier: ItemID, beforeItem toIdentifier: ItemID) {
|
||||
|
||||
self.diffableSnapshot.moveItem(identifier, beforeItem: toIdentifier)
|
||||
}
|
||||
|
||||
public mutating func moveItem(_ identifier: ItemID, afterItem toIdentifier: ItemID) {
|
||||
|
||||
self.diffableSnapshot.moveItem(identifier, afterItem: toIdentifier)
|
||||
}
|
||||
|
||||
public mutating func reloadItems(_ identifiers: [ItemID]) {
|
||||
|
||||
self.diffableSnapshot.reloadItems(identifiers)
|
||||
}
|
||||
|
||||
public mutating func appendSections(_ identifiers: [SectionID]) {
|
||||
|
||||
self.diffableSnapshot.appendSections(identifiers as [NSString])
|
||||
}
|
||||
|
||||
public mutating func insertSections(_ identifiers: [SectionID], beforeSection toIdentifier: SectionID) {
|
||||
|
||||
self.diffableSnapshot.insertSections(identifiers as [NSString], beforeSection: toIdentifier as NSString)
|
||||
}
|
||||
|
||||
public mutating func insertSections(_ identifiers: [SectionID], afterSection toIdentifier: SectionID) {
|
||||
|
||||
self.diffableSnapshot.insertSections(identifiers as [NSString], afterSection: toIdentifier as NSString)
|
||||
}
|
||||
|
||||
public mutating func deleteSections(_ identifiers: [SectionID]) {
|
||||
|
||||
self.diffableSnapshot.deleteSections(identifiers as [NSString])
|
||||
}
|
||||
|
||||
public mutating func moveSection(_ identifier: SectionID, beforeSection toIdentifier: SectionID) {
|
||||
|
||||
self.diffableSnapshot.moveSection(identifier as NSString, beforeSection: toIdentifier as NSString)
|
||||
}
|
||||
|
||||
public mutating func moveSection(_ identifier: SectionID, afterSection toIdentifier: SectionID) {
|
||||
|
||||
self.diffableSnapshot.moveSection(identifier as NSString, afterSection: toIdentifier as NSString)
|
||||
}
|
||||
|
||||
public mutating func reloadSections(_ identifiers: [SectionID]) {
|
||||
|
||||
self.diffableSnapshot.reloadSections(identifiers as [NSString])
|
||||
}
|
||||
|
||||
|
||||
// MARK: SnapshotResult
|
||||
@@ -64,18 +211,18 @@ public struct ListSnapshot<D: DynamicObject>: SnapshotResult, RandomAccessCollec
|
||||
|
||||
public var startIndex: Index {
|
||||
|
||||
return 0
|
||||
return self.diffableSnapshot.itemIdentifiers.startIndex
|
||||
}
|
||||
|
||||
public var endIndex: Index {
|
||||
|
||||
return self.snapshotStruct.numberOfItems
|
||||
return self.diffableSnapshot.itemIdentifiers.endIndex
|
||||
}
|
||||
|
||||
public subscript(position: Index) -> ObjectType {
|
||||
|
||||
let context = self.context!
|
||||
let objectID = self.snapshotStruct.itemIdentifiers[position]
|
||||
let objectID = self.diffableSnapshot.itemIdentifiers[position]
|
||||
return context.fetchExisting(objectID)!
|
||||
}
|
||||
|
||||
@@ -106,16 +253,14 @@ public struct ListSnapshot<D: DynamicObject>: SnapshotResult, RandomAccessCollec
|
||||
// MARK: Internal
|
||||
|
||||
internal init() {
|
||||
|
||||
self.snapshotReference = .init()
|
||||
self.snapshotStruct = self.snapshotReference as NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
|
||||
|
||||
self.diffableSnapshot = Internals.FallbackDiffableDataSourceSnapshot()
|
||||
self.context = nil
|
||||
}
|
||||
|
||||
internal init(snapshotReference: NSDiffableDataSourceSnapshotReference, context: NSManagedObjectContext) {
|
||||
|
||||
self.snapshotReference = snapshotReference
|
||||
self.snapshotStruct = snapshotReference as NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
|
||||
internal init(diffableSnapshot: Internals.DiffableDataSourceSnapshot, context: NSManagedObjectContext) {
|
||||
|
||||
self.diffableSnapshot = diffableSnapshot
|
||||
self.context = context
|
||||
}
|
||||
|
||||
@@ -123,9 +268,6 @@ public struct ListSnapshot<D: DynamicObject>: SnapshotResult, RandomAccessCollec
|
||||
// MARK: Private
|
||||
|
||||
private let id: UUID = .init()
|
||||
private let snapshotReference: NSDiffableDataSourceSnapshotReference
|
||||
private let snapshotStruct: NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
|
||||
private let context: NSManagedObjectContext?
|
||||
private var diffableSnapshot: Internals.DiffableDataSourceSnapshot
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -23,18 +23,8 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
import CoreData
|
||||
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
|
||||
#elseif canImport(AppKit)
|
||||
import AppKit
|
||||
|
||||
#endif
|
||||
|
||||
#if canImport(Combine)
|
||||
import Combine
|
||||
|
||||
@@ -48,7 +38,6 @@ import SwiftUI
|
||||
|
||||
// MARK: - LiveList
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
public final class LiveList<D: DynamicObject> {
|
||||
|
||||
// MARK: Public
|
||||
@@ -57,23 +46,22 @@ public final class LiveList<D: DynamicObject> {
|
||||
|
||||
willSet {
|
||||
|
||||
#if canImport(Combine)
|
||||
|
||||
let oldValue = self.snapshot
|
||||
guard newValue != oldValue else {
|
||||
guard #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) else {
|
||||
|
||||
return
|
||||
}
|
||||
#if canImport(Combine)
|
||||
|
||||
#if canImport(SwiftUI)
|
||||
withAnimation {
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
self.objectWillChange.send()
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -182,13 +170,20 @@ public final class LiveList<D: DynamicObject> {
|
||||
applyFetchClauses: applyFetchClauses
|
||||
)
|
||||
|
||||
#if canImport(Combine)
|
||||
self.rawObjectWillChange = ObservableObjectPublisher()
|
||||
if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) {
|
||||
|
||||
#else
|
||||
self.rawObjectWillChange = nil
|
||||
#if canImport(Combine)
|
||||
self.rawObjectWillChange = ObservableObjectPublisher()
|
||||
|
||||
#endif
|
||||
#else
|
||||
self.rawObjectWillChange = nil
|
||||
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
|
||||
self.rawObjectWillChange = nil
|
||||
}
|
||||
|
||||
|
||||
// if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
|
||||
@@ -278,14 +273,16 @@ public final class LiveList<D: DynamicObject> {
|
||||
|
||||
// MARK: - LiveList: FetchedDiffableDataSourceSnapshotHandler
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
extension LiveList: FetchedDiffableDataSourceSnapshotHandler {
|
||||
|
||||
// MARK: FetchedDiffableDataSourceSnapshotHandler
|
||||
|
||||
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
|
||||
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangContentWith snapshot: Internals.DiffableDataSourceSnapshot) {
|
||||
|
||||
self.snapshot = .init(snapshotReference: snapshot, context: controller.managedObjectContext)
|
||||
self.snapshot = .init(
|
||||
diffableSnapshot: snapshot,
|
||||
context: controller.managedObjectContext
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,5 +304,3 @@ extension LiveList: LiveResult {
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -23,143 +23,151 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(SwiftUI) && canImport(Combine)
|
||||
|
||||
import CoreData
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
|
||||
#warning("TODO: autoupdating doesn't work yet")
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
@propertyWrapper
|
||||
public struct LiveQuery<Result: LiveResult>: DynamicProperty {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@Environment(\.dataStack)
|
||||
public var dataStack: DataStack
|
||||
|
||||
public typealias ObjectType = Result.ObjectType
|
||||
|
||||
|
||||
// MARK: @propertyWrapper
|
||||
|
||||
public fileprivate(set) var wrappedValue: Result {
|
||||
|
||||
get {
|
||||
|
||||
return self.nonMutatingWrappedValue.wrappedValue
|
||||
}
|
||||
set {
|
||||
|
||||
self.nonMutatingWrappedValue = LazyNonmutating { newValue }
|
||||
}
|
||||
}
|
||||
|
||||
public var projectedValue: Result {
|
||||
|
||||
return self.wrappedValue
|
||||
}
|
||||
|
||||
|
||||
// MARK: DynamicProperty
|
||||
|
||||
public mutating func update() {
|
||||
|
||||
SwiftUI.withAnimation {
|
||||
|
||||
let dataStack = self.dataStack
|
||||
if self.set(dataStack: dataStack) {
|
||||
|
||||
return
|
||||
}
|
||||
self.wrappedValue = self.newWrappedValue(dataStack)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate let newWrappedValue: (DataStack) -> Result
|
||||
|
||||
fileprivate init(newWrappedValue: @escaping (DataStack) -> Result) {
|
||||
|
||||
self.newWrappedValue = newWrappedValue
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var nonMutatingWrappedValue: LazyNonmutating<Result> = .init { fatalError() }
|
||||
|
||||
private var currentDataStack: DataStack?
|
||||
|
||||
private mutating func set(dataStack: DataStack) -> Bool {
|
||||
|
||||
guard self.currentDataStack != dataStack else {
|
||||
|
||||
return false
|
||||
}
|
||||
self.currentDataStack = dataStack
|
||||
|
||||
let newWrappedValue = self.newWrappedValue
|
||||
self.nonMutatingWrappedValue = LazyNonmutating<Result> {
|
||||
|
||||
newWrappedValue(dataStack)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// MARK: - LazyNonmutating
|
||||
|
||||
fileprivate final class LazyNonmutating<Value> {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
lazy var wrappedValue: Value = self.initializer()
|
||||
|
||||
init(_ initializer: @escaping () -> Value) {
|
||||
|
||||
self.initializer = initializer
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var initializer: () -> Value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
extension LiveQuery {
|
||||
|
||||
public init<D: DynamicObject>(liveList: LiveList<D>) where Result == LiveList<D> {
|
||||
|
||||
self.init(
|
||||
newWrappedValue: { _ in liveList }
|
||||
)
|
||||
}
|
||||
|
||||
public init<D: DynamicObject>(_ clauseChain: FetchChainBuilder<D>) where Result == LiveList<D> {
|
||||
|
||||
self.init(
|
||||
newWrappedValue: { $0.liveList(clauseChain) }
|
||||
)
|
||||
}
|
||||
|
||||
public init<D: DynamicObject>(_ clauseChain: SectionMonitorChainBuilder<D>) where Result == LiveList<D> {
|
||||
|
||||
self.init(
|
||||
newWrappedValue: { $0.liveList(clauseChain) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
//#if canImport(SwiftUI) && canImport(Combine)
|
||||
//
|
||||
//import CoreData
|
||||
//import Combine
|
||||
//import SwiftUI
|
||||
//
|
||||
//
|
||||
//#warning("TODO: autoupdating doesn't work yet")
|
||||
//@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
//@propertyWrapper
|
||||
//public struct LiveQuery<Result: LiveResult>: DynamicProperty {
|
||||
//
|
||||
// // MARK: Public
|
||||
//
|
||||
// @Environment(\.dataStack)
|
||||
// public var dataStack: DataStack
|
||||
//
|
||||
// public typealias ObjectType = Result.ObjectType
|
||||
//
|
||||
//
|
||||
// // MARK: @propertyWrapper
|
||||
//
|
||||
// public var wrappedValue: Result {
|
||||
//
|
||||
// get {
|
||||
//
|
||||
// return self.backingWrapper!.wrappedValue
|
||||
// }
|
||||
// set {
|
||||
//
|
||||
// self.backingWrapper!.wrappedValue = newValue
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public var projectedValue: ObservedObject<Result>.Wrapper {
|
||||
//
|
||||
// return self.backingWrapper!.projectedValue
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // MARK: DynamicProperty
|
||||
//
|
||||
// public mutating func update() {
|
||||
//
|
||||
// SwiftUI.withAnimation {
|
||||
//
|
||||
// let dataStack = self.dataStack
|
||||
//
|
||||
// if self.backingWrapper == nil {
|
||||
//
|
||||
// self.backingWrapper = ObservedObject(wrappedValue: self.newLiveResult(dataStack))
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// self.backingWrapper!.wrappedValue = self.newLiveResult(dataStack)
|
||||
// }
|
||||
// self.backingWrapper!.update()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // MARK: FilePrivate
|
||||
//
|
||||
// fileprivate let newLiveResult: (DataStack) -> Result
|
||||
//
|
||||
// fileprivate init(newLiveResult: @escaping (DataStack) -> Result) {
|
||||
//
|
||||
// self.newLiveResult = newLiveResult
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // MARK: Private
|
||||
//
|
||||
//// private var nonMutatingWrappedValue: LazyNonmutating<Result> = .init { fatalError() }
|
||||
//
|
||||
// private var currentDataStack: DataStack?
|
||||
// private var backingWrapper: ObservedObject<Result>?
|
||||
//
|
||||
// private mutating func set(dataStack: DataStack) -> Bool {
|
||||
//
|
||||
// guard self.currentDataStack != dataStack else {
|
||||
//
|
||||
// return false
|
||||
// }
|
||||
// self.currentDataStack = dataStack
|
||||
// if self.backingWrapper == nil {
|
||||
//
|
||||
// self.backingWrapper = ObservedObject(wrappedValue: self.newLiveResult(dataStack))
|
||||
// }
|
||||
// else {
|
||||
//
|
||||
// self.backingWrapper?.wrappedValue = self.newLiveResult(dataStack)
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // MARK: - LazyNonmutating
|
||||
//
|
||||
// fileprivate final class LazyNonmutating<Value> {
|
||||
//
|
||||
// // MARK: FilePrivate
|
||||
//
|
||||
// lazy var wrappedValue: Value = self.initializer()
|
||||
//
|
||||
// init(_ initializer: @escaping () -> Value) {
|
||||
//
|
||||
// self.initializer = initializer
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // MARK: Private
|
||||
//
|
||||
// private var initializer: () -> Value
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//#if canImport(UIKit) || canImport(AppKit)
|
||||
//
|
||||
//@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
|
||||
//extension LiveQuery {
|
||||
//
|
||||
// public init<D: DynamicObject>(liveList: LiveList<D>) where Result == LiveList<D> {
|
||||
//
|
||||
// self.init(
|
||||
// newLiveResult: { _ in liveList }
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// public init<D: DynamicObject>(_ clauseChain: FetchChainBuilder<D>) where Result == LiveList<D> {
|
||||
//
|
||||
// self.init(
|
||||
// newLiveResult: { $0.liveList(clauseChain) }
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// public init<D: DynamicObject>(_ clauseChain: SectionMonitorChainBuilder<D>) where Result == LiveList<D> {
|
||||
//
|
||||
// self.init(
|
||||
// newLiveResult: { $0.liveList(clauseChain) }
|
||||
// )
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//#endif
|
||||
//
|
||||
//#endif
|
||||
|
||||
Reference in New Issue
Block a user