Swift 用于CollectionView的NSFetchedResultsContollerDelegate

Swift 用于CollectionView的NSFetchedResultsContollerDelegate,swift,core-data,delegates,collectionview,Swift,Core Data,Delegates,Collectionview,我想在CollectionViewController中使用NSFetchedResultsControllerRelegate。 因此,我刚刚更改了CollectionView的TableViewController的方法 (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atI

我想在CollectionViewController中使用NSFetchedResultsControllerRelegate。 因此,我刚刚更改了CollectionView的TableViewController的方法

(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];
}
但我不知道如何处理CollectionView的TableView的WillChangeContent开始更新和DidChangeContent结束更新

除了我将一个项目从一个部分移动到另一个部分外,其他一切都正常。然后我得到以下错误

这通常是NSManagedObjectContextObjectsIDChangeNotification的观察者中的错误。无效更新:节0中的项目数无效


知道如何解决这个问题吗?

将获取的结果控制器与集合视图相结合有点棘手。 这一问题在本文中作了解释

如果你正在寻找如何绕过 NSInternalInconsistenceException运行时异常 UICollectionView,我在GitHub上有一个例子,详细说明了如何排队 来自NSFetchedResultsControllerDelegate的更新

问题在于现有的UITableView类使用BeginUpdate 和endUpdates将批提交到表视图。UICollectionView 有一个新的performbatchudates:method,它接受一个块参数 更新集合视图。这很性感,但效果不好 使用NSFetchedResultsController的现有范例

幸运的是,本文还提供了一个示例实现:

自述文件:

这是一个如何将新UICollectionView与一起使用的示例 NSFetchedResultsController。诀窍是将所做的更新排队 通过NSFetchedResultsControllerDelegate,直到控制器 完成其更新。UICollectionView没有相同的 开始更新和结束更新,UITableView必须让它轻松工作 使用NSFetchedResultsController,您必须对它们进行排队,否则将 内部一致性运行时异常


这是我用Swift实现的。 首先初始化NSBlockOperations数组:

var blockOperations: [NSBlockOperation] = []
在控制器将更改时,重新初始化阵列:

func controllerWillChangeContent(controller: NSFetchedResultsController) {
    blockOperations.removeAll(keepCapacity: false)
}
在did change对象方法中:

    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))
                }
            })
        )
    }
}
最后,在did控制器中,确实更改了内容方法:

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

这里有一点Swift,它与UICollectionViewController的installsStandardGestureForInteractiveMovement配合使用,是一个有点干涸的组件,它会打开installsStandardGestureForInteractiveMovement,以便所有代码路径都很明显。这与绘图代码的总体模式相同

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()
}
我将@Plot的解决方案作为自己的对象,并将其转换为Swift 2

用法:

fetchedResultsController.delegate = CollectionViewFetchedResultsControllerDelegate(collectionView)
Swift 4版本


2019年版Plot的答案:

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?) {
    if type == NSFetchedResultsChangeType.insert {
        print("Insert Object: \(newIndexPath)")

        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.insertItems(at: [newIndexPath!])
                }
            })
        )
    }
    else if type == NSFetchedResultsChangeType.update {
        print("Update Object: \(indexPath)")
        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.reloadItems(at: [indexPath!])
                }
            })
        )
    }
    else if type == NSFetchedResultsChangeType.move {
        print("Move Object: \(indexPath)")

        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.moveItem(at: indexPath!, to: newIndexPath!)
                }
            })
        )
    }
    else if type == NSFetchedResultsChangeType.delete {
        print("Delete Object: \(indexPath)")

        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.deleteItems(at: [indexPath!])
                }
            })
        )
    }
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    if type == NSFetchedResultsChangeType.insert {
        print("Insert Section: \(sectionIndex)")

        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.insertSections(IndexSet(integer: sectionIndex))
                }
            })
        )
    }
    else if type == NSFetchedResultsChangeType.update {
        print("Update Section: \(sectionIndex)")
        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.reloadSections(IndexSet(integer: sectionIndex))
                }
            })
        )
    }
    else if type == NSFetchedResultsChangeType.delete {
        print("Delete Section: \(sectionIndex)")

        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.deleteSections(IndexSet(integer: sectionIndex))
                }
            })
        )
    }
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    collectionView!.performBatchUpdates({ () -> Void in
        for operation: BlockOperation in self.blockOperations {
            operation.start()
        }
    }, completion: { (finished) -> Void in
        self.blockOperations.removeAll(keepingCapacity: false)
    })
}

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

    blockOperations.removeAll(keepingCapacity: false)
}
2020年版本:

