Use old-style collection view datasource so we can use fetchBatchSize to
avoid loading every single item into memory
This commit is contained in:
parent
a368bc4365
commit
30ec5e54e0
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue