用于CollectionView的NSFetchedResultsContollerDelegate

aqu*_*s68 24 delegates core-data collectionview

我想在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];
}
Run Code Online (Sandbox Code Playgroud)

但我不知道如何处理WillChangeContent(beginUpdatesfor TableView)和DidChangeContent(endUpdatesfor TableView)a CollectionView.

一切正常,除非我将一个项目从一个部分移动到另一个部分.然后我收到以下错误.

这通常是NSManagedObjectContextObjectsDidChangeNotification的观察者中的错误.更新无效:第0部分中的项目数无效....

知道如何解决这个问题?

Plo*_*lot 30

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

var blockOperations: [NSBlockOperation] = []
Run Code Online (Sandbox Code Playgroud)

在控制器中将更改,重新初始化数组:

func controllerWillChangeContent(controller: NSFetchedResultsController) {
    blockOperations.removeAll(keepCapacity: false)
}
Run Code Online (Sandbox Code Playgroud)

在做了改变对象的方法:

    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!])
                }
            })
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

在更改部分方法中:

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))
                }
            })
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,在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)
    })
}
Run Code Online (Sandbox Code Playgroud)

我个人也在deinit方法中添加了一些代码,以便在ViewController即将解除分配时取消操作:

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

    blockOperations.removeAll(keepCapacity: false)
}
Run Code Online (Sandbox Code Playgroud)


Ada*_*ite 20

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

}
Run Code Online (Sandbox Code Playgroud)

用法:

fetchedResultsController.delegate = CollectionViewFetchedResultsControllerDelegate(collectionView)
Run Code Online (Sandbox Code Playgroud)

Swift 4版

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 { $0.start() }
    }, completion: { finished in
        self.blockOperations.removeAll(keepingCapacity: false)
    })
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*n R 12

将获取的结果控制器与集合视图相结合有点棘手.问题在于解释

如果您正在寻找如何解决 NSInternalInconsistencyException运行时异常 UICollectionView,我在GitHub上有一个示例,详细说明如何从NSFetchedResultsControllerDelegate排队更新.

问题是,现有的UITableView类使用beginUpdates ,并endUpdates提交批次表视图.UICollectionView 有一个新performBatchUpdates:方法,它采用块参数来更新集合视图.这很性感,但它与NSFetchedResultsController的现有范例不兼容.

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

来自README:

这是如何使用新的例子UICollectionViewNSFetchedResultsController.诀窍是对通过NSFetchedResultsControllerDelegate控制器 完成更新所做的更新进行排队.UICollectionView没有相同的 beginUpdates,并endUpdates认为UITableView必须让它轻松地与工作NSFetchedResultsController,所以你必须排队他们或你的内部一致性运行时异常.

  • 回购现在是空的。你可以发布一些代码吗?如果可能,斯威夫特?:) (2认同)

Fat*_*tie 6

2020 年的版本:

基于上面令人难以置信的答案,并且与熟悉的 Apple 表格示例相匹配

考虑一下我们熟悉的 Apple 表视图示例:

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/nsfetchedresultscontroller.html#//apple_ref/doc/uid/TP40001075-CH8-SW1

在标题

“将数据更改传达到表视图”...

所以,

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!)
    }
}
Run Code Online (Sandbox Code Playgroud)

.

这是使用当前语法等复制和粘贴集合视图的“类似模式”。

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()
}
Run Code Online (Sandbox Code Playgroud)

.

(我刚刚省略了“部分”材料,这是相同的。)

什么都不做controllerWillChangeContent

在@PhuahYeeKeat 的精彩回答中controllerWillChangeContent, ops 数组被清除。我可能是错的,但没有理由这样做;它被批量更新周期可靠地清空。简单地什么都不做controllerWillChangeContent.

有比赛吗?

我有一个会发生什么,如果关注一个新的didChange到来,而performBatchUpdates被处理前一批次。

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

身份证。

  • 这在 iOS 13 上效果很好,而在线找到的其他解决方案(即 [this gist](https://gist.github.com/Sorix/987af88f82c95ff8c30b51b6a5620657),这是一个流行的 Google 结果)导致我的 UI 在重大操作期间冻结,例如作为新设备上的 iCloud 同步。我在这里没有看到这些冻结,大概是因为 BlockOperations 的处理方式(我不是并发专家,但如果发现任何问题,我会报告问题,因为带有 FetchedResultsController 的 CollectionViews 令人惊讶地缺乏良好的 iOS 13 更新资源,除了这个答案)。谢谢@Fattie! (2认同)
  • @cdf1982 - 谢谢,是的,你完全正确。我们花费了大量的时间来开发这个解决方案……确实是为了准确解决您概述的问题。是的,这是一个很好的例子,不幸的是“您经常在网上看到的示例代码”是完全错误的。Apple 的一个真正奇怪的地方是,他们没有为他们的首要产品 CoreData 构建集合视图的解决方案。他们为蹩脚的旧表视图提供了解决方案,但不是集合视图!这只是苹果公司的“奇怪的事情之一”!希望他们很快就能做到。 (2认同)
  • 我尝试了这段代码。我在 `didChangeContent` 函数内的 `collectionView.performBatchUpdates` 上收到异常:“尝试将项目 0 插入第 0 节,但更新后第 0 节中只有 0 个项目” (2认同)