2013-12-12 64 views
16

我想在CollectionViewController中使用NSFetchedResultsControllerRelegate。 因此我只是改變了CollectionView的TableViewController方法。NSFetchedResultsContollerDelegate 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]; 
} 

但我不知道如何處理WillChangeContentbeginUpdatesTableView)和DidChangeContentendUpdatesTableVie w)的CollectionView

一切工作正常,除非我將一個項目從一個部分移動到另一個部分。然後我得到以下錯誤。

這通常是NSManagedObjectContextObjectsDidChangeNotification觀察者中的一個錯誤。無效的更新:第0部分中的項目數量無效。...

任何想法如何解決此問題?

回答

10

將獲取的結果控制器與集合視圖組合起來有點棘手。 的課題在

如果你正在尋找如何獲得 NSInternalInconsistencyException運行時異常與周圍 UICollectionView解釋,我在GitHub上的例子詳細說明如何排隊 來自NSFetchedResultsControllerDelegate的更新。

問題是,現有的UITableView類使用beginUpdatesendUpdates將批次提交到表視圖。 UICollectionView 有一個新的performBatchUpdates:方法,它採用塊參數 更新集合視圖。這很性感,但與現有的NSFetchedResultsController範式不兼容。

幸運的是,文章還提供了一個簡單的實現:

自述:

這是如何使用新的UICollectionView爲例與 NSFetchedResultsController。訣竅是排列 到NSFetchedResultsControllerDelegate的更新,直到控制器 完成的更新。 UICollectionView不具有相同的 beginUpdatesendUpdatesUITableView必須讓它工作容易 與NSFetchedResultsController,所以你要排隊他們或你 內部一致性運行時異常。

+0

謝謝,馬丁。我之前沒有采取這種解決方法嘗試這種方式 - 沒有看到該解決方案的更新。現在,解決集合視圖中的錯誤終於可以奏效了。由於我有頁眉和頁腳這是一個非常好的幫助。不過,我希望這個bug能夠解決一次。 – aquarius68

+0

@ aquarius68:這不是一個真正的bug。問題是FRC委託方法和集合視圖更新方法並不真正合在一起。修復這將意味着更改或擴展其中一個API。 - 但我很高興你的工作。 –

+0

我不再收到錯誤消息,但它尚未完全工作;即如果用戶添加它的第一個元素,但是如果用戶添加第二個元素,則只有當我返回包含與集合視圖的對象相關的對象的表視圖時才起作用。 – aquarius68

27

這是我用Swift實現的。 首先初始化NSBlockOperations的數組:

var blockOperations: [NSBlockOperation] = [] 

在控制器會發生變化,重新初始化所述陣列:

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

在DID改變對象的方法:

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更換部分方法:

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

最後,在沒有控制並改變內容的方法:

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

我個人加在DEINIT方法一些代碼爲好,以取消操作時的ViewController即將獲得釋放:

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

    blockOperations.removeAll(keepCapacity: false) 
} 
+0

這個美妙的作品,「劇情」!謝謝! –

+0

沒有'.Move'部分的更改類型? – pkamb

+0

@pkamb「UICollectionView」中的部分沒有「移動」。 – Fogmeister

1

這裏有一些Swift與UICollectionViewController的installsStandardGestureForInteractiveMovement一起工作,並且有點DRYed並開啓installsStandardGestureForInteractiveMovement,以便所有的代碼路徑是顯而易見的。它和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

我做@情節的解決方案自己的對象,並轉化爲斯威夫特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) 
     }) 
    } 

} 

用法:

fetchedResultsController.delegate = CollectionViewFetchedResultsControllerDelegate(collectionView)