NSFetchedResultsContollerDelegate for CollectionView
je voudrais utiliser le Nsfetchedresultscontrollerregate dans un CollectionViewController. Par conséquent, je viens de changer la méthode pour le TableViewController pour le 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];
}
Mais je ne sais pas comment gérer l' WillChangeContent
(beginUpdates
TableView
et DidChangeContent
(endUpdates
TableVie
w)CollectionView
.
Tout fonctionne bien sauf quand je déplace un article d'une section à une autre. Ensuite, j'obtiens l'erreur suivante.
il s'agit habituellement d'un bogue au sein d'un observateur de Nsmanagedobjectconcontextobjectsdidchangenotification. Mise à jour invalide: nombre d'éléments invalides dans la section 0....
une idée de comment résoudre ce problème?
4 réponses
combiner un contrôleur de résultats avec une vue collection est un peu délicat. Le problème est expliqué dans
si vous cherchez comment contourner le
NSInternalInconsistencyException
exception à l'exécution avecUICollectionView
, j'ai un exemple sur GitHub détaillant comment faire la queue mises à jour du NSFetchedResultsControllerDelegate.Le problème est que les
UITableView
classbeginUpdates
etendUpdates
pour soumettre les lots à la vue table.UICollectionView
a un nouveauperformBatchUpdates:
méthode, qui prend un paramètre bloc de pour mettre à jour la vue collection. C'est sexy, mais ça ne marche pas bien. avec le paradigme existant pour NSFetchedResultsController.
heureusement, cet article fournit aussi un échantillon application:
From the README:
Ceci est un exemple d'utilisation du nouveau
UICollectionView
avecNSFetchedResultsController
. Le truc est de faire la queue pour les mises à jour par le biais de l'NSFetchedResultsControllerDelegate
jusqu'à ce que le contrôleur de finitions ses mises à jour.UICollectionView
n'a pas la mêmebeginUpdates
etendUpdates
UITableView
doit le laisser fonctionner facilementNSFetchedResultsController
, donc vous devez les faire la queue ou vous obtenez les exceptions d'exécution de cohérence interne.
Voici mon implémentation avec Swift. Initialise d'abord un tableau D'opérations Nsblock:
var blockOperations: [NSBlockOperation] = []
in controller va changer, re-init le tableau:
func controllerWillChangeContent(controller: NSFetchedResultsController) {
blockOperations.removeAll(keepCapacity: false)
}
dans la méthode did change object:
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!])
}
})
)
}
}
Dans le section de changement de méthode:
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))
}
})
)
}
}
et finalement, dans le contrôleur did a changé la méthode de contenu:
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)
})
}
j'ai personnellement ajouté du code dans la méthode deinit aussi, afin d'annuler les opérations quand le ViewController est sur le point d'être désactivé:
deinit {
// Cancel all block operations when VC deallocates
for operation: NSBlockOperation in blockOperations {
operation.cancel()
}
blockOperations.removeAll(keepCapacity: false)
}
j'ai fait @solution de Plotobjet et converti en 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 { .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 { .start() }
}, completion: { finished in
self.blockOperations.removeAll(keepCapacity: false)
})
}
}
Utilisation:
fetchedResultsController.delegate = CollectionViewFetchedResultsControllerDelegate(collectionView)
Swift 4 version
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 { .start() }
}, completion: { finished in
self.blockOperations.removeAll(keepingCapacity: false)
})
}
voici un peu de Swift qui fonctionne avec uicollection Viewcontroller's installsstandardgesturefor interactivemovement et qui est un peu secoué et qui commute sur l'installsstandardgesturefor interactivemovement pour que tous les chemins de code soient évidents. C'est le même schéma général que le code 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()
}