From 30ec5e54e01c6299b66743b1eb89e4135d24173b Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 12 Jan 2022 22:19:24 -0500 Subject: [PATCH] Use old-style collection view datasource so we can use fetchBatchSize to avoid loading every single item into memory --- .../Screens/Items/ItemsViewController.swift | 79 ++++++++++++------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/Reader/Screens/Items/ItemsViewController.swift b/Reader/Screens/Items/ItemsViewController.swift index 46abefc..61f089f 100644 --- a/Reader/Screens/Items/ItemsViewController.swift +++ b/Reader/Screens/Items/ItemsViewController.swift @@ -15,9 +15,10 @@ class ItemsViewController: UIViewController { let fetchRequest: NSFetchRequest private var collectionView: UICollectionView! - private var dataSource: UICollectionViewDiffableDataSource! private var resultsController: NSFetchedResultsController! + private var batchUpdates: [() -> Void] = [] + init(fetchRequest: NSFetchRequest, fervorController: FervorController) { self.fervorController = fervorController self.fetchRequest = fetchRequest @@ -37,56 +38,74 @@ class ItemsViewController: UIViewController { let layout = UICollectionViewCompositionalLayout.list(using: configuration) collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) collectionView.delegate = self + collectionView.dataSource = self collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] collectionView.register(ItemCollectionViewCell.self, forCellWithReuseIdentifier: "itemCell") view.addSubview(collectionView) - dataSource = createDataSource() - - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.items]) - dataSource.apply(snapshot, animatingDifferences: false) - fetchRequest.sortDescriptors = [NSSortDescriptor(key: "published", ascending: false)] + fetchRequest.fetchBatchSize = 20 resultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: fervorController.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil) resultsController.delegate = self try! resultsController.performFetch() } - - private func createDataSource() -> UICollectionViewDiffableDataSource { - let itemCell = UICollectionView.CellRegistration { [unowned self] cell, indexPath, item in - cell.updateUI(item: item) - cell.delegate = self - } - let dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in - return collectionView.dequeueConfiguredReusableCell(using: itemCell, for: indexPath, item: item) - } - return dataSource - } } -extension ItemsViewController { - enum Section: Hashable { - case items +extension ItemsViewController: UICollectionViewDataSource { + func numberOfSections(in collectionView: UICollectionView) -> Int { + 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 { - func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { - var snapshot = self.dataSource.snapshot() - snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .items)) - // use resultsController here instead of controller so we don't have to cast - snapshot.appendItems(resultsController.fetchedObjects!, toSection: .items) - self.dataSource.apply(snapshot, animatingDifferences: false) + func controllerWillChangeContent(_ controller: NSFetchedResultsController) { + batchUpdates = [] + } + func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + collectionView.performBatchUpdates { + for update in self.batchUpdates { + update() + } + } + // clear to prevent retain cycles + batchUpdates = [] + } + func controller(_ controller: NSFetchedResultsController, 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 { func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - guard let item = dataSource.itemIdentifier(for: indexPath) else { - return nil - } + let item = resultsController.fetchedObjects![indexPath.row] return UIContextMenuConfiguration(identifier: nil, previewProvider: { ReadViewController(item: item, fervorController: self.fervorController) }, actionProvider: { _ in