基于上述令人难以置信的答案,并与熟悉的苹果表格示例相匹配:

以熟悉的Apple表视图示例为例:

在标题处

正在将数据更改传递到表视图

所以

下面是类似的模式,用于复制和粘贴集合视图,使用当前语法等

var ops: [BlockOperation] = []

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    switch type {
        case .insert:
            ops.append(BlockOperation(block: { [weak self] in
                self?.insertItems(at: [newIndexPath!])
            }))
        case .delete:
            ops.append(BlockOperation(block: { [weak self] in
                self?.deleteItems(at: [indexPath!])
            }))
        case .update:
            ops.append(BlockOperation(block: { [weak self] in
                self?.reloadItems(at: [indexPath!])
            }))
        case .move:
            ops.append(BlockOperation(block: { [weak self] in
                self?.moveItem(at: indexPath!, to: newIndexPath!)
            }))
        @unknown default:
            break
    }
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    performBatchUpdates({ () -> Void in
        for op: BlockOperation in self.ops { op.start() }
    }, completion: { (finished) -> Void in self.ops.removeAll() })
}

deinit {
    for o in ops { o.cancel() }
    ops.removeAll()
}

我刚刚遗漏了相同的章节材料

在controllerWillChangeContent中不执行任何操作? 在@PhuahYeeKeat的精彩回答中,在controllerWillChangeContent中,ops阵列被清除。我可能错了,但没有理由这么做;它通过批更新周期可靠地清空。只需在controllerWillChangeContent中不执行任何操作

有比赛吗? 我担心在PerformBatchUpdate处理前一批时,如果出现新的didChange,会发生什么情况

我真的不知道performBatchUpdates是否制作了本地副本或什么-在这种情况下,在执行performBatchUpdates之前应该删除全局副本


IDK.

