core-data delegates collectionview

core data - NSFetchedResultsContollerDelegate for CollectionView



core-data delegates (4)

Me gustaría usar el NSFetchedResultsControllerRelegate en un CollectionViewController. Por lo tanto, acabo de cambiar el método para el TableViewController para el CollectionView.

(void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { switch(type) { case NSFetchedResultsChangeInsert: [self.collectionView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]]; break; case NSFetchedResultsChangeDelete: [self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] ]; break; } } (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { UICollectionView *collectionView = self.collectionView; switch(type) { case NSFetchedResultsChangeInsert: [collectionView insertItemsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]]; break; case NSFetchedResultsChangeDelete: [collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]]; break; case NSFetchedResultsChangeUpdate: [collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]]; break; case NSFetchedResultsChangeMove: [collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]]; [collectionView insertItemsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]]; break; } } (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [self.collectionView reloadData]; }

Pero no sé cómo manejar el WillChangeContent ( beginUpdates para TableView ) y DidChangeContent ( endUpdates para TableVie w) para un CollectionView .

Todo funciona bien, excepto cuando muevo un elemento de una sección a otra. Entonces me sale el siguiente error.

Esto suele ser un error dentro de un observador de NSManagedObjectContextObjectsDidChangeNotification. Actualización inválida: número inválido de artículos en la sección 0 ....

¿Alguna idea de cómo puedo resolver este problema?


Aquí está mi implementación con Swift. Primero inicialice una matriz de NSBlockOperations:

var blockOperations: [NSBlockOperation] = []

En el controlador cambiará, reinicie la matriz:

func controllerWillChangeContent(controller: NSFetchedResultsController) { blockOperations.removeAll(keepCapacity: false) }

En el método de cambio de objeto:

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { if type == NSFetchedResultsChangeType.Insert { println("Insert Object: /(newIndexPath)") blockOperations.append( NSBlockOperation(block: { [weak self] in if let this = self { this.collectionView!.insertItemsAtIndexPaths([newIndexPath!]) } }) ) } else if type == NSFetchedResultsChangeType.Update { println("Update Object: /(indexPath)") blockOperations.append( NSBlockOperation(block: { [weak self] in if let this = self { this.collectionView!.reloadItemsAtIndexPaths([indexPath!]) } }) ) } else if type == NSFetchedResultsChangeType.Move { println("Move Object: /(indexPath)") blockOperations.append( NSBlockOperation(block: { [weak self] in if let this = self { this.collectionView!.moveItemAtIndexPath(indexPath!, toIndexPath: newIndexPath!) } }) ) } else if type == NSFetchedResultsChangeType.Delete { println("Delete Object: /(indexPath)") blockOperations.append( NSBlockOperation(block: { [weak self] in if let this = self { this.collectionView!.deleteItemsAtIndexPaths([indexPath!]) } }) ) } }

En el método de cambio de sección:

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { if type == NSFetchedResultsChangeType.Insert { println("Insert Section: /(sectionIndex)") blockOperations.append( NSBlockOperation(block: { [weak self] in if let this = self { this.collectionView!.insertSections(NSIndexSet(index: sectionIndex)) } }) ) } else if type == NSFetchedResultsChangeType.Update { println("Update Section: /(sectionIndex)") blockOperations.append( NSBlockOperation(block: { [weak self] in if let this = self { this.collectionView!.reloadSections(NSIndexSet(index: sectionIndex)) } }) ) } else if type == NSFetchedResultsChangeType.Delete { println("Delete Section: /(sectionIndex)") blockOperations.append( NSBlockOperation(block: { [weak self] in if let this = self { this.collectionView!.deleteSections(NSIndexSet(index: sectionIndex)) } }) ) } }

Y finalmente, en el controlador hizo cambiar el método de contenido:

func controllerDidChangeContent(controller: NSFetchedResultsController) { collectionView!.performBatchUpdates({ () -> Void in for operation: NSBlockOperation in self.blockOperations { operation.start() } }, completion: { (finished) -> Void in self.blockOperations.removeAll(keepCapacity: false) }) }

Personalmente también agregué algo de código en el método deinit, para cancelar las operaciones cuando el ViewController está a punto de ser desasignado:

