2013-12-12 22 views
16

Mi piacerebbe utilizzare NSFetchedResultsControllerRelegate in un controllo CollectionViewController. Quindi ho appena cambiato il metodo per TableViewController per il CollectionView.NSFetchedResultsContollerDelegate for 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]; 
} 

Ma io non so come gestire la WillChangeContent (beginUpdates per TableView) e DidChangeContent (endUpdates per TableVie w) per un CollectionView.

Tutto funziona correttamente tranne quando si sposta un elemento da una sezione a un'altra sezione. Allora ottengo il seguente errore.

Di solito si tratta di un bug all'interno di un osservatore di NSManagedObjectContextObjectsDidChangeNotification. Aggiornamento non valido: numero non valido di articoli nella sezione 0 ....

Qualche idea come posso risolvere questo problema?

risposta

10

La combinazione di un controller di risultati recuperato con una vista di raccolta è un po 'complicata. Il problema è spiegato in

Se siete alla ricerca di come aggirare l'eccezione NSInternalInconsistencyException runtime con UICollectionView, ho un esempio su GitHub dettaglio come fare la fila aggiornamenti da NSFetchedResultsControllerDelegate.

Il problema è che il UITableView classe esistente utilizza beginUpdates e endUpdates presentare lotti per la visualizzazione della tabella. UICollectionView ha un nuovo metodo performBatchUpdates:, che accetta un parametro di blocco per aggiornare la vista di raccolta. È sexy, ma non funziona bene con il paradigma esistente per NSFetchedResultsController.

Fortunatamente, tale articolo prevede anche un esempio di implementazione:

Dal README:

Questo è un esempio di come utilizzare la nuova UICollectionView con NSFetchedResultsController. Il trucco è quello di accodare gli aggiornamenti effettuati tramite il NSFetchedResultsControllerDelegate fino a quando il controller termina gli aggiornamenti. UICollectionView non avere lo stesso beginUpdates e endUpdates che UITableView deve farlo lavorare facilmente con NSFetchedResultsController, in modo da fare la fila o li si ottiene eccezioni coerenza di runtime interni.

+0

Grazie, Martin. Ho provato questo senza la soluzione in anticipo - non ho visto l'aggiornamento per la soluzione alternativa. Ora con la soluzione del problema nella vista della raccolta finalmente funziona. A causa del fatto che ho intestazioni e piè di pagina questo è stato un ottimo aiuto. Spero tuttavia che questo errore verrà risolto una volta. – aquarius68

+0

@ aquarius68: Non è proprio un bug. Il problema è che i metodi di delega dell'RFC e i metodi di aggiornamento della vista di raccolta non si adattano perfettamente. Correggere ciò significherebbe modificare o estendere una delle API. - Ma sono contento che tu abbia funzionato. –

+0

Non ricevo più i messaggi di errore ma non funziona ancora completamente; Ad esempio, se l'utente aggiunge il primo elemento funziona, ma se l'utente aggiunge il secondo elemento, funziona solo se torno alla vista tabella che contiene oggetti correlati agli oggetti della vista di raccolta. – aquarius68

27

Ecco la mia implementazione con Swift. Prima inizializzare un array di NSBlockOperations:

var blockOperations: [NSBlockOperation] = [] 

Nel controllore cambierà, ri-init dell'array:

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

Nel metodo cambiamento dell'oggetto fatto:

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

DID cambio sezione metodo:

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

E, infine, nel dispositivo di controllo ha fatto ha cambiato metodo di contenuti:

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

io personalmente aggiunto un po 'di codice nel metodo deinit così, al fine di annullare le operazioni quando il ViewController sta per essere deallocata:

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

    blockOperations.removeAll(keepCapacity: false) 
} 
+0

Funziona magnificamente, "Plot"! Grazie! –

+0

Nessun tipo di modifica '.Move' per le sezioni? – pkamb

+0

@pkamb non c'è "spostamento" per le sezioni in 'UICollectionView'. – Fogmeister

1

Ecco un po 'di Swift che funziona con installsStandardGestureForInteractiveMovement di UICollectionViewController ed è un po essiccato e accende l'installsStandardGestureForInteractiveMovement in modo che tutti i percorsi di codice sono evidenti. È lo stesso schema generale del codice di 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

ho fatto @ soluzione Plot è proprio oggetto e convertita in 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) 
     }) 
    } 

} 

Usage:

fetchedResultsController.delegate = CollectionViewFetchedResultsControllerDelegate(collectionView)