From c17673240124f466fb2133a861001422d7815d8c Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 5 Apr 2018 00:21:49 -0300 Subject: [PATCH 01/18] Initial implementation of NSTableView convenience extensions --- .../AlecrimCoreData.xcodeproj/project.pbxproj | 6 +- .../Convenience/NSTableView+Extensions.swift | 177 ++++++++++++++++++ 2 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift diff --git a/Source/AlecrimCoreData.xcodeproj/project.pbxproj b/Source/AlecrimCoreData.xcodeproj/project.pbxproj index 680ae73..b0a52f9 100644 --- a/Source/AlecrimCoreData.xcodeproj/project.pbxproj +++ b/Source/AlecrimCoreData.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 1420DF9E2054FFBC00B34160 /* AlecrimCoreData.h in Headers */ = {isa = PBXBuildFile; fileRef = 1420DF9C2054FFBC00B34160 /* AlecrimCoreData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 14B9460020759D0D00A7CFFD /* NSTableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */; }; 14CC3374205B28CA00BA682A /* NSArrayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */; }; 14CC3375205B28CA00BA682A /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */; }; 14CC3376205B28CA00BA682A /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14CC3360205B28CA00BA682A /* UICollectionView+Extensions.swift */; }; @@ -32,6 +33,7 @@ 1420DF992054FFBC00B34160 /* AlecrimCoreData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AlecrimCoreData.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1420DF9C2054FFBC00B34160 /* AlecrimCoreData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AlecrimCoreData.h; sourceTree = ""; }; 1420DF9D2054FFBC00B34160 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Extensions.swift"; sourceTree = ""; }; 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSArrayController+Extensions.swift"; sourceTree = ""; }; 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+Extensions.swift"; sourceTree = ""; }; 14CC3360205B28CA00BA682A /* UICollectionView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extensions.swift"; sourceTree = ""; }; @@ -102,10 +104,11 @@ 14CC335D205B28CA00BA682A /* Convenience */ = { isa = PBXGroup; children = ( - 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */, 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */, 14CC3360205B28CA00BA682A /* UICollectionView+Extensions.swift */, + 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */, 14CC3361205B28CA00BA682A /* NSCollectionView+Extensions.swift */, + 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */, ); path = Convenience; sourceTree = ""; @@ -237,6 +240,7 @@ 14CC337E205B28CA00BA682A /* Predicate.swift in Sources */, 14CC337D205B28CA00BA682A /* FetchRequest.swift in Sources */, 14CC3381205B28CA00BA682A /* KeyPath.swift in Sources */, + 14B9460020759D0D00A7CFFD /* NSTableView+Extensions.swift in Sources */, 14CC337F205B28CA00BA682A /* SortDescriptor.swift in Sources */, 14CC337A205B28CA00BA682A /* ManagedObject.swift in Sources */, 14CC3382205B28CA00BA682A /* Queryable.swift in Sources */, diff --git a/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift b/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift new file mode 100644 index 0000000..57db939 --- /dev/null +++ b/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift @@ -0,0 +1,177 @@ +// +// NSTableView+Extensions.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 04/04/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +#if os(macOS) + +import Foundation +import Cocoa + +extension FetchRequestController { + + @discardableResult + public func bind(to tableView: NSTableView, animationOptions: NSTableView.AnimationOptions = .effectFade, sectionOffset: Int = 0, cellConfigurationHandler: ((CellType, IndexPath) -> Void)? = nil) -> Self { + let insertedSectionIndexes = NSMutableIndexSet() + let deletedSectionIndexes = NSMutableIndexSet() + let updatedSectionIndexes = NSMutableIndexSet() + + var insertedItemIndexPaths = [IndexPath]() + var deletedItemIndexPaths = [IndexPath]() + var updatedItemIndexPaths = [IndexPath]() + + var reloadData = false + + // + func reset() { + insertedSectionIndexes.removeAllIndexes() + deletedSectionIndexes.removeAllIndexes() + updatedSectionIndexes.removeAllIndexes() + + insertedItemIndexPaths.removeAll(keepingCapacity: false) + deletedItemIndexPaths.removeAll(keepingCapacity: false) + updatedItemIndexPaths.removeAll(keepingCapacity: false) + + reloadData = false + } + + // + self + .needsReloadData { + reloadData = true + } + .willChangeContent { + if !reloadData { + // + reset() + } + } + .didInsertSection { sectionInfo, sectionIndex in + if !reloadData { + insertedSectionIndexes.add(sectionIndex + sectionOffset) + } + } + .didDeleteSection { sectionInfo, sectionIndex in + if !reloadData { + deletedSectionIndexes.add(sectionIndex + sectionOffset) + deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex} + updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex} + } + } + .didInsertObject { entity, newIndexPath in + if !reloadData { + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + + if !insertedSectionIndexes.contains(newIndexPath.section) { + insertedItemIndexPaths.append(newIndexPath) + } + } + } + .didDeleteObject { entity, indexPath in + if !reloadData { + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + + if !deletedSectionIndexes.contains(indexPath.section) { + deletedItemIndexPaths.append(indexPath) + } + } + } + .didUpdateObject { entity, indexPath in + if !reloadData { + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + + if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { + updatedItemIndexPaths.append(indexPath) + } + } + } + .didMoveObject { entity, indexPath, newIndexPath in + if !reloadData { + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + + if newIndexPath == indexPath { + if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { + updatedItemIndexPaths.append(indexPath) + } + } + else { + if !deletedSectionIndexes.contains(indexPath.section) { + deletedItemIndexPaths.append(indexPath) + } + + if !insertedSectionIndexes.contains(newIndexPath.section) { + insertedItemIndexPaths.append(newIndexPath) + } + } + } + } + .didChangeContent { [weak tableView] in + // + defer { reset() } + + // + guard let tableView = tableView else { + return + } + + // + if reloadData { + tableView.reloadData() + } + else { + tableView.beginUpdates() + +// if deletedSectionIndexes.count > 0 { +// tableView.deleteSections(deletedSectionIndexes as IndexSet, with: animationOptions) +// } + +// if insertedSectionIndexes.count > 0 { +// tableView.insertSections(insertedSectionIndexes as IndexSet, with: animationOptions) +// } + +// if updatedSectionIndexes.count > 0 { +// tableView.reloadSections(updatedSectionIndexes as IndexSet, with: animationOptions) +// } + + if deletedItemIndexPaths.count > 0 { + let deletedRowsIndexSet = IndexSet(deletedItemIndexPaths.map { $0.item }) + tableView.removeRows(at: deletedRowsIndexSet, withAnimation: animationOptions) + } + + if insertedItemIndexPaths.count > 0 { + let insertedRowsIndexSet = IndexSet(insertedItemIndexPaths.map { $0.item }) + tableView.insertRows(at: insertedRowsIndexSet, withAnimation: animationOptions) + } + + tableView.endUpdates() + + if updatedItemIndexPaths.count > 0 && cellConfigurationHandler == nil { + let updatedItemIndexSet = IndexSet(updatedItemIndexPaths.map { $0.item }) + tableView.reloadData(forRowIndexes: updatedItemIndexSet, columnIndexes: IndexSet()) + } + + if let cellConfigurationHandler = cellConfigurationHandler { + for updatedItemIndexPath in updatedItemIndexPaths { + if let cell = tableView.view(atColumn: 0, row: updatedItemIndexPath.item, makeIfNecessary: false) as? CellType { + cellConfigurationHandler(cell, updatedItemIndexPath) + } + } + } + } + } + + // + tableView.reloadData() + + // + return self + } + +} + +#endif + From fd059cefc84fb3c729d68219fb98cff1bdfda560 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Wed, 11 Apr 2018 03:08:55 -0300 Subject: [PATCH 02/18] Opened main persistent container methods and properties --- .../Core/Container/PersistentContainer.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift b/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift index 941cc60..b8e111f 100644 --- a/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift +++ b/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift @@ -160,16 +160,15 @@ open class GenericPersistentContainer { // MARK: - - public final var viewContext: Context { + open var viewContext: Context { return unsafeDowncast(self.rawValue.viewContext, to: Context.self) } - public final func newBackgroundContext() -> Context { + open func newBackgroundContext() -> Context { return unsafeDowncast(self.rawValue.newBackgroundContext(), to: Context.self) - } - public final func performBackgroundTask(_ block: @escaping (Context) -> Void) { + open func performBackgroundTask(_ block: @escaping (Context) -> Void) { self.rawValue.performBackgroundTask { managedObjectContext in block(unsafeDowncast(managedObjectContext, to: Context.self)) } From 68360ff64ab2178735bd1b3760e0da22f3c15a32 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 12 Apr 2018 18:49:22 -0300 Subject: [PATCH 03/18] Persistent container changes (CoreData does not call `newBackgroundContext` in `performBackgroundTask` method as assumed before.) --- .../Core/Container/PersistentContainer.swift | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift b/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift index b8e111f..33f2819 100644 --- a/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift +++ b/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift @@ -107,6 +107,17 @@ open class PersistentContainer: NSPersistentContainer { return context } + + open override func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) { + super.performBackgroundTask { context in + // + context.automaticallyMergesChangesFromParent = true + context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + + // + block(context) + } + } } // MARK: - @@ -140,6 +151,20 @@ open class GenericPersistentContainer { return context } + + fileprivate override func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) { + super.performBackgroundTask { context in + // + context.persistentStoreCoordinator = self.persistentStoreCoordinator + + // + context.automaticallyMergesChangesFromParent = true + context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + + // + block(context) + } + } } From d5c434f015d36b248a2ea748d49c21be936e67a1 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 19 Apr 2018 14:47:34 -0300 Subject: [PATCH 04/18] Rename `take` and `skip` to `dropFirst` and `prefix` respectively to match Swift names --- README.md | 2 +- .../Core/Query/FetchRequest.swift | 4 +- Source/AlecrimCoreData/Core/Query/Query.swift | 39 +++++++++++++++---- .../Core/Query/Queryable.swift | 4 +- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 91db61d..d6dfec5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ let query = persistentContainer.viewContext.people .where { \.city == "Piracicaba" } .orderBy { \.name } -for person in query.skip(20).take(10) { +for person in query.dropFirst(20).prefix(10) { print(person.name, person.address) } ``` diff --git a/Source/AlecrimCoreData/Core/Query/FetchRequest.swift b/Source/AlecrimCoreData/Core/Query/FetchRequest.swift index 94b78aa..a4bb8d3 100644 --- a/Source/AlecrimCoreData/Core/Query/FetchRequest.swift +++ b/Source/AlecrimCoreData/Core/Query/FetchRequest.swift @@ -47,14 +47,14 @@ public struct FetchRequest: Queryable { extension FetchRequest { - public func skip(_ offset: Int) -> FetchRequest { + public func dropFirst(_ n: Int) -> FetchRequest { var clone = self clone.offset = offset return clone } - public func take(_ limit: Int) -> FetchRequest { + public func prefix(_ maxLength: Int) -> FetchRequest { var clone = self clone.limit = limit diff --git a/Source/AlecrimCoreData/Core/Query/Query.swift b/Source/AlecrimCoreData/Core/Query/Query.swift index b34ea90..3bc8e3e 100644 --- a/Source/AlecrimCoreData/Core/Query/Query.swift +++ b/Source/AlecrimCoreData/Core/Query/Query.swift @@ -55,7 +55,7 @@ extension Query { extension Query { public func first() -> Entity? { - return self.execute(fetchRequest: self.fetchRequest.take(1)).first + return self.execute(fetchRequest: self.fetchRequest.prefix(1)).first } } @@ -108,13 +108,14 @@ extension Query { // MARK: - extension Query: Sequence { - + + // MARK: - + public func makeIterator() -> QueryIterator { return QueryIterator(self.execute()) } public struct QueryIterator: IteratorProtocol { - private let entities: [Entity] private var index: Int @@ -133,7 +134,29 @@ extension Query: Sequence { return self.entities[index] } } - + + // MARK: - + + public func dropLast(_ n: Int) -> Query { + fatalError("Not applicable or not available.") + } + + public func drop(while predicate: (Entity) throws -> Bool) rethrows -> Query { + fatalError("Not applicable or not available.") + } + + public func prefix(while predicate: (Entity) throws -> Bool) rethrows -> Query { + fatalError("Not applicable or not available.") + } + + public func suffix(_ maxLength: Int) -> Query { + fatalError("Not applicable or not available.") + } + + public func split(maxSplits: Int, omittingEmptySubsequences: Bool, whereSeparator isSeparator: (Entity) throws -> Bool) rethrows -> [Query] { + fatalError("Not applicable or not available.") + } + } // MARK: - @@ -197,16 +220,16 @@ extension Query { extension Query: Queryable { - public func skip(_ offset: Int) -> Query { + public func dropFirst(_ n: Int) -> Query { var clone = self - clone.fetchRequest = clone.fetchRequest.skip(offset) + clone.fetchRequest = clone.fetchRequest.dropFirst(n) return clone } - public func take(_ limit: Int) -> Query { + public func prefix(_ maxLength: Int) -> Query { var clone = self - clone.fetchRequest = clone.fetchRequest.take(limit) + clone.fetchRequest = clone.fetchRequest.prefix(maxLength) return clone } diff --git a/Source/AlecrimCoreData/Core/Query/Queryable.swift b/Source/AlecrimCoreData/Core/Query/Queryable.swift index 5137e29..692ba48 100644 --- a/Source/AlecrimCoreData/Core/Query/Queryable.swift +++ b/Source/AlecrimCoreData/Core/Query/Queryable.swift @@ -15,8 +15,8 @@ public protocol Queryable { associatedtype Element: ManagedObject - func skip(_ offset: Int) -> Self - func take(_ limit: Int) -> Self + func dropFirst(_ n: Int) -> Self + func prefix(_ maxLength: Int) -> Self func setBatchSize(_ batchSize: Int) -> Self func filtered(using predicate: Predicate) -> Self From c7304f5c0308702fdc6b60dd9ee517d77a9eb20e Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Fri, 20 Apr 2018 23:40:03 -0300 Subject: [PATCH 05/18] Updated the convenience `FetchRequestController` `bind` method to discourage passing a func as configuration handler. Also added a warning about this. --- .../NSCollectionView+Extensions.swift | 282 +++++++++--------- .../Convenience/NSTableView+Extensions.swift | 30 +- .../UICollectionView+Extensions.swift | 278 ++++++++--------- .../Convenience/UITableView+Extensions.swift | 277 ++++++++--------- 4 files changed, 437 insertions(+), 430 deletions(-) diff --git a/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift b/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift index 1b20baf..2c21d1e 100644 --- a/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift @@ -7,168 +7,170 @@ // #if os(macOS) - - import Foundation - import AppKit - - extension FetchRequestController { - - @discardableResult - public func bind(to collectionView: NSCollectionView, sectionOffset: Int = 0, cellConfigurationHandler: ((ItemType, IndexPath) -> Void)? = nil) -> Self { - let insertedSectionIndexes = NSMutableIndexSet() - let deletedSectionIndexes = NSMutableIndexSet() - let updatedSectionIndexes = NSMutableIndexSet() - - var insertedItemIndexPaths = [IndexPath]() - var deletedItemIndexPaths = [IndexPath]() - var updatedItemIndexPaths = [IndexPath]() - - var reloadData = false - - // - func reset() { - insertedSectionIndexes.removeAllIndexes() - deletedSectionIndexes.removeAllIndexes() - updatedSectionIndexes.removeAllIndexes() - - insertedItemIndexPaths.removeAll(keepingCapacity: false) - deletedItemIndexPaths.removeAll(keepingCapacity: false) - updatedItemIndexPaths.removeAll(keepingCapacity: false) - - reloadData = false + +import Foundation +import AppKit + +extension FetchRequestController { + + /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. + @discardableResult + public func bind(to collectionView: NSCollectionView, sectionOffset: Int = 0, itemConfigurationHandler: ((NSCollectionViewItem, IndexPath) -> Void)? = nil) -> Self { + let insertedSectionIndexes = NSMutableIndexSet() + let deletedSectionIndexes = NSMutableIndexSet() + let updatedSectionIndexes = NSMutableIndexSet() + + var insertedItemIndexPaths = [IndexPath]() + var deletedItemIndexPaths = [IndexPath]() + var updatedItemIndexPaths = [IndexPath]() + + var reloadData = false + + // + func reset() { + insertedSectionIndexes.removeAllIndexes() + deletedSectionIndexes.removeAllIndexes() + updatedSectionIndexes.removeAllIndexes() + + insertedItemIndexPaths.removeAll(keepingCapacity: false) + deletedItemIndexPaths.removeAll(keepingCapacity: false) + updatedItemIndexPaths.removeAll(keepingCapacity: false) + + reloadData = false + } + + // + self + .needsReloadData { + reloadData = true } - - // - self - .needsReloadData { - reloadData = true + .willChangeContent { + if !reloadData { + reset() } - .willChangeContent { - if !reloadData { - reset() - } + } + .didInsertSection { sectionInfo, sectionIndex in + if !reloadData { + insertedSectionIndexes.add(sectionIndex + sectionOffset) } - .didInsertSection { sectionInfo, sectionIndex in - if !reloadData { - insertedSectionIndexes.add(sectionIndex + sectionOffset) - } + } + .didDeleteSection { sectionInfo, sectionIndex in + if !reloadData { + deletedSectionIndexes.add(sectionIndex + sectionOffset) + deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex } + updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex } } - .didDeleteSection { sectionInfo, sectionIndex in - if !reloadData { - deletedSectionIndexes.add(sectionIndex + sectionOffset) - deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex } - updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex } + } + .didInsertObject { entity, newIndexPath in + if !reloadData { + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + + if !insertedSectionIndexes.contains(newIndexPath.section) { + insertedItemIndexPaths.append(newIndexPath) } } - .didInsertObject { entity, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } + } + .didDeleteObject { entity, indexPath in + if !reloadData { + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + + if !deletedSectionIndexes.contains(indexPath.section) { + deletedItemIndexPaths.append(indexPath) } } - .didDeleteObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } + } + .didUpdateObject { entity, indexPath in + if !reloadData { + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + + if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { + updatedItemIndexPaths.append(indexPath) } } - .didUpdateObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - + } + .didMoveObject { entity, indexPath, newIndexPath in + if !reloadData { + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + + if newIndexPath == indexPath { if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { updatedItemIndexPaths.append(indexPath) } } - } - .didMoveObject { entity, indexPath, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if newIndexPath == indexPath { - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } + else { + if !deletedSectionIndexes.contains(indexPath.section) { + deletedItemIndexPaths.append(indexPath) } - else { - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } - - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } + + if !insertedSectionIndexes.contains(newIndexPath.section) { + insertedItemIndexPaths.append(newIndexPath) } } } - .didChangeContent { [weak collectionView] in - // - guard let collectionView = collectionView else { - reset() - return - } - - // - if reloadData { - collectionView.reloadData() - reset() - } - else { - collectionView.performBatchUpdates({ - if deletedSectionIndexes.count > 0 { - collectionView.deleteSections(deletedSectionIndexes as IndexSet) - } - - if insertedSectionIndexes.count > 0 { - collectionView.insertSections(insertedSectionIndexes as IndexSet) - } - - if updatedSectionIndexes.count > 0 { - collectionView.reloadSections(updatedSectionIndexes as IndexSet) - } - - if deletedItemIndexPaths.count > 0 { - collectionView.deleteItems(at: Set(deletedItemIndexPaths)) - } - - if insertedItemIndexPaths.count > 0 { - collectionView.insertItems(at: Set(insertedItemIndexPaths)) - } - - if updatedItemIndexPaths.count > 0 && cellConfigurationHandler == nil { - collectionView.reloadItems(at: Set(updatedItemIndexPaths)) - } - }, completionHandler: { finished in - if finished { - if let cellConfigurationHandler = cellConfigurationHandler { - for updatedItemIndexPath in updatedItemIndexPaths { - if let item = collectionView.item(at: updatedItemIndexPath) as? ItemType { - cellConfigurationHandler(item, updatedItemIndexPath) - } + } + .didChangeContent { [weak collectionView] in + // + guard let collectionView = collectionView else { + reset() + return + } + + // + if reloadData { + collectionView.reloadData() + reset() + } + else { + collectionView.performBatchUpdates({ + if deletedSectionIndexes.count > 0 { + collectionView.deleteSections(deletedSectionIndexes as IndexSet) + } + + if insertedSectionIndexes.count > 0 { + collectionView.insertSections(insertedSectionIndexes as IndexSet) + } + + if updatedSectionIndexes.count > 0 { + collectionView.reloadSections(updatedSectionIndexes as IndexSet) + } + + if deletedItemIndexPaths.count > 0 { + collectionView.deleteItems(at: Set(deletedItemIndexPaths)) + } + + if insertedItemIndexPaths.count > 0 { + collectionView.insertItems(at: Set(insertedItemIndexPaths)) + } + + if updatedItemIndexPaths.count > 0 && itemConfigurationHandler == nil { + collectionView.reloadItems(at: Set(updatedItemIndexPaths)) + } + }, completionHandler: { finished in + if finished { + if let itemConfigurationHandler = itemConfigurationHandler { + for updatedItemIndexPath in updatedItemIndexPaths { + if let item = collectionView.item(at: updatedItemIndexPath) { + itemConfigurationHandler(item, updatedItemIndexPath) } } - - reset() } - }) - } - } - - // - collectionView.reloadData() - - // - return self + + reset() + } + }) + } } - + + // + collectionView.reloadData() + + // + return self } - + +} + #endif + diff --git a/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift b/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift index 57db939..7cca1bf 100644 --- a/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift @@ -13,8 +13,9 @@ import Cocoa extension FetchRequestController { + /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. @discardableResult - public func bind(to tableView: NSTableView, animationOptions: NSTableView.AnimationOptions = .effectFade, sectionOffset: Int = 0, cellConfigurationHandler: ((CellType, IndexPath) -> Void)? = nil) -> Self { + public func bind(to tableView: NSTableView, animationOptions: NSTableView.AnimationOptions = .effectFade, sectionOffset: Int = 0, cellViewConfigurationHandler: ((NSTableCellView, IndexPath) -> Void)? = nil) -> Self { let insertedSectionIndexes = NSMutableIndexSet() let deletedSectionIndexes = NSMutableIndexSet() let updatedSectionIndexes = NSMutableIndexSet() @@ -125,17 +126,17 @@ extension FetchRequestController { else { tableView.beginUpdates() -// if deletedSectionIndexes.count > 0 { -// tableView.deleteSections(deletedSectionIndexes as IndexSet, with: animationOptions) -// } + // if deletedSectionIndexes.count > 0 { + // tableView.deleteSections(deletedSectionIndexes as IndexSet, with: animationOptions) + // } -// if insertedSectionIndexes.count > 0 { -// tableView.insertSections(insertedSectionIndexes as IndexSet, with: animationOptions) -// } + // if insertedSectionIndexes.count > 0 { + // tableView.insertSections(insertedSectionIndexes as IndexSet, with: animationOptions) + // } -// if updatedSectionIndexes.count > 0 { -// tableView.reloadSections(updatedSectionIndexes as IndexSet, with: animationOptions) -// } + // if updatedSectionIndexes.count > 0 { + // tableView.reloadSections(updatedSectionIndexes as IndexSet, with: animationOptions) + // } if deletedItemIndexPaths.count > 0 { let deletedRowsIndexSet = IndexSet(deletedItemIndexPaths.map { $0.item }) @@ -149,15 +150,15 @@ extension FetchRequestController { tableView.endUpdates() - if updatedItemIndexPaths.count > 0 && cellConfigurationHandler == nil { + if updatedItemIndexPaths.count > 0 && cellViewConfigurationHandler == nil { let updatedItemIndexSet = IndexSet(updatedItemIndexPaths.map { $0.item }) tableView.reloadData(forRowIndexes: updatedItemIndexSet, columnIndexes: IndexSet()) } - if let cellConfigurationHandler = cellConfigurationHandler { + if let cellViewConfigurationHandler = cellViewConfigurationHandler { for updatedItemIndexPath in updatedItemIndexPaths { - if let cell = tableView.view(atColumn: 0, row: updatedItemIndexPath.item, makeIfNecessary: false) as? CellType { - cellConfigurationHandler(cell, updatedItemIndexPath) + if let cell = tableView.view(atColumn: 0, row: updatedItemIndexPath.item, makeIfNecessary: false) as? NSTableCellView { + cellViewConfigurationHandler(cell, updatedItemIndexPath) } } } @@ -175,3 +176,4 @@ extension FetchRequestController { #endif + diff --git a/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift b/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift index 3a1af49..b101103 100644 --- a/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift @@ -7,166 +7,168 @@ // #if os(iOS) || os(tvOS) - - import Foundation - import UIKit - - extension FetchRequestController { - - @discardableResult - public func bind(to collectionView: UICollectionView, sectionOffset: Int = 0, cellConfigurationHandler: ((CellType, IndexPath) -> Void)? = nil) -> Self { - let insertedSectionIndexes = NSMutableIndexSet() - let deletedSectionIndexes = NSMutableIndexSet() - let updatedSectionIndexes = NSMutableIndexSet() - - var insertedItemIndexPaths = [IndexPath]() - var deletedItemIndexPaths = [IndexPath]() - var updatedItemIndexPaths = [IndexPath]() - - var reloadData = false - - // - func reset() { - insertedSectionIndexes.removeAllIndexes() - deletedSectionIndexes.removeAllIndexes() - updatedSectionIndexes.removeAllIndexes() - - insertedItemIndexPaths.removeAll(keepingCapacity: false) - deletedItemIndexPaths.removeAll(keepingCapacity: false) - updatedItemIndexPaths.removeAll(keepingCapacity: false) - - reloadData = false + +import Foundation +import UIKit + +extension FetchRequestController { + + /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. + @discardableResult + public func bind(to collectionView: UICollectionView, sectionOffset: Int = 0, cellConfigurationHandler: ((UICollectionViewCell, IndexPath) -> Void)? = nil) -> Self { + let insertedSectionIndexes = NSMutableIndexSet() + let deletedSectionIndexes = NSMutableIndexSet() + let updatedSectionIndexes = NSMutableIndexSet() + + var insertedItemIndexPaths = [IndexPath]() + var deletedItemIndexPaths = [IndexPath]() + var updatedItemIndexPaths = [IndexPath]() + + var reloadData = false + + // + func reset() { + insertedSectionIndexes.removeAllIndexes() + deletedSectionIndexes.removeAllIndexes() + updatedSectionIndexes.removeAllIndexes() + + insertedItemIndexPaths.removeAll(keepingCapacity: false) + deletedItemIndexPaths.removeAll(keepingCapacity: false) + updatedItemIndexPaths.removeAll(keepingCapacity: false) + + reloadData = false + } + + // + self + .needsReloadData { + reloadData = true } - - // - self - .needsReloadData { - reloadData = true + .willChangeContent { + if !reloadData { + reset() } - .willChangeContent { - if !reloadData { - reset() - } + } + .didInsertSection { sectionInfo, sectionIndex in + if !reloadData { + insertedSectionIndexes.add(sectionIndex + sectionOffset) } - .didInsertSection { sectionInfo, sectionIndex in - if !reloadData { - insertedSectionIndexes.add(sectionIndex + sectionOffset) - } + } + .didDeleteSection { sectionInfo, sectionIndex in + if !reloadData { + deletedSectionIndexes.add(sectionIndex + sectionOffset) + deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex } + updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex } } - .didDeleteSection { sectionInfo, sectionIndex in - if !reloadData { - deletedSectionIndexes.add(sectionIndex + sectionOffset) - deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex } - updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex } + } + .didInsertObject { entity, newIndexPath in + if !reloadData { + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + + if !insertedSectionIndexes.contains(newIndexPath.section) { + insertedItemIndexPaths.append(newIndexPath) } } - .didInsertObject { entity, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } + } + .didDeleteObject { entity, indexPath in + if !reloadData { + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + + if !deletedSectionIndexes.contains(indexPath.section) { + deletedItemIndexPaths.append(indexPath) } } - .didDeleteObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } + } + .didUpdateObject { entity, indexPath in + if !reloadData { + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + + if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { + updatedItemIndexPaths.append(indexPath) } } - .didUpdateObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - + } + .didMoveObject { entity, indexPath, newIndexPath in + if !reloadData { + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + + if newIndexPath == indexPath { if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { updatedItemIndexPaths.append(indexPath) } } - } - .didMoveObject { entity, indexPath, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if newIndexPath == indexPath { - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } + else { + if !deletedSectionIndexes.contains(indexPath.section) { + deletedItemIndexPaths.append(indexPath) } - else { - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } - - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } + + if !insertedSectionIndexes.contains(newIndexPath.section) { + insertedItemIndexPaths.append(newIndexPath) } } } - .didChangeContent { [weak collectionView] in - guard let collectionView = collectionView else { - reset() - return - } - - if reloadData { - collectionView.reloadData() - reset() - } - else { - collectionView.performBatchUpdates({ - if deletedSectionIndexes.count > 0 { - collectionView.deleteSections(deletedSectionIndexes as IndexSet) - } - - if insertedSectionIndexes.count > 0 { - collectionView.insertSections(insertedSectionIndexes as IndexSet) - } - - if updatedSectionIndexes.count > 0 { - collectionView.reloadSections(updatedSectionIndexes as IndexSet) - } - - if deletedItemIndexPaths.count > 0 { - collectionView.deleteItems(at: deletedItemIndexPaths) - } - - if insertedItemIndexPaths.count > 0 { - collectionView.insertItems(at: insertedItemIndexPaths) - } - - if updatedItemIndexPaths.count > 0 && cellConfigurationHandler == nil { - collectionView.reloadItems(at: updatedItemIndexPaths) - } - }, completion: { finished in - if finished { - if let cellConfigurationHandler = cellConfigurationHandler { - for updatedItemIndexPath in updatedItemIndexPaths { - if let cell = collectionView.cellForItem(at: updatedItemIndexPath) as? CellType { - cellConfigurationHandler(cell, updatedItemIndexPath) - } + } + .didChangeContent { [weak collectionView] in + guard let collectionView = collectionView else { + reset() + return + } + + if reloadData { + collectionView.reloadData() + reset() + } + else { + collectionView.performBatchUpdates({ + if deletedSectionIndexes.count > 0 { + collectionView.deleteSections(deletedSectionIndexes as IndexSet) + } + + if insertedSectionIndexes.count > 0 { + collectionView.insertSections(insertedSectionIndexes as IndexSet) + } + + if updatedSectionIndexes.count > 0 { + collectionView.reloadSections(updatedSectionIndexes as IndexSet) + } + + if deletedItemIndexPaths.count > 0 { + collectionView.deleteItems(at: deletedItemIndexPaths) + } + + if insertedItemIndexPaths.count > 0 { + collectionView.insertItems(at: insertedItemIndexPaths) + } + + if updatedItemIndexPaths.count > 0 && cellConfigurationHandler == nil { + collectionView.reloadItems(at: updatedItemIndexPaths) + } + }, completion: { finished in + if finished { + if let cellConfigurationHandler = cellConfigurationHandler { + for updatedItemIndexPath in updatedItemIndexPaths { + if let cell = collectionView.cellForItem(at: updatedItemIndexPath) { + cellConfigurationHandler(cell, updatedItemIndexPath) } } - - reset() } - }) - } - } - - // - collectionView.reloadData() - - // - return self + + reset() + } + }) + } } - + + // + collectionView.reloadData() + + // + return self } - + +} + #endif + diff --git a/Source/AlecrimCoreData/Convenience/UITableView+Extensions.swift b/Source/AlecrimCoreData/Convenience/UITableView+Extensions.swift index 90bcab2..dd2ae7a 100644 --- a/Source/AlecrimCoreData/Convenience/UITableView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/UITableView+Extensions.swift @@ -7,168 +7,169 @@ // #if os(iOS) || os(tvOS) - - import Foundation - import UIKit - - extension FetchRequestController { - - @discardableResult - public func bind(to tableView: UITableView, rowAnimation: UITableViewRowAnimation = .fade, sectionOffset: Int = 0, cellConfigurationHandler: ((CellType, IndexPath) -> Void)? = nil) -> Self { - let insertedSectionIndexes = NSMutableIndexSet() - let deletedSectionIndexes = NSMutableIndexSet() - let updatedSectionIndexes = NSMutableIndexSet() - - var insertedItemIndexPaths = [IndexPath]() - var deletedItemIndexPaths = [IndexPath]() - var updatedItemIndexPaths = [IndexPath]() - - var reloadData = false - - // - func reset() { - insertedSectionIndexes.removeAllIndexes() - deletedSectionIndexes.removeAllIndexes() - updatedSectionIndexes.removeAllIndexes() - - insertedItemIndexPaths.removeAll(keepingCapacity: false) - deletedItemIndexPaths.removeAll(keepingCapacity: false) - updatedItemIndexPaths.removeAll(keepingCapacity: false) - - reloadData = false + +import Foundation +import UIKit + +extension FetchRequestController { + + /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. + @discardableResult + public func bind(to tableView: UITableView, rowAnimation: UITableViewRowAnimation = .fade, sectionOffset: Int = 0, cellConfigurationHandler: ((UITableViewCell, IndexPath) -> Void)? = nil) -> Self { + let insertedSectionIndexes = NSMutableIndexSet() + let deletedSectionIndexes = NSMutableIndexSet() + let updatedSectionIndexes = NSMutableIndexSet() + + var insertedItemIndexPaths = [IndexPath]() + var deletedItemIndexPaths = [IndexPath]() + var updatedItemIndexPaths = [IndexPath]() + + var reloadData = false + + // + func reset() { + insertedSectionIndexes.removeAllIndexes() + deletedSectionIndexes.removeAllIndexes() + updatedSectionIndexes.removeAllIndexes() + + insertedItemIndexPaths.removeAll(keepingCapacity: false) + deletedItemIndexPaths.removeAll(keepingCapacity: false) + updatedItemIndexPaths.removeAll(keepingCapacity: false) + + reloadData = false + } + + // + self + .needsReloadData { + reloadData = true } - - // - self - .needsReloadData { - reloadData = true + .willChangeContent { + if !reloadData { + // + reset() } - .willChangeContent { - if !reloadData { - // - reset() - } + } + .didInsertSection { sectionInfo, sectionIndex in + if !reloadData { + insertedSectionIndexes.add(sectionIndex + sectionOffset) } - .didInsertSection { sectionInfo, sectionIndex in - if !reloadData { - insertedSectionIndexes.add(sectionIndex + sectionOffset) - } + } + .didDeleteSection { sectionInfo, sectionIndex in + if !reloadData { + deletedSectionIndexes.add(sectionIndex + sectionOffset) + deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex} + updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex} } - .didDeleteSection { sectionInfo, sectionIndex in - if !reloadData { - deletedSectionIndexes.add(sectionIndex + sectionOffset) - deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex} - updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex} + } + .didInsertObject { entity, newIndexPath in + if !reloadData { + let newIndexPath = sectionOffset > 0 ? IndexPath(row: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + + if !insertedSectionIndexes.contains(newIndexPath.section) { + insertedItemIndexPaths.append(newIndexPath) } } - .didInsertObject { entity, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(row: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } + } + .didDeleteObject { entity, indexPath in + if !reloadData { + let indexPath = sectionOffset > 0 ? IndexPath(row: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + + if !deletedSectionIndexes.contains(indexPath.section) { + deletedItemIndexPaths.append(indexPath) } } - .didDeleteObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(row: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } + } + .didUpdateObject { entity, indexPath in + if !reloadData { + let indexPath = sectionOffset > 0 ? IndexPath(row: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + + if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { + updatedItemIndexPaths.append(indexPath) } } - .didUpdateObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(row: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - + } + .didMoveObject { entity, indexPath, newIndexPath in + if !reloadData { + let newIndexPath = sectionOffset > 0 ? IndexPath(row: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + let indexPath = sectionOffset > 0 ? IndexPath(row: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + + if newIndexPath == indexPath { if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { updatedItemIndexPaths.append(indexPath) } } - } - .didMoveObject { entity, indexPath, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(row: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - let indexPath = sectionOffset > 0 ? IndexPath(row: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if newIndexPath == indexPath { - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } + else { + if !deletedSectionIndexes.contains(indexPath.section) { + deletedItemIndexPaths.append(indexPath) } - else { - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } - - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } + + if !insertedSectionIndexes.contains(newIndexPath.section) { + insertedItemIndexPaths.append(newIndexPath) } } } - .didChangeContent { [weak tableView] in - // - defer { reset() } - - // - guard let tableView = tableView else { - return + } + .didChangeContent { [weak tableView] in + // + defer { reset() } + + // + guard let tableView = tableView else { + return + } + + // + if reloadData { + tableView.reloadData() + } + else { + tableView.beginUpdates() + + if deletedSectionIndexes.count > 0 { + tableView.deleteSections(deletedSectionIndexes as IndexSet, with: rowAnimation) } - - // - if reloadData { - tableView.reloadData() + + if insertedSectionIndexes.count > 0 { + tableView.insertSections(insertedSectionIndexes as IndexSet, with: rowAnimation) } - else { - tableView.beginUpdates() - - if deletedSectionIndexes.count > 0 { - tableView.deleteSections(deletedSectionIndexes as IndexSet, with: rowAnimation) - } - - if insertedSectionIndexes.count > 0 { - tableView.insertSections(insertedSectionIndexes as IndexSet, with: rowAnimation) - } - - if updatedSectionIndexes.count > 0 { - tableView.reloadSections(updatedSectionIndexes as IndexSet, with: rowAnimation) - } - - if deletedItemIndexPaths.count > 0 { - tableView.deleteRows(at: deletedItemIndexPaths, with: rowAnimation) - } - - if insertedItemIndexPaths.count > 0 { - tableView.insertRows(at: insertedItemIndexPaths, with: rowAnimation) - } - - if updatedItemIndexPaths.count > 0 && cellConfigurationHandler == nil { - tableView.reloadRows(at: updatedItemIndexPaths, with: rowAnimation) - } - - tableView.endUpdates() - - if let cellConfigurationHandler = cellConfigurationHandler { - for updatedItemIndexPath in updatedItemIndexPaths { - if let cell = tableView.cellForRow(at: updatedItemIndexPath) as? CellType { - cellConfigurationHandler(cell, updatedItemIndexPath) - } + + if updatedSectionIndexes.count > 0 { + tableView.reloadSections(updatedSectionIndexes as IndexSet, with: rowAnimation) + } + + if deletedItemIndexPaths.count > 0 { + tableView.deleteRows(at: deletedItemIndexPaths, with: rowAnimation) + } + + if insertedItemIndexPaths.count > 0 { + tableView.insertRows(at: insertedItemIndexPaths, with: rowAnimation) + } + + if updatedItemIndexPaths.count > 0 && cellConfigurationHandler == nil { + tableView.reloadRows(at: updatedItemIndexPaths, with: rowAnimation) + } + + tableView.endUpdates() + + if let cellConfigurationHandler = cellConfigurationHandler { + for updatedItemIndexPath in updatedItemIndexPaths { + if let cell = tableView.cellForRow(at: updatedItemIndexPath) { + cellConfigurationHandler(cell, updatedItemIndexPath) } } } - } - - // - tableView.reloadData() - - // - return self + } } - + + // + tableView.reloadData() + + // + return self } - + +} + #endif From 4715da7ae5971b1e25c8a37746a84e0788536a02 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 21 Apr 2018 01:10:47 -0300 Subject: [PATCH 06/18] Better `FetchRequestController` convenience `bind` method handling --- .../AlecrimCoreData.xcodeproj/project.pbxproj | 6 +- .../FetchRequestController+Extensions.swift | 20 +++ .../NSCollectionView+Extensions.swift | 170 ++++++++---------- .../Convenience/NSTableView+Extensions.swift | 159 +++++++--------- .../UICollectionView+Extensions.swift | 169 ++++++++--------- .../Convenience/UITableView+Extensions.swift | 168 ++++++++--------- 6 files changed, 306 insertions(+), 386 deletions(-) create mode 100644 Source/AlecrimCoreData/Convenience/FetchRequestController+Extensions.swift diff --git a/Source/AlecrimCoreData.xcodeproj/project.pbxproj b/Source/AlecrimCoreData.xcodeproj/project.pbxproj index b0a52f9..dd41c4b 100644 --- a/Source/AlecrimCoreData.xcodeproj/project.pbxproj +++ b/Source/AlecrimCoreData.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 1420DF9E2054FFBC00B34160 /* AlecrimCoreData.h in Headers */ = {isa = PBXBuildFile; fileRef = 1420DF9C2054FFBC00B34160 /* AlecrimCoreData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 146B042F208AEAE3002091BF /* FetchRequestController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 146B042E208AEAE3002091BF /* FetchRequestController+Extensions.swift */; }; 14B9460020759D0D00A7CFFD /* NSTableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */; }; 14CC3374205B28CA00BA682A /* NSArrayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */; }; 14CC3375205B28CA00BA682A /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */; }; @@ -33,6 +34,7 @@ 1420DF992054FFBC00B34160 /* AlecrimCoreData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AlecrimCoreData.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1420DF9C2054FFBC00B34160 /* AlecrimCoreData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AlecrimCoreData.h; sourceTree = ""; }; 1420DF9D2054FFBC00B34160 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 146B042E208AEAE3002091BF /* FetchRequestController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequestController+Extensions.swift"; sourceTree = ""; }; 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Extensions.swift"; sourceTree = ""; }; 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSArrayController+Extensions.swift"; sourceTree = ""; }; 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+Extensions.swift"; sourceTree = ""; }; @@ -104,8 +106,9 @@ 14CC335D205B28CA00BA682A /* Convenience */ = { isa = PBXGroup; children = ( - 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */, + 146B042E208AEAE3002091BF /* FetchRequestController+Extensions.swift */, 14CC3360205B28CA00BA682A /* UICollectionView+Extensions.swift */, + 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */, 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */, 14CC3361205B28CA00BA682A /* NSCollectionView+Extensions.swift */, 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */, @@ -241,6 +244,7 @@ 14CC337D205B28CA00BA682A /* FetchRequest.swift in Sources */, 14CC3381205B28CA00BA682A /* KeyPath.swift in Sources */, 14B9460020759D0D00A7CFFD /* NSTableView+Extensions.swift in Sources */, + 146B042F208AEAE3002091BF /* FetchRequestController+Extensions.swift in Sources */, 14CC337F205B28CA00BA682A /* SortDescriptor.swift in Sources */, 14CC337A205B28CA00BA682A /* ManagedObject.swift in Sources */, 14CC3382205B28CA00BA682A /* Queryable.swift in Sources */, diff --git a/Source/AlecrimCoreData/Convenience/FetchRequestController+Extensions.swift b/Source/AlecrimCoreData/Convenience/FetchRequestController+Extensions.swift new file mode 100644 index 0000000..0986562 --- /dev/null +++ b/Source/AlecrimCoreData/Convenience/FetchRequestController+Extensions.swift @@ -0,0 +1,20 @@ +// +// FetchRequestController+Extensions.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 21/04/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +import Foundation + +extension FetchRequestController { + + internal enum Change { + case insert(T) + case delete(T) + case update(T) + case move(T, T) // from, to + } + +} diff --git a/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift b/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift index 2c21d1e..cac3f3b 100644 --- a/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift @@ -16,27 +16,16 @@ extension FetchRequestController { /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. @discardableResult public func bind(to collectionView: NSCollectionView, sectionOffset: Int = 0, itemConfigurationHandler: ((NSCollectionViewItem, IndexPath) -> Void)? = nil) -> Self { - let insertedSectionIndexes = NSMutableIndexSet() - let deletedSectionIndexes = NSMutableIndexSet() - let updatedSectionIndexes = NSMutableIndexSet() - - var insertedItemIndexPaths = [IndexPath]() - var deletedItemIndexPaths = [IndexPath]() - var updatedItemIndexPaths = [IndexPath]() - + // var reloadData = false + var sectionChanges = Array>() + var itemChanges = Array>() // func reset() { - insertedSectionIndexes.removeAllIndexes() - deletedSectionIndexes.removeAllIndexes() - updatedSectionIndexes.removeAllIndexes() - - insertedItemIndexPaths.removeAll(keepingCapacity: false) - deletedItemIndexPaths.removeAll(keepingCapacity: false) - updatedItemIndexPaths.removeAll(keepingCapacity: false) - reloadData = false + sectionChanges.removeAll() + itemChanges.removeAll() } // @@ -45,69 +34,46 @@ extension FetchRequestController { reloadData = true } .willChangeContent { - if !reloadData { - reset() + if collectionView.numberOfSections == 0 { + reloadData = true } + + guard !reloadData else { return } + reset() } - .didInsertSection { sectionInfo, sectionIndex in - if !reloadData { - insertedSectionIndexes.add(sectionIndex + sectionOffset) - } + .didInsertSection { _, sectionIndex in + guard !reloadData else { return } + sectionChanges.append(.insert(sectionIndex)) } - .didDeleteSection { sectionInfo, sectionIndex in - if !reloadData { - deletedSectionIndexes.add(sectionIndex + sectionOffset) - deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex } - updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex } - } + .didDeleteSection { _, sectionIndex in + guard !reloadData else { return } + sectionChanges.append(.delete(sectionIndex)) } - .didInsertObject { entity, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + .didInsertObject { _, newIndexPath in + guard !reloadData else { return } - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } - } + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + itemChanges.append(.insert(newIndexPath)) } - .didDeleteObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + .didDeleteObject { _, indexPath in + guard !reloadData else { return } - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } - } + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.delete(indexPath)) } - .didUpdateObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + .didUpdateObject { _, indexPath in + guard !reloadData else { return } - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } - } + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.update(indexPath)) } .didMoveObject { entity, indexPath, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + guard !reloadData else { return } - if newIndexPath == indexPath { - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } - } - else { - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } - } - } + itemChanges.append(.move(indexPath, newIndexPath)) } .didChangeContent { [weak collectionView] in // @@ -117,49 +83,58 @@ extension FetchRequestController { } // - if reloadData { + guard !reloadData else { collectionView.reloadData() reset() + return } - else { - collectionView.performBatchUpdates({ - if deletedSectionIndexes.count > 0 { - collectionView.deleteSections(deletedSectionIndexes as IndexSet) - } - if insertedSectionIndexes.count > 0 { - collectionView.insertSections(insertedSectionIndexes as IndexSet) - } + // + var updatedIndexPaths = [IndexPath]() - if updatedSectionIndexes.count > 0 { - collectionView.reloadSections(updatedSectionIndexes as IndexSet) - } + collectionView.performBatchUpdates({ + sectionChanges.forEach { + switch $0 { + case .insert(let sectionIndex): + collectionView.insertSections(IndexSet(integer: sectionIndex)) - if deletedItemIndexPaths.count > 0 { - collectionView.deleteItems(at: Set(deletedItemIndexPaths)) - } + case .delete(let sectionIndex): + collectionView.deleteSections(IndexSet(integer: sectionIndex)) - if insertedItemIndexPaths.count > 0 { - collectionView.insertItems(at: Set(insertedItemIndexPaths)) + default: + break } + } - if updatedItemIndexPaths.count > 0 && itemConfigurationHandler == nil { - collectionView.reloadItems(at: Set(updatedItemIndexPaths)) - } - }, completionHandler: { finished in - if finished { - if let itemConfigurationHandler = itemConfigurationHandler { - for updatedItemIndexPath in updatedItemIndexPaths { - if let item = collectionView.item(at: updatedItemIndexPath) { - itemConfigurationHandler(item, updatedItemIndexPath) - } - } + itemChanges.forEach { + switch $0 { + case .insert(let indexPath): + collectionView.insertItems(at: Set([indexPath])) + + case .delete(let indexPath): + collectionView.deleteItems(at: Set([indexPath])) + + case .update(let indexPath): + if itemConfigurationHandler == nil { + collectionView.reloadItems(at: Set([indexPath])) + } + else { + updatedIndexPaths.append(indexPath) } - reset() + case .move(let oldIndexPath, let newIndexPath): + collectionView.moveItem(at: oldIndexPath, to: newIndexPath) } - }) - } + } + }, completionHandler: { _ in + updatedIndexPaths.forEach { + if let item = collectionView.item(at: $0) { + itemConfigurationHandler?(item, $0) + } + } + + reset() + }) } // @@ -174,3 +149,4 @@ extension FetchRequestController { #endif + diff --git a/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift b/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift index 7cca1bf..76534ae 100644 --- a/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift @@ -16,27 +16,16 @@ extension FetchRequestController { /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. @discardableResult public func bind(to tableView: NSTableView, animationOptions: NSTableView.AnimationOptions = .effectFade, sectionOffset: Int = 0, cellViewConfigurationHandler: ((NSTableCellView, IndexPath) -> Void)? = nil) -> Self { - let insertedSectionIndexes = NSMutableIndexSet() - let deletedSectionIndexes = NSMutableIndexSet() - let updatedSectionIndexes = NSMutableIndexSet() - - var insertedItemIndexPaths = [IndexPath]() - var deletedItemIndexPaths = [IndexPath]() - var updatedItemIndexPaths = [IndexPath]() - + // var reloadData = false + var sectionChanges = Array>() + var itemChanges = Array>() // func reset() { - insertedSectionIndexes.removeAllIndexes() - deletedSectionIndexes.removeAllIndexes() - updatedSectionIndexes.removeAllIndexes() - - insertedItemIndexPaths.removeAll(keepingCapacity: false) - deletedItemIndexPaths.removeAll(keepingCapacity: false) - updatedItemIndexPaths.removeAll(keepingCapacity: false) - reloadData = false + sectionChanges.removeAll() + itemChanges.removeAll() } // @@ -45,70 +34,44 @@ extension FetchRequestController { reloadData = true } .willChangeContent { - if !reloadData { - // - reset() + if tableView.numberOfRows == 0 { + reloadData = true } + + guard !reloadData else { return } + reset() } - .didInsertSection { sectionInfo, sectionIndex in - if !reloadData { - insertedSectionIndexes.add(sectionIndex + sectionOffset) - } + .didInsertSection { _, sectionIndex in + reloadData = true } - .didDeleteSection { sectionInfo, sectionIndex in - if !reloadData { - deletedSectionIndexes.add(sectionIndex + sectionOffset) - deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex} - updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex} - } + .didDeleteSection { _, sectionIndex in + reloadData = true } - .didInsertObject { entity, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + .didInsertObject { _, newIndexPath in + guard !reloadData else { return } - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } - } + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + itemChanges.append(.insert(newIndexPath)) } - .didDeleteObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + .didDeleteObject { _, indexPath in + guard !reloadData else { return } - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } - } + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.delete(indexPath)) } - .didUpdateObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + .didUpdateObject { _, indexPath in + guard !reloadData else { return } - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } - } + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.update(indexPath)) } .didMoveObject { entity, indexPath, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + guard !reloadData else { return } - if newIndexPath == indexPath { - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } - } - else { - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } - } - } + itemChanges.append(.move(indexPath, newIndexPath)) } .didChangeContent { [weak tableView] in // @@ -120,47 +83,47 @@ extension FetchRequestController { } // - if reloadData { + guard !reloadData else { tableView.reloadData() + return } - else { - tableView.beginUpdates() - // if deletedSectionIndexes.count > 0 { - // tableView.deleteSections(deletedSectionIndexes as IndexSet, with: animationOptions) - // } - - // if insertedSectionIndexes.count > 0 { - // tableView.insertSections(insertedSectionIndexes as IndexSet, with: animationOptions) - // } + // + tableView.beginUpdates() - // if updatedSectionIndexes.count > 0 { - // tableView.reloadSections(updatedSectionIndexes as IndexSet, with: animationOptions) - // } + // + var updatedIndexPaths = [IndexPath]() - if deletedItemIndexPaths.count > 0 { - let deletedRowsIndexSet = IndexSet(deletedItemIndexPaths.map { $0.item }) - tableView.removeRows(at: deletedRowsIndexSet, withAnimation: animationOptions) - } + itemChanges.forEach { + switch $0 { + case .update(let indexPath): + if cellViewConfigurationHandler == nil { + tableView.reloadData(forRowIndexes: IndexSet(integer: indexPath.item), columnIndexes: IndexSet()) + } + else { + updatedIndexPaths.append(indexPath) + } - if insertedItemIndexPaths.count > 0 { - let insertedRowsIndexSet = IndexSet(insertedItemIndexPaths.map { $0.item }) - tableView.insertRows(at: insertedRowsIndexSet, withAnimation: animationOptions) - } + case .delete(let indexPath): + tableView.removeRows(at: IndexSet(integer: indexPath.item), withAnimation: animationOptions) - tableView.endUpdates() + case .insert(let indexPath): + tableView.insertRows(at: IndexSet(integer: indexPath.item), withAnimation: animationOptions) - if updatedItemIndexPaths.count > 0 && cellViewConfigurationHandler == nil { - let updatedItemIndexSet = IndexSet(updatedItemIndexPaths.map { $0.item }) - tableView.reloadData(forRowIndexes: updatedItemIndexSet, columnIndexes: IndexSet()) + case .move(let oldIndexPath, let newIndexPath): + //tableView.moveRow(at: oldIndexPath.item, to: newIndexPath.item) + tableView.removeRows(at: IndexSet(integer: oldIndexPath.item), withAnimation: animationOptions) + tableView.insertRows(at: IndexSet(integer: newIndexPath.item), withAnimation: animationOptions) } + } - if let cellViewConfigurationHandler = cellViewConfigurationHandler { - for updatedItemIndexPath in updatedItemIndexPaths { - if let cell = tableView.view(atColumn: 0, row: updatedItemIndexPath.item, makeIfNecessary: false) as? NSTableCellView { - cellViewConfigurationHandler(cell, updatedItemIndexPath) - } - } + // + tableView.endUpdates() + + // + updatedIndexPaths.forEach { + if let item = tableView.view(atColumn: 0, row: $0.item, makeIfNecessary: false) as? NSTableCellView { + cellViewConfigurationHandler?(item, $0) } } } diff --git a/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift b/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift index b101103..6c66c19 100644 --- a/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift @@ -16,27 +16,16 @@ extension FetchRequestController { /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. @discardableResult public func bind(to collectionView: UICollectionView, sectionOffset: Int = 0, cellConfigurationHandler: ((UICollectionViewCell, IndexPath) -> Void)? = nil) -> Self { - let insertedSectionIndexes = NSMutableIndexSet() - let deletedSectionIndexes = NSMutableIndexSet() - let updatedSectionIndexes = NSMutableIndexSet() - - var insertedItemIndexPaths = [IndexPath]() - var deletedItemIndexPaths = [IndexPath]() - var updatedItemIndexPaths = [IndexPath]() - + // var reloadData = false + var sectionChanges = Array>() + var itemChanges = Array>() // func reset() { - insertedSectionIndexes.removeAllIndexes() - deletedSectionIndexes.removeAllIndexes() - updatedSectionIndexes.removeAllIndexes() - - insertedItemIndexPaths.removeAll(keepingCapacity: false) - deletedItemIndexPaths.removeAll(keepingCapacity: false) - updatedItemIndexPaths.removeAll(keepingCapacity: false) - reloadData = false + sectionChanges.removeAll() + itemChanges.removeAll() } // @@ -45,69 +34,46 @@ extension FetchRequestController { reloadData = true } .willChangeContent { - if !reloadData { - reset() + if collectionView.numberOfSections == 0 { + reloadData = true } + + guard !reloadData else { return } + reset() } - .didInsertSection { sectionInfo, sectionIndex in - if !reloadData { - insertedSectionIndexes.add(sectionIndex + sectionOffset) - } + .didInsertSection { _, sectionIndex in + guard !reloadData else { return } + sectionChanges.append(.insert(sectionIndex)) } - .didDeleteSection { sectionInfo, sectionIndex in - if !reloadData { - deletedSectionIndexes.add(sectionIndex + sectionOffset) - deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex } - updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex } - } + .didDeleteSection { _, sectionIndex in + guard !reloadData else { return } + sectionChanges.append(.delete(sectionIndex)) } - .didInsertObject { entity, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + .didInsertObject { _, newIndexPath in + guard !reloadData else { return } - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } - } + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + itemChanges.append(.insert(newIndexPath)) } - .didDeleteObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + .didDeleteObject { _, indexPath in + guard !reloadData else { return } - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } - } + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.delete(indexPath)) } - .didUpdateObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + .didUpdateObject { _, indexPath in + guard !reloadData else { return } - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } - } + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.update(indexPath)) } .didMoveObject { entity, indexPath, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + guard !reloadData else { return } - if newIndexPath == indexPath { - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } - } - else { - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } - } - } + itemChanges.append(.move(indexPath, newIndexPath)) } .didChangeContent { [weak collectionView] in guard let collectionView = collectionView else { @@ -115,49 +81,58 @@ extension FetchRequestController { return } - if reloadData { + guard !reloadData else { collectionView.reloadData() reset() + return } - else { - collectionView.performBatchUpdates({ - if deletedSectionIndexes.count > 0 { - collectionView.deleteSections(deletedSectionIndexes as IndexSet) - } - if insertedSectionIndexes.count > 0 { - collectionView.insertSections(insertedSectionIndexes as IndexSet) - } + // + var updatedIndexPaths = [IndexPath]() - if updatedSectionIndexes.count > 0 { - collectionView.reloadSections(updatedSectionIndexes as IndexSet) - } + collectionView.performBatchUpdates({ + sectionChanges.forEach { + switch $0 { + case .insert(let sectionIndex): + collectionView.insertSections(IndexSet(integer: sectionIndex)) - if deletedItemIndexPaths.count > 0 { - collectionView.deleteItems(at: deletedItemIndexPaths) - } + case .delete(let sectionIndex): + collectionView.deleteSections(IndexSet(integer: sectionIndex)) - if insertedItemIndexPaths.count > 0 { - collectionView.insertItems(at: insertedItemIndexPaths) + default: + break } + } - if updatedItemIndexPaths.count > 0 && cellConfigurationHandler == nil { - collectionView.reloadItems(at: updatedItemIndexPaths) - } - }, completion: { finished in - if finished { - if let cellConfigurationHandler = cellConfigurationHandler { - for updatedItemIndexPath in updatedItemIndexPaths { - if let cell = collectionView.cellForItem(at: updatedItemIndexPath) { - cellConfigurationHandler(cell, updatedItemIndexPath) - } - } + itemChanges.forEach { + switch $0 { + case .insert(let indexPath): + collectionView.insertItems(at: [indexPath]) + + case .delete(let indexPath): + collectionView.deleteItems(at: [indexPath]) + + case .update(let indexPath): + if cellConfigurationHandler == nil { + collectionView.reloadItems(at: [indexPath]) + } + else { + updatedIndexPaths.append(indexPath) } - reset() + case .move(let oldIndexPath, let newIndexPath): + collectionView.moveItem(at: oldIndexPath, to: newIndexPath) } - }) - } + } + }, completion: { _ in + updatedIndexPaths.forEach { + if let cell = collectionView.cellForItem(at: $0) { + cellConfigurationHandler?(cell, $0) + } + } + + reset() + }) } // diff --git a/Source/AlecrimCoreData/Convenience/UITableView+Extensions.swift b/Source/AlecrimCoreData/Convenience/UITableView+Extensions.swift index dd2ae7a..0416f87 100644 --- a/Source/AlecrimCoreData/Convenience/UITableView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/UITableView+Extensions.swift @@ -16,27 +16,16 @@ extension FetchRequestController { /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. @discardableResult public func bind(to tableView: UITableView, rowAnimation: UITableViewRowAnimation = .fade, sectionOffset: Int = 0, cellConfigurationHandler: ((UITableViewCell, IndexPath) -> Void)? = nil) -> Self { - let insertedSectionIndexes = NSMutableIndexSet() - let deletedSectionIndexes = NSMutableIndexSet() - let updatedSectionIndexes = NSMutableIndexSet() - - var insertedItemIndexPaths = [IndexPath]() - var deletedItemIndexPaths = [IndexPath]() - var updatedItemIndexPaths = [IndexPath]() - + // var reloadData = false + var sectionChanges = Array>() + var itemChanges = Array>() // func reset() { - insertedSectionIndexes.removeAllIndexes() - deletedSectionIndexes.removeAllIndexes() - updatedSectionIndexes.removeAllIndexes() - - insertedItemIndexPaths.removeAll(keepingCapacity: false) - deletedItemIndexPaths.removeAll(keepingCapacity: false) - updatedItemIndexPaths.removeAll(keepingCapacity: false) - reloadData = false + sectionChanges.removeAll() + itemChanges.removeAll() } // @@ -45,70 +34,46 @@ extension FetchRequestController { reloadData = true } .willChangeContent { - if !reloadData { - // - reset() + if tableView.numberOfSections == 0 { + reloadData = true } + + guard !reloadData else { return } + reset() } - .didInsertSection { sectionInfo, sectionIndex in - if !reloadData { - insertedSectionIndexes.add(sectionIndex + sectionOffset) - } + .didInsertSection { _, sectionIndex in + guard !reloadData else { return } + sectionChanges.append(.insert(sectionIndex)) } - .didDeleteSection { sectionInfo, sectionIndex in - if !reloadData { - deletedSectionIndexes.add(sectionIndex + sectionOffset) - deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex} - updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex} - } + .didDeleteSection { _, sectionIndex in + guard !reloadData else { return } + sectionChanges.append(.delete(sectionIndex)) } - .didInsertObject { entity, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(row: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + .didInsertObject { _, newIndexPath in + guard !reloadData else { return } - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } - } + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + itemChanges.append(.insert(newIndexPath)) } - .didDeleteObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(row: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + .didDeleteObject { _, indexPath in + guard !reloadData else { return } - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } - } + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.delete(indexPath)) } - .didUpdateObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(row: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + .didUpdateObject { _, indexPath in + guard !reloadData else { return } - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } - } + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.update(indexPath)) } .didMoveObject { entity, indexPath, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(row: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - let indexPath = sectionOffset > 0 ? IndexPath(row: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + guard !reloadData else { return } - if newIndexPath == indexPath { - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } - } - else { - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } - } - } + itemChanges.append(.move(indexPath, newIndexPath)) } .didChangeContent { [weak tableView] in // @@ -120,44 +85,61 @@ extension FetchRequestController { } // - if reloadData { + guard !reloadData else { tableView.reloadData() + return } - else { - tableView.beginUpdates() - if deletedSectionIndexes.count > 0 { - tableView.deleteSections(deletedSectionIndexes as IndexSet, with: rowAnimation) - } + // + tableView.beginUpdates() - if insertedSectionIndexes.count > 0 { - tableView.insertSections(insertedSectionIndexes as IndexSet, with: rowAnimation) - } + // + sectionChanges.forEach { + switch $0 { + case .delete(let sectionIndex): + tableView.deleteSections(IndexSet(integer: sectionIndex), with: rowAnimation) - if updatedSectionIndexes.count > 0 { - tableView.reloadSections(updatedSectionIndexes as IndexSet, with: rowAnimation) - } + case .insert(let sectionIndex): + tableView.insertSections(IndexSet(integer: sectionIndex), with: rowAnimation) - if deletedItemIndexPaths.count > 0 { - tableView.deleteRows(at: deletedItemIndexPaths, with: rowAnimation) + default: + break } + } - if insertedItemIndexPaths.count > 0 { - tableView.insertRows(at: insertedItemIndexPaths, with: rowAnimation) - } + // + var updatedIndexPaths = [IndexPath]() - if updatedItemIndexPaths.count > 0 && cellConfigurationHandler == nil { - tableView.reloadRows(at: updatedItemIndexPaths, with: rowAnimation) + itemChanges.forEach { + switch $0 { + case .update(let indexPath): + if cellConfigurationHandler == nil { + tableView.reloadRows(at: [indexPath], with: rowAnimation) + } + else { + updatedIndexPaths.append(indexPath) + } + + case .delete(let indexPath): + tableView.deleteRows(at: [indexPath], with: rowAnimation) + + case .insert(let indexPath): + tableView.insertRows(at: [indexPath], with: rowAnimation) + + case .move(let oldIndexPath, let newIndexPath): + //tableView.moveRow(at: oldIndexPath, to: newIndexPath) + tableView.deleteRows(at: [oldIndexPath], with: rowAnimation) + tableView.insertRows(at: [newIndexPath], with: rowAnimation) } + } - tableView.endUpdates() + // + tableView.endUpdates() - if let cellConfigurationHandler = cellConfigurationHandler { - for updatedItemIndexPath in updatedItemIndexPaths { - if let cell = tableView.cellForRow(at: updatedItemIndexPath) { - cellConfigurationHandler(cell, updatedItemIndexPath) - } - } + // + updatedIndexPaths.forEach { + if let item = tableView.cellForRow(at: $0) { + cellConfigurationHandler?(item, $0) } } } From afba05b2f13b79998de6aa2465aeb2bead370afd Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Mon, 23 Apr 2018 00:31:42 -0300 Subject: [PATCH 07/18] Extended `FetchRequestController` "auto perform fetch" behaviour to other methods --- .../FetchedResultsControllerDelegate.swift | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Source/AlecrimCoreData/Fetch Request Controller/FetchedResultsControllerDelegate.swift b/Source/AlecrimCoreData/Fetch Request Controller/FetchedResultsControllerDelegate.swift index 270f7a9..693ff72 100644 --- a/Source/AlecrimCoreData/Fetch Request Controller/FetchedResultsControllerDelegate.swift +++ b/Source/AlecrimCoreData/Fetch Request Controller/FetchedResultsControllerDelegate.swift @@ -114,6 +114,8 @@ public func +=(left: ClosuresContainer, right: Closure) { extension FetchRequestController { public func removeAllBindings() { + self.performFetchIfNeeded() + self.rawValueDelegate.needsReloadDataClosure = nil self.rawValueDelegate.willChangeContentClosuresContainer.removeAll() @@ -135,8 +137,10 @@ extension FetchRequestController { extension FetchRequestController { public func refresh() { + self.performFetchIfNeeded() + + self.rawValueDelegate.needsReloadDataClosure?() - self.rawValueDelegate.willChangeContentClosuresContainer.closures.forEach { $0() } if let cacheName = self.rawValue.cacheName { @@ -154,6 +158,8 @@ extension FetchRequestController { @discardableResult internal func needsReloadData(closure: @escaping FetchedResultsControllerDelegate.NeedsReloadDataClosure) -> Self { + self.performFetchIfNeeded() + self.rawValueDelegate.needsReloadDataClosure = closure return self } @@ -165,54 +171,72 @@ extension FetchRequestController { @discardableResult public func willChangeContent(closure: @escaping FetchedResultsControllerDelegate.ChangeContentClosure) -> Self { + self.performFetchIfNeeded() + self.rawValueDelegate.willChangeContentClosuresContainer += closure return self } @discardableResult public func didChangeContent(closure: @escaping FetchedResultsControllerDelegate.ChangeContentClosure) -> Self { + self.performFetchIfNeeded() + self.rawValueDelegate.didChangeContentClosuresContainer += closure return self } @discardableResult public func didInsertSection(closure: @escaping FetchedResultsControllerDelegate.ChangeSectionClosure) -> Self { + self.performFetchIfNeeded() + self.rawValueDelegate.didInsertSectionClosuresContainer += closure return self } @discardableResult public func didDeleteSection(closure: @escaping FetchedResultsControllerDelegate.ChangeSectionClosure) -> Self { + self.performFetchIfNeeded() + self.rawValueDelegate.didDeleteSectionClosuresContainer += closure return self } @discardableResult public func didInsertObject(closure: @escaping FetchedResultsControllerDelegate.ChangeItemClosure) -> Self { + self.performFetchIfNeeded() + self.rawValueDelegate.didInsertObjectClosuresContainer += closure return self } @discardableResult public func didDeleteObject(closure: @escaping FetchedResultsControllerDelegate.ChangeItemClosure) -> Self { + self.performFetchIfNeeded() + self.rawValueDelegate.didDeleteObjectClosuresContainer += closure return self } @discardableResult public func didUpdateObject(closure: @escaping FetchedResultsControllerDelegate.ChangeItemClosure) -> Self { + self.performFetchIfNeeded() + self.rawValueDelegate.didUpdateObjectClosuresContainer += closure return self } @discardableResult public func didMoveObject(closure: @escaping FetchedResultsControllerDelegate.MoveItemClosure) -> Self { + self.performFetchIfNeeded() + self.rawValueDelegate.didMoveObjectClosuresContainer += closure return self } @discardableResult public func sectionIndexTitle(closure: @escaping FetchedResultsControllerDelegate.SectionIndexTitleClosure) -> Self { + self.performFetchIfNeeded() + self.rawValueDelegate.sectionIndexTitleClosure = closure return self } From dd8e210e8f1a84b9952cfee1c24899d290e64390 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Tue, 24 Apr 2018 22:42:57 -0300 Subject: [PATCH 08/18] Allow updates to be animated in macOS (`bind` convenience method) --- .../NSCollectionView+Extensions.swift | 5 +++-- .../Convenience/NSTableView+Extensions.swift | 21 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift b/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift index cac3f3b..48155a1 100644 --- a/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift @@ -15,7 +15,7 @@ extension FetchRequestController { /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. @discardableResult - public func bind(to collectionView: NSCollectionView, sectionOffset: Int = 0, itemConfigurationHandler: ((NSCollectionViewItem, IndexPath) -> Void)? = nil) -> Self { + public func bind(to collectionView: NSCollectionView, sectionOffset: Int = 0, animated: Bool = false, itemConfigurationHandler: ((NSCollectionViewItem, IndexPath) -> Void)? = nil) -> Self { // var reloadData = false var sectionChanges = Array>() @@ -92,7 +92,8 @@ extension FetchRequestController { // var updatedIndexPaths = [IndexPath]() - collectionView.performBatchUpdates({ + let performer = (animated ? collectionView.animator() : collectionView) + performer.performBatchUpdates({ sectionChanges.forEach { switch $0 { case .insert(let sectionIndex): diff --git a/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift b/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift index 76534ae..973abdb 100644 --- a/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift @@ -15,7 +15,7 @@ extension FetchRequestController { /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. @discardableResult - public func bind(to tableView: NSTableView, animationOptions: NSTableView.AnimationOptions = .effectFade, sectionOffset: Int = 0, cellViewConfigurationHandler: ((NSTableCellView, IndexPath) -> Void)? = nil) -> Self { + public func bind(to tableView: NSTableView, animationOptions: NSTableView.AnimationOptions = .effectFade, sectionOffset: Int = 0, animated: Bool = false, cellViewConfigurationHandler: ((NSTableCellView, IndexPath) -> Void)? = nil) -> Self { // var reloadData = false var sectionChanges = Array>() @@ -89,7 +89,10 @@ extension FetchRequestController { } // - tableView.beginUpdates() + let performer = animated ? tableView.animator() : tableView + + // + performer.beginUpdates() // var updatedIndexPaths = [IndexPath]() @@ -98,27 +101,27 @@ extension FetchRequestController { switch $0 { case .update(let indexPath): if cellViewConfigurationHandler == nil { - tableView.reloadData(forRowIndexes: IndexSet(integer: indexPath.item), columnIndexes: IndexSet()) + performer.reloadData(forRowIndexes: IndexSet(integer: indexPath.item), columnIndexes: IndexSet()) } else { updatedIndexPaths.append(indexPath) } case .delete(let indexPath): - tableView.removeRows(at: IndexSet(integer: indexPath.item), withAnimation: animationOptions) + performer.removeRows(at: IndexSet(integer: indexPath.item), withAnimation: animationOptions) case .insert(let indexPath): - tableView.insertRows(at: IndexSet(integer: indexPath.item), withAnimation: animationOptions) + performer.insertRows(at: IndexSet(integer: indexPath.item), withAnimation: animationOptions) case .move(let oldIndexPath, let newIndexPath): - //tableView.moveRow(at: oldIndexPath.item, to: newIndexPath.item) - tableView.removeRows(at: IndexSet(integer: oldIndexPath.item), withAnimation: animationOptions) - tableView.insertRows(at: IndexSet(integer: newIndexPath.item), withAnimation: animationOptions) + //performer.moveRow(at: oldIndexPath.item, to: newIndexPath.item) + performer.removeRows(at: IndexSet(integer: oldIndexPath.item), withAnimation: animationOptions) + performer.insertRows(at: IndexSet(integer: newIndexPath.item), withAnimation: animationOptions) } } // - tableView.endUpdates() + performer.endUpdates() // updatedIndexPaths.forEach { From 537937f90d7a1091a0d56c1dca5c77c851174e22 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Tue, 24 Apr 2018 22:43:59 -0300 Subject: [PATCH 09/18] Minor change --- .../Convenience/NSCollectionView+Extensions.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift b/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift index 48155a1..d841265 100644 --- a/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift @@ -92,7 +92,8 @@ extension FetchRequestController { // var updatedIndexPaths = [IndexPath]() - let performer = (animated ? collectionView.animator() : collectionView) + let performer = animated ? collectionView.animator() : collectionView + performer.performBatchUpdates({ sectionChanges.forEach { switch $0 { From 68a3b2e39d46a4240f3525788c73dffaba66cbf3 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Wed, 25 Apr 2018 16:58:13 -0300 Subject: [PATCH 10/18] Renamed `firstOrNewEntity` to `firstOrEmptyNew` (to be clear) --- Source/AlecrimCoreData/Core/Query/Query.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/AlecrimCoreData/Core/Query/Query.swift b/Source/AlecrimCoreData/Core/Query/Query.swift index 3bc8e3e..6ad31d4 100644 --- a/Source/AlecrimCoreData/Core/Query/Query.swift +++ b/Source/AlecrimCoreData/Core/Query/Query.swift @@ -78,7 +78,7 @@ extension Query { extension Query { - public func firstOrNewEntity(where predicate: Predicate) -> Entity { + public func firstOrEmptyNew(where predicate: Predicate) -> Entity { guard let existingEntity = self.filtered(using: predicate).first() else { return self.newEntity() } @@ -86,7 +86,7 @@ extension Query { return existingEntity } - public func firstOrNewEntity(where rawValue: NSPredicate) -> Entity { + public func firstOrEmptyNew(where rawValue: NSPredicate) -> Entity { guard let existingEntity = self.filtered(using: Predicate(rawValue: rawValue)).first() else { return self.newEntity() } @@ -94,7 +94,7 @@ extension Query { return existingEntity } - public func firstOrNewEntity(where closure: () -> Predicate) -> Entity { + public func firstOrEmptyNew(where closure: () -> Predicate) -> Entity { guard let existingEntity = self.filtered(using: closure()).first() else { return self.newEntity() } From cea236f868e262c036caf55298889f76434dcd5c Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Wed, 25 Apr 2018 16:59:59 -0300 Subject: [PATCH 11/18] Rename `newEntity` method to `new --- Source/AlecrimCoreData/Core/Query/Query.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/AlecrimCoreData/Core/Query/Query.swift b/Source/AlecrimCoreData/Core/Query/Query.swift index 6ad31d4..65c6a5c 100644 --- a/Source/AlecrimCoreData/Core/Query/Query.swift +++ b/Source/AlecrimCoreData/Core/Query/Query.swift @@ -80,7 +80,7 @@ extension Query { public func firstOrEmptyNew(where predicate: Predicate) -> Entity { guard let existingEntity = self.filtered(using: predicate).first() else { - return self.newEntity() + return self.new() } return existingEntity @@ -88,7 +88,7 @@ extension Query { public func firstOrEmptyNew(where rawValue: NSPredicate) -> Entity { guard let existingEntity = self.filtered(using: Predicate(rawValue: rawValue)).first() else { - return self.newEntity() + return self.new() } return existingEntity @@ -96,7 +96,7 @@ extension Query { public func firstOrEmptyNew(where closure: () -> Predicate) -> Entity { guard let existingEntity = self.filtered(using: closure()).first() else { - return self.newEntity() + return self.new() } return existingEntity @@ -163,13 +163,13 @@ extension Query: Sequence { extension Query { - public func newEntity() -> Entity { + public func new() -> Entity { return Entity(context: self.context) } @discardableResult public func insert(with entityPropertiesInitializationClosure: (Entity) -> Void) -> Entity { - let entity = self.newEntity() + let entity = self.new() entityPropertiesInitializationClosure(entity) return entity From 675a93b6ae922bf0be60ebf3ab31464bc8e455bc Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 26 Apr 2018 17:33:12 -0300 Subject: [PATCH 12/18] Added `any` and `none` methods to Query --- Source/AlecrimCoreData/Core/Query/Query.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Source/AlecrimCoreData/Core/Query/Query.swift b/Source/AlecrimCoreData/Core/Query/Query.swift index 65c6a5c..f96e71b 100644 --- a/Source/AlecrimCoreData/Core/Query/Query.swift +++ b/Source/AlecrimCoreData/Core/Query/Query.swift @@ -48,6 +48,16 @@ extension Query { return try! self.context.count(for: fetchRequest.toRaw()) } + // + + public func any() -> Bool { + return self.prefix(1).count() > 0 + } + + public func none() -> Bool { + return !self.any() + } + } // MARK: - From 58f348db87d72be5c21c0c2eb9cc18d0a3c7cde3 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Wed, 9 May 2018 05:25:15 -0300 Subject: [PATCH 13/18] Minor change --- Source/AlecrimCoreData/Core/Container/PersistentContainer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift b/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift index 33f2819..a3a8ff6 100644 --- a/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift +++ b/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift @@ -234,7 +234,7 @@ public enum PersistentContainerError: Error { // MARK: - -public protocol PersistentContainerType: class {} +public protocol PersistentContainerType: AnyObject {} extension PersistentContainer: PersistentContainerType {} extension GenericPersistentContainer: PersistentContainerType {} From 1567ff1a8ff36190b7370014faf36c9d9ff2b43a Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sun, 20 May 2018 21:23:56 -0300 Subject: [PATCH 14/18] Files structure reorganisation --- .../AlecrimCoreData.xcodeproj/project.pbxproj | 18 +- .../Core/Container/PersistentContainer.swift | 309 ------------------ .../CustomPersistentContainer.swift | 89 +++++ .../ManagedObject.swift | 0 .../ManagedObjectContext.swift | 0 .../PersistentContainer.swift | 119 +++++++ .../PersistentContainerAuxiliarTypes.swift | 35 ++ .../PersistentContainerType.swift | 83 +++++ 8 files changed, 341 insertions(+), 312 deletions(-) delete mode 100644 Source/AlecrimCoreData/Core/Container/PersistentContainer.swift create mode 100644 Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift rename Source/AlecrimCoreData/Core/{Container => Persistent Container}/ManagedObject.swift (100%) rename Source/AlecrimCoreData/Core/{Container => Persistent Container}/ManagedObjectContext.swift (100%) create mode 100644 Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift create mode 100644 Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerAuxiliarTypes.swift create mode 100644 Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift diff --git a/Source/AlecrimCoreData.xcodeproj/project.pbxproj b/Source/AlecrimCoreData.xcodeproj/project.pbxproj index dd41c4b..758bc89 100644 --- a/Source/AlecrimCoreData.xcodeproj/project.pbxproj +++ b/Source/AlecrimCoreData.xcodeproj/project.pbxproj @@ -8,6 +8,9 @@ /* Begin PBXBuildFile section */ 1420DF9E2054FFBC00B34160 /* AlecrimCoreData.h in Headers */ = {isa = PBXBuildFile; fileRef = 1420DF9C2054FFBC00B34160 /* AlecrimCoreData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1454786620B247F300831016 /* PersistentContainerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1454786520B247F300831016 /* PersistentContainerType.swift */; }; + 1454786820B2484900831016 /* PersistentContainerAuxiliarTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1454786720B2484800831016 /* PersistentContainerAuxiliarTypes.swift */; }; + 1454786A20B2487300831016 /* CustomPersistentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1454786920B2487300831016 /* CustomPersistentContainer.swift */; }; 146B042F208AEAE3002091BF /* FetchRequestController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 146B042E208AEAE3002091BF /* FetchRequestController+Extensions.swift */; }; 14B9460020759D0D00A7CFFD /* NSTableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */; }; 14CC3374205B28CA00BA682A /* NSArrayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */; }; @@ -34,6 +37,9 @@ 1420DF992054FFBC00B34160 /* AlecrimCoreData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AlecrimCoreData.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1420DF9C2054FFBC00B34160 /* AlecrimCoreData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AlecrimCoreData.h; sourceTree = ""; }; 1420DF9D2054FFBC00B34160 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1454786520B247F300831016 /* PersistentContainerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistentContainerType.swift; sourceTree = ""; }; + 1454786720B2484800831016 /* PersistentContainerAuxiliarTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistentContainerAuxiliarTypes.swift; sourceTree = ""; }; + 1454786920B2487300831016 /* CustomPersistentContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPersistentContainer.swift; sourceTree = ""; }; 146B042E208AEAE3002091BF /* FetchRequestController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequestController+Extensions.swift"; sourceTree = ""; }; 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Extensions.swift"; sourceTree = ""; }; 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSArrayController+Extensions.swift"; sourceTree = ""; }; @@ -119,20 +125,23 @@ 14CC3362205B28CA00BA682A /* Core */ = { isa = PBXGroup; children = ( - 14CC3363205B28CA00BA682A /* Container */, + 14CC3363205B28CA00BA682A /* Persistent Container */, 14CC3367205B28CA00BA682A /* Query */, ); path = Core; sourceTree = ""; }; - 14CC3363205B28CA00BA682A /* Container */ = { + 14CC3363205B28CA00BA682A /* Persistent Container */ = { isa = PBXGroup; children = ( 14CC3364205B28CA00BA682A /* PersistentContainer.swift */, + 1454786920B2487300831016 /* CustomPersistentContainer.swift */, + 1454786720B2484800831016 /* PersistentContainerAuxiliarTypes.swift */, + 1454786520B247F300831016 /* PersistentContainerType.swift */, 14CC3365205B28CA00BA682A /* ManagedObjectContext.swift */, 14CC3366205B28CA00BA682A /* ManagedObject.swift */, ); - path = Container; + path = "Persistent Container"; sourceTree = ""; }; 14CC3367205B28CA00BA682A /* Query */ = { @@ -247,13 +256,16 @@ 146B042F208AEAE3002091BF /* FetchRequestController+Extensions.swift in Sources */, 14CC337F205B28CA00BA682A /* SortDescriptor.swift in Sources */, 14CC337A205B28CA00BA682A /* ManagedObject.swift in Sources */, + 1454786A20B2487300831016 /* CustomPersistentContainer.swift in Sources */, 14CC3382205B28CA00BA682A /* Queryable.swift in Sources */, 14CC3374205B28CA00BA682A /* NSArrayController+Extensions.swift in Sources */, 14CC3385205B28CA00BA682A /* FetchedResultsControllerDelegate.swift in Sources */, 14CC337C205B28CA00BA682A /* Expression.swift in Sources */, 14CC3380205B28CA00BA682A /* Config.swift in Sources */, 14CC3377205B28CA00BA682A /* NSCollectionView+Extensions.swift in Sources */, + 1454786820B2484900831016 /* PersistentContainerAuxiliarTypes.swift in Sources */, 14CC3378205B28CA00BA682A /* PersistentContainer.swift in Sources */, + 1454786620B247F300831016 /* PersistentContainerType.swift in Sources */, 14CC337B205B28CA00BA682A /* Query.swift in Sources */, 14CC3384205B28CA00BA682A /* FetchedResultsSectionInfo.swift in Sources */, 14CC3375205B28CA00BA682A /* UITableView+Extensions.swift in Sources */, diff --git a/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift b/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift deleted file mode 100644 index a3a8ff6..0000000 --- a/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift +++ /dev/null @@ -1,309 +0,0 @@ -// -// PersistentContainer.swift -// AlecrimCoreData -// -// Created by Vanderlei Martinelli on 11/03/18. -// Copyright © 2018 Alecrim. All rights reserved. -// - -import Foundation -import CoreData - -// MARK: - - -@objc(ALCPersistentContainer) -open class PersistentContainer: NSPersistentContainer { - - // MARK: - - - private var didImportUbiquitousContentNotificationObserver: NSObjectProtocol? - - // MARK: - - - public convenience init() { - try! self.init(storageType: .disk, managedObjectModel: type(of: self).managedObjectModel(), persistentStoreURL: type(of: self).persistentStoreURL(), ubiquitousConfiguration: nil) - } - - public init(storageType: PersistentContainerStorageType, managedObjectModel: NSManagedObjectModel, persistentStoreURL: URL, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { - // - let name = persistentStoreURL.deletingPathExtension().lastPathComponent - super.init(name: name, managedObjectModel: managedObjectModel) - - // - if storageType == .disk { - let directoryPath = persistentStoreURL.deletingLastPathComponent().path - - if !FileManager.default.fileExists(atPath: directoryPath) { - try FileManager.default.createDirectory(atPath: directoryPath, withIntermediateDirectories: true) - } - } - - // - let persistentStoreDescription = NSPersistentStoreDescription(url: persistentStoreURL) - - persistentStoreDescription.type = (storageType == .disk ? NSSQLiteStoreType : NSInMemoryStoreType) - persistentStoreDescription.shouldAddStoreAsynchronously = false - persistentStoreDescription.shouldInferMappingModelAutomatically = true - persistentStoreDescription.shouldMigrateStoreAutomatically = true - - #if os(macOS) || os(iOS) - if let ubiquitousConfiguration = ubiquitousConfiguration { - persistentStoreDescription.setOption(ubiquitousConfiguration.containerIdentifier as NSString, forKey: NSPersistentStoreUbiquitousContainerIdentifierKey) - persistentStoreDescription.setOption(ubiquitousConfiguration.contentRelativePath as NSString, forKey: NSPersistentStoreUbiquitousContentURLKey) - persistentStoreDescription.setOption(ubiquitousConfiguration.contentName as NSString, forKey: NSPersistentStoreUbiquitousContentNameKey) - } - #endif - - // - self.persistentStoreDescriptions = [persistentStoreDescription] - - // this should run synchronously since shouldAddStoreAsynchronously is false - var outError: Swift.Error? - - self.loadPersistentStores { description, error in - if let error = error { - outError = error - } - - // - #if os(macOS) || os(iOS) - if let _ = ubiquitousConfiguration { - self.didImportUbiquitousContentNotificationObserver = NotificationCenter.default.addObserver(forName: .NSPersistentStoreDidImportUbiquitousContentChanges, object: self.persistentStoreCoordinator, queue: nil) { [weak self] notification in - guard let context = self?.viewContext.parent ?? self?.viewContext else { - return - } - - context.perform { - context.mergeChanges(fromContextDidSave: notification) - } - } - } - #endif - - // - self.viewContext.automaticallyMergesChangesFromParent = true - self.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - } - - if let outError = outError { - throw outError - } - } - - deinit { - if let didImportUbiquitousContentNotificationObserver = self.didImportUbiquitousContentNotificationObserver { - self.didImportUbiquitousContentNotificationObserver = nil - NotificationCenter.default.removeObserver(didImportUbiquitousContentNotificationObserver) - } - } - - // MARK: - - - open override func newBackgroundContext() -> NSManagedObjectContext { - let context = super.newBackgroundContext() - - context.automaticallyMergesChangesFromParent = true - context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - return context - } - - open override func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) { - super.performBackgroundTask { context in - // - context.automaticallyMergesChangesFromParent = true - context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - // - block(context) - } - } -} - -// MARK: - - -open class GenericPersistentContainer { - - // MARK: - - - fileprivate final class HelperPersistentContainer: PersistentContainer { - - private lazy var _viewContext: NSManagedObjectContext = { - let context = Context(concurrencyType: .mainQueueConcurrencyType) - - context.persistentStoreCoordinator = self.persistentStoreCoordinator - - context.automaticallyMergesChangesFromParent = true - context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - return context - }() - - fileprivate override var viewContext: NSManagedObjectContext { return self._viewContext } - - fileprivate override func newBackgroundContext() -> NSManagedObjectContext { - let context = Context(concurrencyType: .privateQueueConcurrencyType) - - context.persistentStoreCoordinator = self.persistentStoreCoordinator - - context.automaticallyMergesChangesFromParent = true - context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - return context - } - - fileprivate override func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) { - super.performBackgroundTask { context in - // - context.persistentStoreCoordinator = self.persistentStoreCoordinator - - // - context.automaticallyMergesChangesFromParent = true - context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - // - block(context) - } - } - - } - - - // MARK: - - - fileprivate let rawValue: NSPersistentContainer - - // MARK: - - - public convenience init() { - try! self.init(storageType: .disk, managedObjectModel: type(of: self).managedObjectModel(), persistentStoreURL: type(of: self).persistentStoreURL(), ubiquitousConfiguration: nil) - } - - public init(storageType: PersistentContainerStorageType = .disk, managedObjectModel: NSManagedObjectModel, persistentStoreURL: URL, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { - self.rawValue = try HelperPersistentContainer(storageType: storageType, managedObjectModel: managedObjectModel, persistentStoreURL: persistentStoreURL, ubiquitousConfiguration: ubiquitousConfiguration) - } - - // MARK: - - - open var viewContext: Context { - return unsafeDowncast(self.rawValue.viewContext, to: Context.self) - } - - open func newBackgroundContext() -> Context { - return unsafeDowncast(self.rawValue.newBackgroundContext(), to: Context.self) - } - - open func performBackgroundTask(_ block: @escaping (Context) -> Void) { - self.rawValue.performBackgroundTask { managedObjectContext in - block(unsafeDowncast(managedObjectContext, to: Context.self)) - } - } - -} - - -// MARK: - - -public enum PersistentContainerStorageType { - case disk - case memory -} - - -public struct PersistentContainerUbiquitousConfiguration { - public let containerIdentifier: String - public let contentRelativePath: String - public let contentName: String - - public init(containerIdentifier: String, contentRelativePath: String = "Data/TransactionLogs", contentName: String = "UbiquityStore") { - self.containerIdentifier = containerIdentifier - self.contentRelativePath = contentRelativePath - self.contentName = contentName - } - -} - -public enum PersistentContainerError: Error { - case invalidManagedObjectModelURL - case invalidPersistentStoreURL - case invalidGroupContainerURL - case applicationSupportDirectoryNotFound - case managedObjectModelNotFound -} - - -// MARK: - - -public protocol PersistentContainerType: AnyObject {} - -extension PersistentContainer: PersistentContainerType {} -extension GenericPersistentContainer: PersistentContainerType {} - -extension PersistentContainerType { - - public static func managedObjectModel(withName name: String? = nil, in bundle: Bundle? = nil) throws -> NSManagedObjectModel { - let bundle = bundle ?? Bundle(for: Self.self) - let name = name ?? bundle.bundleURL.deletingPathExtension().lastPathComponent - - let managedObjectModelURL = try self.managedObjectModelURL(withName: name, in: bundle) - - guard let managedObjectModel = NSManagedObjectModel(contentsOf: managedObjectModelURL) else { - throw PersistentContainerError.managedObjectModelNotFound - } - - return managedObjectModel - } - - private static func managedObjectModelURL(withName name: String, in bundle: Bundle) throws -> URL { - let resourceURL = bundle.url(forResource: name, withExtension: "momd") ?? bundle.url(forResource: name, withExtension: "mom") - - guard let managedObjectModelURL = resourceURL else { - throw PersistentContainerError.invalidManagedObjectModelURL - } - - return managedObjectModelURL - } - -} - -extension PersistentContainerType { - - public static func persistentStoreURL(withName name: String? = nil, in bundle: Bundle? = nil) throws -> URL { - guard let applicationSupportURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).last else { - throw PersistentContainerError.applicationSupportDirectoryNotFound - } - - let bundle = bundle ?? Bundle.main - let bundleLastPathComponent = bundle.bundleURL.deletingPathExtension().lastPathComponent - let name = name ?? bundleLastPathComponent - - let persistentStoreURL = applicationSupportURL - .appendingPathComponent(bundleLastPathComponent, isDirectory: true) - .appendingPathComponent("CoreData", isDirectory: true) - .appendingPathComponent(name, isDirectory: false) - .appendingPathExtension("sqlite") - - return persistentStoreURL - } - - public static func persistentStoreURL(withName name: String? = nil, forSecurityApplicationGroupIdentifier applicationGroupIdentifier: String, in bundle: Bundle? = nil) throws -> URL { - guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: applicationGroupIdentifier) else { - throw PersistentContainerError.invalidGroupContainerURL - } - - let bundle = bundle ?? Bundle.main - let bundleLastPathComponent = bundle.bundleURL.deletingPathExtension().lastPathComponent - let name = name ?? bundleLastPathComponent - - let persistentStoreURL = containerURL - .appendingPathComponent("Library", isDirectory: true) - .appendingPathComponent("Application Support", isDirectory: true) - .appendingPathComponent(bundleLastPathComponent, isDirectory: true) - .appendingPathComponent("CoreData", isDirectory: true) - .appendingPathComponent(name, isDirectory: false) - .appendingPathExtension("sqlite") - - return persistentStoreURL - } - -} diff --git a/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift b/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift new file mode 100644 index 0000000..55ba6b3 --- /dev/null +++ b/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift @@ -0,0 +1,89 @@ +// +// CustomPersistentContainer.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 20/05/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +import Foundation +import CoreData + +open class CustomPersistentContainer { + + // MARK: - + + fileprivate final class HelperPersistentContainer: PersistentContainer { + + private lazy var _viewContext: NSManagedObjectContext = { + let context = Context(concurrencyType: .mainQueueConcurrencyType) + + context.persistentStoreCoordinator = self.persistentStoreCoordinator + + context.automaticallyMergesChangesFromParent = true + context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + + return context + }() + + fileprivate override var viewContext: NSManagedObjectContext { return self._viewContext } + + fileprivate override func newBackgroundContext() -> NSManagedObjectContext { + let context = Context(concurrencyType: .privateQueueConcurrencyType) + + context.persistentStoreCoordinator = self.persistentStoreCoordinator + + context.automaticallyMergesChangesFromParent = true + context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + + return context + } + + fileprivate override func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) { + super.performBackgroundTask { context in + // + context.persistentStoreCoordinator = self.persistentStoreCoordinator + + // + context.automaticallyMergesChangesFromParent = true + context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + + // + block(context) + } + } + + } + + + // MARK: - + + fileprivate let rawValue: NSPersistentContainer + + // MARK: - + + public convenience init() { + try! self.init(storageType: .disk, managedObjectModel: type(of: self).managedObjectModel(), persistentStoreURL: type(of: self).persistentStoreURL(), ubiquitousConfiguration: nil) + } + + public init(storageType: PersistentContainerStorageType = .disk, managedObjectModel: NSManagedObjectModel, persistentStoreURL: URL, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { + self.rawValue = try HelperPersistentContainer(storageType: storageType, managedObjectModel: managedObjectModel, persistentStoreURL: persistentStoreURL, ubiquitousConfiguration: ubiquitousConfiguration) + } + + // MARK: - + + open var viewContext: Context { + return unsafeDowncast(self.rawValue.viewContext, to: Context.self) + } + + open func newBackgroundContext() -> Context { + return unsafeDowncast(self.rawValue.newBackgroundContext(), to: Context.self) + } + + open func performBackgroundTask(_ block: @escaping (Context) -> Void) { + self.rawValue.performBackgroundTask { managedObjectContext in + block(unsafeDowncast(managedObjectContext, to: Context.self)) + } + } + +} diff --git a/Source/AlecrimCoreData/Core/Container/ManagedObject.swift b/Source/AlecrimCoreData/Core/Persistent Container/ManagedObject.swift similarity index 100% rename from Source/AlecrimCoreData/Core/Container/ManagedObject.swift rename to Source/AlecrimCoreData/Core/Persistent Container/ManagedObject.swift diff --git a/Source/AlecrimCoreData/Core/Container/ManagedObjectContext.swift b/Source/AlecrimCoreData/Core/Persistent Container/ManagedObjectContext.swift similarity index 100% rename from Source/AlecrimCoreData/Core/Container/ManagedObjectContext.swift rename to Source/AlecrimCoreData/Core/Persistent Container/ManagedObjectContext.swift diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift new file mode 100644 index 0000000..f825ccd --- /dev/null +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift @@ -0,0 +1,119 @@ +// +// PersistentContainer.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 11/03/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +import Foundation +import CoreData + +@objc(ALCPersistentContainer) +open class PersistentContainer: NSPersistentContainer { + + // MARK: - + + private var didImportUbiquitousContentNotificationObserver: NSObjectProtocol? + + // MARK: - + + public convenience init() { + try! self.init(storageType: .disk, managedObjectModel: type(of: self).managedObjectModel(), persistentStoreURL: type(of: self).persistentStoreURL(), ubiquitousConfiguration: nil) + } + + public init(storageType: PersistentContainerStorageType, managedObjectModel: NSManagedObjectModel, persistentStoreURL: URL, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { + // + let name = persistentStoreURL.deletingPathExtension().lastPathComponent + super.init(name: name, managedObjectModel: managedObjectModel) + + // + if storageType == .disk { + let directoryPath = persistentStoreURL.deletingLastPathComponent().path + + if !FileManager.default.fileExists(atPath: directoryPath) { + try FileManager.default.createDirectory(atPath: directoryPath, withIntermediateDirectories: true) + } + } + + // + let persistentStoreDescription = NSPersistentStoreDescription(url: persistentStoreURL) + + persistentStoreDescription.type = (storageType == .disk ? NSSQLiteStoreType : NSInMemoryStoreType) + persistentStoreDescription.shouldAddStoreAsynchronously = false + persistentStoreDescription.shouldInferMappingModelAutomatically = true + persistentStoreDescription.shouldMigrateStoreAutomatically = true + + #if os(macOS) || os(iOS) + if let ubiquitousConfiguration = ubiquitousConfiguration { + persistentStoreDescription.setOption(ubiquitousConfiguration.containerIdentifier as NSString, forKey: NSPersistentStoreUbiquitousContainerIdentifierKey) + persistentStoreDescription.setOption(ubiquitousConfiguration.contentRelativePath as NSString, forKey: NSPersistentStoreUbiquitousContentURLKey) + persistentStoreDescription.setOption(ubiquitousConfiguration.contentName as NSString, forKey: NSPersistentStoreUbiquitousContentNameKey) + } + #endif + + // + self.persistentStoreDescriptions = [persistentStoreDescription] + + // this should run synchronously since shouldAddStoreAsynchronously is false + var outError: Swift.Error? + + self.loadPersistentStores { description, error in + if let error = error { + outError = error + } + + // + #if os(macOS) || os(iOS) + if let _ = ubiquitousConfiguration { + self.didImportUbiquitousContentNotificationObserver = NotificationCenter.default.addObserver(forName: .NSPersistentStoreDidImportUbiquitousContentChanges, object: self.persistentStoreCoordinator, queue: nil) { [weak self] notification in + guard let context = self?.viewContext.parent ?? self?.viewContext else { + return + } + + context.perform { + context.mergeChanges(fromContextDidSave: notification) + } + } + } + #endif + + // + self.viewContext.automaticallyMergesChangesFromParent = true + self.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + } + + if let outError = outError { + throw outError + } + } + + deinit { + if let didImportUbiquitousContentNotificationObserver = self.didImportUbiquitousContentNotificationObserver { + self.didImportUbiquitousContentNotificationObserver = nil + NotificationCenter.default.removeObserver(didImportUbiquitousContentNotificationObserver) + } + } + + // MARK: - + + open override func newBackgroundContext() -> NSManagedObjectContext { + let context = super.newBackgroundContext() + + context.automaticallyMergesChangesFromParent = true + context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + + return context + } + + open override func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) { + super.performBackgroundTask { context in + // + context.automaticallyMergesChangesFromParent = true + context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + + // + block(context) + } + } +} diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerAuxiliarTypes.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerAuxiliarTypes.swift new file mode 100644 index 0000000..a63340b --- /dev/null +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerAuxiliarTypes.swift @@ -0,0 +1,35 @@ +// +// PersistentContainerAuxiliarTypes.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 20/05/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +import Foundation + +public enum PersistentContainerStorageType { + case disk + case memory +} + +public struct PersistentContainerUbiquitousConfiguration { + public let containerIdentifier: String + public let contentRelativePath: String + public let contentName: String + + public init(containerIdentifier: String, contentRelativePath: String = "Data/TransactionLogs", contentName: String = "UbiquityStore") { + self.containerIdentifier = containerIdentifier + self.contentRelativePath = contentRelativePath + self.contentName = contentName + } + +} + +public enum PersistentContainerError: Error { + case invalidManagedObjectModelURL + case invalidPersistentStoreURL + case invalidGroupContainerURL + case applicationSupportDirectoryNotFound + case managedObjectModelNotFound +} diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift new file mode 100644 index 0000000..f21b370 --- /dev/null +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift @@ -0,0 +1,83 @@ +// +// PersistentContainerType.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 20/05/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +import Foundation + +public protocol PersistentContainerType: AnyObject {} + +extension PersistentContainer: PersistentContainerType {} +extension CustomPersistentContainer: PersistentContainerType {} + +extension PersistentContainerType { + + public static func managedObjectModel(withName name: String? = nil, in bundle: Bundle? = nil) throws -> NSManagedObjectModel { + let bundle = bundle ?? Bundle(for: Self.self) + let name = name ?? bundle.bundleURL.deletingPathExtension().lastPathComponent + + let managedObjectModelURL = try self.managedObjectModelURL(withName: name, in: bundle) + + guard let managedObjectModel = NSManagedObjectModel(contentsOf: managedObjectModelURL) else { + throw PersistentContainerError.managedObjectModelNotFound + } + + return managedObjectModel + } + + private static func managedObjectModelURL(withName name: String, in bundle: Bundle) throws -> URL { + let resourceURL = bundle.url(forResource: name, withExtension: "momd") ?? bundle.url(forResource: name, withExtension: "mom") + + guard let managedObjectModelURL = resourceURL else { + throw PersistentContainerError.invalidManagedObjectModelURL + } + + return managedObjectModelURL + } + +} + +extension PersistentContainerType { + + public static func persistentStoreURL(withName name: String? = nil, in bundle: Bundle? = nil) throws -> URL { + guard let applicationSupportURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).last else { + throw PersistentContainerError.applicationSupportDirectoryNotFound + } + + let bundle = bundle ?? Bundle.main + let bundleLastPathComponent = bundle.bundleURL.deletingPathExtension().lastPathComponent + let name = name ?? bundleLastPathComponent + + let persistentStoreURL = applicationSupportURL + .appendingPathComponent(bundleLastPathComponent, isDirectory: true) + .appendingPathComponent("CoreData", isDirectory: true) + .appendingPathComponent(name, isDirectory: false) + .appendingPathExtension("sqlite") + + return persistentStoreURL + } + + public static func persistentStoreURL(withName name: String? = nil, forSecurityApplicationGroupIdentifier applicationGroupIdentifier: String, in bundle: Bundle? = nil) throws -> URL { + guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: applicationGroupIdentifier) else { + throw PersistentContainerError.invalidGroupContainerURL + } + + let bundle = bundle ?? Bundle.main + let bundleLastPathComponent = bundle.bundleURL.deletingPathExtension().lastPathComponent + let name = name ?? bundleLastPathComponent + + let persistentStoreURL = containerURL + .appendingPathComponent("Library", isDirectory: true) + .appendingPathComponent("Application Support", isDirectory: true) + .appendingPathComponent(bundleLastPathComponent, isDirectory: true) + .appendingPathComponent("CoreData", isDirectory: true) + .appendingPathComponent(name, isDirectory: false) + .appendingPathExtension("sqlite") + + return persistentStoreURL + } + +} From e70758ae68212b7ba882805196ffb35b6cac6f1f Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sun, 20 May 2018 21:25:49 -0300 Subject: [PATCH 15/18] Added "Package.swift" --- Package.swift | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Package.swift diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..a31d459 --- /dev/null +++ b/Package.swift @@ -0,0 +1,3 @@ +import PackageDescription + +let package = Package(name: "AlecrimCoreData") From 23df685495c94f612be0218179c3be674cd3edfc Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sun, 20 May 2018 21:28:23 -0300 Subject: [PATCH 16/18] Bumped version and build numbers --- AlecrimCoreData.podspec | 2 +- Source/AlecrimCoreData.xcodeproj/project.pbxproj | 4 ++-- Source/AlecrimCoreData/Info.plist | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AlecrimCoreData.podspec b/AlecrimCoreData.podspec index 0b53efc..7b14cf9 100644 --- a/AlecrimCoreData.podspec +++ b/AlecrimCoreData.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "AlecrimCoreData" - s.version = "6.0-beta.3" + s.version = "6.0" s.summary = "A powerful and elegant Core Data framework for Swift." s.homepage = "https://www.alecrim.com/AlecrimCoreData" diff --git a/Source/AlecrimCoreData.xcodeproj/project.pbxproj b/Source/AlecrimCoreData.xcodeproj/project.pbxproj index 758bc89..ec0b171 100644 --- a/Source/AlecrimCoreData.xcodeproj/project.pbxproj +++ b/Source/AlecrimCoreData.xcodeproj/project.pbxproj @@ -412,7 +412,7 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1798; + CURRENT_PROJECT_VERSION = 1815; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -444,7 +444,7 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1798; + CURRENT_PROJECT_VERSION = 1815; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/Source/AlecrimCoreData/Info.plist b/Source/AlecrimCoreData/Info.plist index bb6321d..e2953de 100644 --- a/Source/AlecrimCoreData/Info.plist +++ b/Source/AlecrimCoreData/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + 6.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright From 93ebcf4a700dd38a2b261b8b847774b80fb8d7e6 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sun, 20 May 2018 21:29:24 -0300 Subject: [PATCH 17/18] Updated README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d6dfec5..08b523f 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Simple do that: let query = persistentContainer.viewContext.people .where { \.city == "Piracicaba" } .orderBy { \.name } - + for person in query.dropFirst(20).prefix(10) { print(person.name, person.address) } @@ -30,7 +30,7 @@ persistentContainer.performBackgroundTask { context in .filtered(using: \.country == "Brazil" && \.isContributor == true) .sorted(by: .descending(\.contributionCount)) .sorted(by: \.name) - + if let person = query.first() { print(person.name, person.email) } @@ -60,7 +60,7 @@ Some well known features and functionalities may be reimplemented in a future re ## Contribute If you have any problems or need more information, please open an issue using the provided GitHub link. -You can also contribute by fixing errors or creating new features. When doing this, please submit your pull requests to this repository as I do not have much time to "hunt" forks for not submited patches. +You can also contribute by fixing errors or creating new features. When doing this, please submit your pull requests to this repository as I do not have much time to "hunt" forks for not submitted patches. - master - The production branch. Clone or fork this repository for the latest copy. - develop - The active development branch. [Pull requests](https://help.github.com/articles/creating-a-pull-request) should be directed to this branch. From e8382f1a886468a6cd3e316069b4a0adcd5789bf Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sun, 20 May 2018 21:30:09 -0300 Subject: [PATCH 18/18] Added missing import --- .../Core/Persistent Container/PersistentContainerType.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift index f21b370..5b93b2d 100644 --- a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift @@ -7,6 +7,7 @@ // import Foundation +import CoreData public protocol PersistentContainerType: AnyObject {}