谢谢,马丁。我在没有解决方案的情况下尝试了这个方法-没有看到解决方案的更新。现在,通过在集合视图中解决该错误,它终于可以工作了。由于我有页眉和页脚,这是一个非常好的帮助。不过我还是希望这个错误能被解决一次。@aquarius68:它不是一个真正的错误。问题是FRC委托方法和集合视图更新方法实际上并不适合。修复此问题意味着更改或扩展其中一个API。-但我很高兴你让它工作了。我不再收到任何错误消息,但它还没有完全工作;i、 e.如果用户添加了第一个元素,那么它可以工作,但是如果用户添加了第二个元素,那么只有当我返回到包含与集合视图的对象相关的对象的表视图时,它才能工作。@aquarius68:这似乎是一个单独的问题,因此我建议开始一个新问题。然后,更多的人会读到它,或许可以提供帮助。回购协议现在是空的。你可以寄封信吗
ome代码?如果可能,斯威夫特否。节的移动更改类型?@pkamb UICollectionView中没有节的移动。这是“给我”错误扩展不能包含私有变量blockOperations上的存储属性:[BlockOperation]=[]我不知道为什么=\n我没有尝试此操作,但我想知道。这是否有效:fetchedResultsController.delegate=CollectionViewFetchedResultsController DelegateCollectionView?我认为Delegates很弱。这在iOS 13上非常有效,而其他在线解决方案(例如,一个流行的Google结果)在重要操作(如在新设备上进行iCloud同步)期间导致我的UI冻结。我在这里没有看到这些冻结,大概是因为如何处理块操作我不是并发专家,但如果我发现任何问题,我会报告,因为除此之外,FetchedResultsController的CollectionViews出人意料地缺少良好的iOS 13更新资源。谢谢你@Fattie@cdf1982-谢谢,是的,你完全正确。开发这个解决方案花了我们一大笔钱。。。。。。确实是为了准确地解决你概述的问题。是的,这是一个很好的例子,说明你经常在网上看到的示例代码不幸是完全错误的。这是苹果公司一个非常奇怪的角落,他们没有为他们的首要产品CoreData构建一个用于收集视图的解决方案。他们给了蹩脚的旧表视图一个解决方案,但不是集合视图!这只是苹果公司奇怪的事情之一!希望他们能很快做到。我试过这个代码。我遇到异常:尝试将项0插入节0,但在collectionView上更新后,节0中只有0项。didChangeContent函数中的PerformButUpdate只能继续进行标准调试,到处添加打印语句以查找问题编辑器:做得好,我只是简单地添加了缺少的Swift标记,它将修复整个页面
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?) {
    if type == NSFetchedResultsChangeType.insert {
        print("Insert Object: \(newIndexPath)")

        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.insertItems(at: [newIndexPath!])
                }
            })
        )
    }
    else if type == NSFetchedResultsChangeType.update {
        print("Update Object: \(indexPath)")
        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.reloadItems(at: [indexPath!])
                }
            })
        )
    }
    else if type == NSFetchedResultsChangeType.move {
        print("Move Object: \(indexPath)")

        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.moveItem(at: indexPath!, to: newIndexPath!)
                }
            })
        )
    }
    else if type == NSFetchedResultsChangeType.delete {
        print("Delete Object: \(indexPath)")

        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.deleteItems(at: [indexPath!])
                }
            })
        )
    }
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    if type == NSFetchedResultsChangeType.insert {
        print("Insert Section: \(sectionIndex)")

        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.insertSections(IndexSet(integer: sectionIndex))
                }
            })
        )
    }
    else if type == NSFetchedResultsChangeType.update {
        print("Update Section: \(sectionIndex)")
        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.reloadSections(IndexSet(integer: sectionIndex))
                }
            })
        )
    }
    else if type == NSFetchedResultsChangeType.delete {
        print("Delete Section: \(sectionIndex)")

        blockOperations.append(
            BlockOperation(block: { [weak self] in
                if let this = self {
                    this.collectionView!.deleteSections(IndexSet(integer: sectionIndex))
                }
            })
        )
    }
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    collectionView!.performBatchUpdates({ () -> Void in
        for operation: BlockOperation in self.blockOperations {
            operation.start()
        }
    }, completion: { (finished) -> Void in
        self.blockOperations.removeAll(keepingCapacity: false)
    })
}

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

    blockOperations.removeAll(keepingCapacity: false)
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    switch type {
    case .insert:
        insertRows(at: [newIndexPath!], with: .fade)
    case .delete:
        deleteRows(at: [indexPath!], with: .fade)
    case .update:
        reloadRows(at: [indexPath!], with: .fade)
    case .move:
        moveRow(at: indexPath!, to: newIndexPath!)
    }
}
var ops: [BlockOperation] = []

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    switch type {
        case .insert:
            ops.append(BlockOperation(block: { [weak self] in
                self?.insertItems(at: [newIndexPath!])
            }))
        case .delete:
            ops.append(BlockOperation(block: { [weak self] in
                self?.deleteItems(at: [indexPath!])
            }))
        case .update:
            ops.append(BlockOperation(block: { [weak self] in
                self?.reloadItems(at: [indexPath!])
            }))
        case .move:
            ops.append(BlockOperation(block: { [weak self] in
                self?.moveItem(at: indexPath!, to: newIndexPath!)
            }))
        @unknown default:
            break
    }
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    performBatchUpdates({ () -> Void in
        for op: BlockOperation in self.ops { op.start() }
    }, completion: { (finished) -> Void in self.ops.removeAll() })
}

deinit {
    for o in ops { o.cancel() }
    ops.removeAll()
}