Use old-style collection view datasource so we can use fetchBatchSize to

avoid loading every single item into memory
This commit is contained in:
Shadowfacts 2022-01-12 22:19:24 -05:00
parent a368bc4365
commit 30ec5e54e0
1 changed files with 49 additions and 30 deletions

View File

@ -15,9 +15,10 @@ class ItemsViewController: UIViewController {
let fetchRequest: NSFetchRequest<Item> let fetchRequest: NSFetchRequest<Item>
private var collectionView: UICollectionView! private var collectionView: UICollectionView!
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
private var resultsController: NSFetchedResultsController<Item>! private var resultsController: NSFetchedResultsController<Item>!
private var batchUpdates: [() -> Void] = []
init(fetchRequest: NSFetchRequest<Item>, fervorController: FervorController) { init(fetchRequest: NSFetchRequest<Item>, fervorController: FervorController) {
self.fervorController = fervorController self.fervorController = fervorController
self.fetchRequest = fetchRequest self.fetchRequest = fetchRequest
@ -37,56 +38,74 @@ class ItemsViewController: UIViewController {
let layout = UICollectionViewCompositionalLayout.list(using: configuration) let layout = UICollectionViewCompositionalLayout.list(using: configuration)
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.delegate = self collectionView.delegate = self
collectionView.dataSource = self
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.register(ItemCollectionViewCell.self, forCellWithReuseIdentifier: "itemCell") collectionView.register(ItemCollectionViewCell.self, forCellWithReuseIdentifier: "itemCell")
view.addSubview(collectionView) view.addSubview(collectionView)
dataSource = createDataSource()
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.items])
dataSource.apply(snapshot, animatingDifferences: false)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "published", ascending: false)] fetchRequest.sortDescriptors = [NSSortDescriptor(key: "published", ascending: false)]
fetchRequest.fetchBatchSize = 20
resultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: fervorController.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil) resultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: fervorController.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
resultsController.delegate = self resultsController.delegate = self
try! resultsController.performFetch() try! resultsController.performFetch()
} }
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
let itemCell = UICollectionView.CellRegistration<ItemCollectionViewCell, Item> { [unowned self] cell, indexPath, item in
cell.updateUI(item: item)
cell.delegate = self
}
let dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, item in
return collectionView.dequeueConfiguredReusableCell(using: itemCell, for: indexPath, item: item)
}
return dataSource
}
} }
extension ItemsViewController { extension ItemsViewController: UICollectionViewDataSource {
enum Section: Hashable { func numberOfSections(in collectionView: UICollectionView) -> Int {
case items return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return resultsController.fetchedObjects?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "itemCell", for: indexPath) as! ItemCollectionViewCell
cell.delegate = self
cell.updateUI(item: resultsController.fetchedObjects![indexPath.row])
return cell
} }
} }
extension ItemsViewController: NSFetchedResultsControllerDelegate { extension ItemsViewController: NSFetchedResultsControllerDelegate {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
var snapshot = self.dataSource.snapshot() batchUpdates = []
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .items)) }
// use resultsController here instead of controller so we don't have to cast func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
snapshot.appendItems(resultsController.fetchedObjects!, toSection: .items) collectionView.performBatchUpdates {
self.dataSource.apply(snapshot, animatingDifferences: false) for update in self.batchUpdates {
update()
}
}
// clear to prevent retain cycles
batchUpdates = []
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
guard let indexPath = indexPath else {
return
}
batchUpdates.append {
switch type {
case .insert:
self.collectionView.insertItems(at: [indexPath])
case .delete:
self.collectionView.deleteItems(at: [indexPath])
case .move:
if let newIndexPath = newIndexPath {
self.collectionView.moveItem(at: indexPath, to: newIndexPath)
}
case .update:
self.collectionView.reloadItems(at: [indexPath])
@unknown default:
break
}
}
} }
} }
extension ItemsViewController: UICollectionViewDelegate { extension ItemsViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
guard let item = dataSource.itemIdentifier(for: indexPath) else { let item = resultsController.fetchedObjects![indexPath.row]
return nil
}
return UIContextMenuConfiguration(identifier: nil, previewProvider: { return UIContextMenuConfiguration(identifier: nil, previewProvider: {
ReadViewController(item: item, fervorController: self.fervorController) ReadViewController(item: item, fervorController: self.fervorController)
}, actionProvider: { _ in }, actionProvider: { _ in