2013-12-12 13 views
16

Ich möchte die NSFetchedResultsControllerRelegate in einem CollectionViewController verwenden. Daher habe ich gerade die Methode für den TableViewController für die CollectionView geändert.NSFetchedResultsContollerDelegate für 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]; 
} 

Aber ich weiß nicht, wie die WillChangeContent (beginUpdates für TableView) und DidChangeContent (endUpdates für TableVie w) zu handhaben für ein CollectionView.

Alles funktioniert gut, außer wenn ich ein Element von einem Abschnitt zu einem anderen Abschnitt verschiebe. Dann bekomme ich den folgenden Fehler.

Dies ist normalerweise ein Fehler innerhalb eines Beobachters von NSManagedObjectContextObjectsDidChangeNotification. Ungültiges Update: ungültige Anzahl von Artikeln in Abschnitt 0 ....

Irgendeine Idee, wie kann ich dieses Problem lösen?

Antwort

10

Die Kombination eines abgerufenen Ergebniscontrollers mit einer Sammlungsansicht ist ein bisschen schwierig. Das Problem erklärt in

Wenn Sie suchen, wie mit UICollectionView um die NSInternalInconsistencyException Runtime-Ausnahme zu bekommen, ich habe ein Beispiel auf GitHub detailliert, wie die Warteschlange Updates von NSFetchedResultsControllerDelegate.

Das Problem ist, dass die bestehende UITableView Klasse verwendet beginUpdates und endUpdates Chargen auf den Tisch zu unterbreiten. UICollectionView hat eine neue performBatchUpdates: Methode, die einen Blockparameter übernimmt, um die Sammlungsansicht zu aktualisieren. Das ist sexy, aber es funktioniert nicht gut mit dem vorhandenen Paradigma für NSFetchedResultsController.

Glück, das Artikel enthält auch eine Beispielimplementierung:

Aus der Readme:

Dies ist ein Beispiel dafür, wie die neuen UICollectionView verwenden mit NSFetchedResultsController. Der Trick besteht darin, die vorgenommenen Aktualisierungen über die NSFetchedResultsControllerDelegate in die Warteschlange zu stellen, bis der Controller seine Aktualisierungen beendet. UICollectionView hat nicht die gleichen beginUpdates und endUpdates die UITableView hat es leicht mit NSFetchedResultsController arbeiten zu lassen, so dass Sie sie in der Warteschlange oder Sie erhalten interne Konsistenz Laufzeit Ausnahmen.

+0

Danke, Martin. Ich habe das ohne die Problemumgehung zuvor versucht - das Update für die Problemumgehung nicht angezeigt. Jetzt mit Workaround des Fehlers in der Sammlungsansicht funktioniert es schließlich. Aufgrund der Tatsache, dass ich Kopf- und Fußzeilen habe, war dies eine sehr gute Hilfe. Ich hoffe dennoch, dass dieser Bug einmal gelöst wird. – aquarius68

+0

@ aquarius68: Es ist nicht wirklich ein Fehler. Das Problem besteht darin, dass die FRC-Delegatmethoden und die Aktualisierungsmethoden für die Sammlungsansicht nicht wirklich zusammenpassen. Das zu beheben würde bedeuten, eine der APIs zu ändern oder zu erweitern. - Aber ich bin froh, dass du es funktioniert hast. –

+0

Ich bekomme die Fehlermeldungen nicht mehr, aber es funktioniert noch nicht ganz; d. h. wenn der Benutzer das erste Element hinzufügt, funktioniert es, aber wenn der Benutzer das zweite Element hinzufügt, funktioniert es nur, wenn ich zu der Tabellenansicht zurückkehre, die Objekte enthält, die sich auf die Objekte der Sammlungsansicht beziehen. – aquarius68

27

Hier ist meine Implementierung mit Swift. Erstes ein Array von NSBlockOperations initialisieren:

var blockOperations: [NSBlockOperation] = [] 

In Controller ändert, Re-init das Array:

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

Im Änderungsobjektmethode hat:

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!]) 
       } 
      }) 
     ) 
    } 
} 

in der DID Änderungsabschnittsmethode:

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)) 
       } 
      }) 
     ) 
    } 
} 

Und schließlich hat in der tat Controller Gradmethode ändern:

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) 
    }) 
} 

ich persönlich einige Code in der deinit Methode, um die Vorgänge abzubrechen und, zugegeben, wenn die Viewcontroller im Begriff ist, aufgehoben werden:

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

    blockOperations.removeAll(keepCapacity: false) 
} 
+0

Das funktioniert wunderbar, "Plot"! Vielen Dank! –

+0

Kein '.Move' Änderungstyp für Abschnitte? – pkamb

+0

@pkamb gibt es keine "move" für Abschnitte in "UICollectionView". – Fogmeister

1

Hier ist ein bisschen Swift, die mit UICollectionViewController der installsStandardGestureForInteractiveMovement funktioniert und ist eine etwas dryed und schaltet den installsStandardGestureForInteractiveMovement so, dass alle Codepfade liegen auf der Hand. Es ist das gleiche Gesamtmuster wie der Code von 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() 
} 
9

Ich habe @-Lösung Plot es eigenes Objekt und konvertiert es zu 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) 
     }) 
    } 

} 

Verbrauch:

fetchedResultsController.delegate = CollectionViewFetchedResultsControllerDelegate(collectionView) 
Verwandte Themen