Created
January 19, 2020 07:06
-
-
Save everlof/2bf4e0a6ab60201fbed687875f31ac03 to your computer and use it in GitHub Desktop.
FetchedCollectionView with NSFetchedResultsController
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import CoreData | |
import os | |
class FetchedCollectionViewCell<DataObject>: UICollectionViewCell { | |
static var identifier: String { String(describing: Self.self) } | |
func update(_: DataObject) { | |
fatalError() | |
} | |
} | |
class FetchedCollectionView<DataObject: NSManagedObject, CellType: FetchedCollectionViewCell<DataObject>>: UICollectionView, | |
UICollectionViewDataSource, | |
NSFetchedResultsControllerDelegate { | |
// Checks if we should reload the collection view to fix a bug @ http://openradar.appspot.com/12954582 | |
private var shouldReloadCollectionView = true | |
private var blockOperations = [BlockOperation]() | |
var preUpdate: ((CellType) -> Void) = { _ in } | |
private lazy var log: OSLog = { | |
OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "FetchedCollectionView") | |
}() | |
private var _predicate: NSPredicate = NSPredicate(value: true) | |
var predicate: NSPredicate { | |
get { | |
_predicate | |
} | |
} | |
private var _sortDescriptors: [NSSortDescriptor] = [] | |
var sortDescriptors: [NSSortDescriptor] { | |
get { | |
_sortDescriptors | |
} | |
} | |
var fetchRequest: NSFetchRequest<DataObject> { | |
let fetchRequest: NSFetchRequest<DataObject> = NSFetchRequest(entityName: DataObject.entity().name!) | |
fetchRequest.sortDescriptors = sortDescriptors | |
fetchRequest.predicate = predicate | |
return fetchRequest | |
} | |
lazy var fetchedResultController: NSFetchedResultsController<DataObject> = { | |
let fetchedResultController = NSFetchedResultsController(fetchRequest: fetchRequest, | |
managedObjectContext: context, | |
sectionNameKeyPath: nil, | |
cacheName: nil) | |
fetchedResultController.delegate = self | |
os_log("initial filter, using fetch-request => %@", log: log, type: .info, String(describing: fetchedResultController.fetchRequest)) | |
do { | |
try fetchedResultController.performFetch() | |
} catch { | |
os_log("Error when fetching in %@: %@", log: log, type: .info, type(of: self).description(), String(describing: error)) | |
} | |
self.reloadData() | |
return fetchedResultController | |
}() | |
let context: NSManagedObjectContext | |
init(context: NSManagedObjectContext, layout: UICollectionViewLayout) { | |
self.context = context | |
super.init(frame: .zero, collectionViewLayout: layout) | |
register(CellType.self, forCellWithReuseIdentifier: CellType.identifier) | |
dataSource = self | |
} | |
required init?(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
func fetch() { | |
fetchedResultController.fetchRequest.predicate = predicate | |
fetchedResultController.fetchRequest.sortDescriptors = sortDescriptors | |
os_log("update filter, using fetch-request => %@", log: log, type: .info, String(describing: fetchedResultController.fetchRequest)) | |
do { | |
try fetchedResultController.performFetch() | |
} catch { | |
os_log("Error when fetching in %@: %@", log: log, type: .info, type(of: self).description(), String(describing: error)) | |
} | |
reloadData() | |
} | |
// MARK: UICollectionViewDataSource | |
func numberOfSections(in collectionView: UICollectionView) -> Int { | |
return fetchedResultController.sections?.count ?? 0 | |
} | |
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { | |
let nbrInSection = fetchedResultController.sections?[section].numberOfObjects ?? 0 | |
os_log("numberOfItemsInSection => %d", log: log, type: .debug, nbrInSection) | |
return nbrInSection | |
} | |
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { | |
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellType.identifier, for: indexPath) as? CellType else { | |
fatalError("Cell couldn't be casted to \(CellType.self)") | |
} | |
let object = fetchedResultController.object(at: indexPath) | |
os_log("Update cell for document => %@", log: log, type: .info, object) | |
preUpdate(cell) | |
cell.update(object) | |
return cell | |
} | |
// MARK: - NSFetchedResultsControllerDelegate | |
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { | |
switch type { | |
case .insert: | |
if numberOfSections > 0 { | |
if numberOfItems(inSection: newIndexPath!.section) == 0 { | |
os_log("shouldReloadCollectionView => YES", log: log, type: .debug) | |
shouldReloadCollectionView = true | |
} else { | |
blockOperations.append(BlockOperation { | |
self.insertItems(at: [newIndexPath!]) | |
}) | |
} | |
} else { | |
os_log("shouldReloadCollectionView => YES", log: log, type: .debug) | |
} | |
case .update: | |
blockOperations.append(BlockOperation { | |
self.reloadItems(at: [indexPath!]) | |
}) | |
case .delete: | |
blockOperations.append(BlockOperation { | |
self.deleteItems(at: [indexPath!]) | |
}) | |
case .move: | |
blockOperations.append(BlockOperation { | |
self.moveItem(at: indexPath!, to: newIndexPath!) | |
}) | |
} | |
} | |
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { | |
os_log("controllerWillChangeContent, emptying `blockOperations`", log: log, type: .debug) | |
blockOperations = [] | |
} | |
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { | |
os_log("controllerDidChangeContent, executing `blockOperations`", log: log, type: .debug) | |
if shouldReloadCollectionView { | |
os_log("Reloading collectionView", log: log, type: .debug) | |
reloadData() | |
} else { | |
performBatchUpdates({ blockOperations.forEach { $0.start() } }, completion: { _ in | |
os_log("Done executing `blockOperations`", log: self.log, type: .debug) | |
}) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment