Skip to content

Instantly share code, notes, and snippets.

@everlof
Created January 19, 2020 07:06
Show Gist options
  • Save everlof/2bf4e0a6ab60201fbed687875f31ac03 to your computer and use it in GitHub Desktop.
Save everlof/2bf4e0a6ab60201fbed687875f31ac03 to your computer and use it in GitHub Desktop.
FetchedCollectionView with NSFetchedResultsController
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