deinit { // Cancel all block operations when VC deallocates for operation: NSBlockOperation in blockOperations { operation.cancel() } blockOperations.removeAll(keepCapacity: false) }


Aquí hay un poco de Swift que funciona con UICollectionViewController installStandardGestureForInteractiveMovement y está un poco SECADO y enciende el installstandardGestureForInteractiveMovement para que todas las rutas de código sean obvias. Es el mismo patrón general que el código de Plot.

var fetchedResultsProcessingOperations: [NSBlockOperation] = [] private func addFetchedResultsProcessingBlock(processingBlock:(Void)->Void) { fetchedResultsProcessingOperations.append(NSBlockOperation(block: processingBlock)) } func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { switch type { case .Insert: addFetchedResultsProcessingBlock {self.collectionView!.insertItemsAtIndexPaths([newIndexPath!])} case .Update: addFetchedResultsProcessingBlock {self.collectionView!.reloadItemsAtIndexPaths([indexPath!])} case .Move: addFetchedResultsProcessingBlock { // If installsStandardGestureForInteractiveMovement is on // the UICollectionViewController will handle this on its own. guard !self.installsStandardGestureForInteractiveMovement else { return } self.collectionView!.moveItemAtIndexPath(indexPath!, toIndexPath: newIndexPath!) } case .Delete: addFetchedResultsProcessingBlock {self.collectionView!.deleteItemsAtIndexPaths([indexPath!])} } } func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { switch type { case .Insert: addFetchedResultsProcessingBlock {self.collectionView!.insertSections(NSIndexSet(index: sectionIndex))} case .Update: addFetchedResultsProcessingBlock {self.collectionView!.reloadSections(NSIndexSet(index: sectionIndex))} case .Delete: addFetchedResultsProcessingBlock {self.collectionView!.deleteSections(NSIndexSet(index: sectionIndex))} case .Move: // Not something I''m worrying about right now. break } } func controllerDidChangeContent(controller: NSFetchedResultsController) { collectionView!.performBatchUpdates({ () -> Void in for operation in self.fetchedResultsProcessingOperations { operation.start() } }, completion: { (finished) -> Void in self.fetchedResultsProcessingOperations.removeAll(keepCapacity: false) }) } deinit { for operation in fetchedResultsProcessingOperations { operation.cancel() } fetchedResultsProcessingOperations.removeAll() }


Combinar un controlador de resultados recuperado con una vista de colección es un poco complicado. El problema se explica en

Si está buscando cómo evitar la excepción de tiempo de ejecución NSInternalInconsistencyException con UICollectionView , tengo un ejemplo en GitHub que detalla cómo poner en cola las actualizaciones de NSFetchedResultsControllerDelegate.

El problema es que la clase UITableView existente utiliza beginUpdates y endUpdates para enviar lotes a la vista de tabla. UICollectionView tiene un nuevo método performBatchUpdates: que toma un parámetro de bloque para actualizar la vista de colección. Eso es sexy, pero no funciona bien con el paradigma existente para NSFetchedResultsController.

Afortunadamente, ese artículo también proporciona una implementación de ejemplo:

Desde el README:

Este es un ejemplo de cómo usar el nuevo UICollectionView con NSFetchedResultsController . El truco consiste en poner en cola las actualizaciones realizadas a través de NSFetchedResultsControllerDelegate hasta que el controlador finalice sus actualizaciones. UICollectionView no tiene las mismas beginUpdates y endUpdates que UITableView tiene que dejar que funcione fácilmente con NSFetchedResultsController , por lo que tiene que hacer una cola o tener excepciones de tiempo de ejecución de coherencia interna.


Hice la solución de @ Plot, es un objeto propio y lo convertí a Swift 2

import Foundation import CoreData class CollectionViewFetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate { // MARK: Properties private let collectionView: UICollectionView private var blockOperations: [NSBlockOperation] = [] // MARK: Init init(collectionView: UICollectionView) { self.collectionView = collectionView } // MARK: Deinit deinit { blockOperations.forEach { $0.cancel() } blockOperations.removeAll(keepCapacity: false) } // MARK: NSFetchedResultsControllerDelegate func controllerWillChangeContent(controller: NSFetchedResultsController) { blockOperations.removeAll(keepCapacity: false) } func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { switch type { case .Insert: guard let newIndexPath = newIndexPath else { return } let op = NSBlockOperation { [weak self] in self?.collectionView.insertItemsAtIndexPaths([newIndexPath]) } blockOperations.append(op) case .Update: guard let newIndexPath = newIndexPath else { return } let op = NSBlockOperation { [weak self] in self?.collectionView.reloadItemsAtIndexPaths([newIndexPath]) } blockOperations.append(op) case .Move: guard let indexPath = indexPath else { return } guard let newIndexPath = newIndexPath else { return } let op = NSBlockOperation { [weak self] in self?.collectionView.moveItemAtIndexPath(indexPath, toIndexPath: newIndexPath) } blockOperations.append(op) case .Delete: guard let indexPath = indexPath else { return } let op = NSBlockOperation { [weak self] in self?.collectionView.deleteItemsAtIndexPaths([indexPath]) } blockOperations.append(op) } } func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { switch type { case .Insert: let op = NSBlockOperation { [weak self] in self?.collectionView.insertSections(NSIndexSet(index: sectionIndex)) } blockOperations.append(op) case .Update: let op = NSBlockOperation { [weak self] in self?.collectionView.reloadSections(NSIndexSet(index: sectionIndex)) } blockOperations.append(op) case .Delete: let op = NSBlockOperation { [weak self] in self?.collectionView.deleteSections(NSIndexSet(index: sectionIndex)) } blockOperations.append(op) default: break } } func controllerDidChangeContent(controller: NSFetchedResultsController) { collectionView.performBatchUpdates({ self.blockOperations.forEach { $0.start() } }, completion: { finished in self.blockOperations.removeAll(keepCapacity: false) }) } }

Uso:

fetchedResultsController.delegate = CollectionViewFetchedResultsControllerDelegate(collectionView)

Versión Swift 4

private var blockOperations: [BlockOperation] = [] func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { blockOperations.removeAll(keepingCapacity: false) } func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { let op: BlockOperation switch type { case .insert: guard let newIndexPath = newIndexPath else { return } op = BlockOperation { self.collectionView.insertItems(at: [newIndexPath]) } case .delete: guard let indexPath = indexPath else { return } op = BlockOperation { self.collectionView.deleteItems(at: [indexPath]) } case .move: guard let indexPath = indexPath, let newIndexPath = newIndexPath else { return } op = BlockOperation { self.collectionView.moveItem(at: indexPath, to: newIndexPath) } case .update: guard let indexPath = indexPath else { return } op = BlockOperation { self.collectionView.reloadItems(at: [indexPath]) } } blockOperations.append(op) } func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { collectionView.performBatchUpdates({ self.blockOperations.forEach { $0.start() } }, completion: { finished in self.blockOperations.removeAll(keepingCapacity: false) }) }