我想在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];
}
但我不知道如何处理WillChangeContent
beginUpdates
TableView
}和DidChangeContent
endUpdates
TableVie
w)对于CollectionView
。
除非我将一个项目从一个部分移动到另一个部分,否则一切正常。然后我收到以下错误。
这通常是NSManagedObjectContextObjectsDidChangeNotification观察者中的错误。更新无效:第0部分中的项目数无效....
我知道如何解决这个问题?
答案 0 :(得分:29)
这是我使用Swift的实现。 首先初始化NSBlockOperations数组:
var blockOperations: [NSBlockOperation] = []
在控制器中将更改,重新启动数组:
func controllerWillChangeContent(controller: NSFetchedResultsController) {
blockOperations.removeAll(keepCapacity: false)
}
在改变对象方法中:
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!])
}
})
)
}
}
在更改部分方法中:
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)
}
答案 1 :(得分:19)
我做了 @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)
})
}
}
用法:
fetchedResultsController.delegate = CollectionViewFetchedResultsControllerDelegate(collectionView)
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)
})
}
答案 2 :(得分:12)
将获取的结果控制器与集合视图相结合有点棘手。
中解释了这个问题如果你正在寻找如何绕过
NSInternalInconsistencyException
运行时异常UICollectionView
,我在GitHub上有一个详细说明如何排队的例子 来自NSFetchedResultsControllerDelegate的更新。问题是现有的
UITableView
班级使用beginUpdates
和endUpdates
将批次提交到表格视图。UICollectionView
有一个新的performBatchUpdates:
方法,它接受一个块参数 更新集合视图。这很性感,但效果不好 使用现有的NSFetchedResultsController范例。
幸运的是,该文章还提供了一个示例实现:
来自自述文件:
这是如何使用新
UICollectionView
的示例NSFetchedResultsController
。诀窍是对所做的更新进行排队 通过NSFetchedResultsControllerDelegate
直到控制器 完成其更新。UICollectionView
不一样beginUpdates
和endUpdates
,UITableView
必须让它轻松工作 使用NSFetchedResultsController
,所以你必须排队或者你得到 内部一致性运行时异常。
答案 3 :(得分:4)
基于以上令人难以置信的答案,
考虑表中常见的Apple示例:
https://github.com/kubernetes/minikube/issues/1568
标题
“将数据更改传达到表视图” ...
所以
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()
}
。
(我刚刚删除了“ sections”材料,这是相同的。)
controllerWillChangeContent
中什么都不做?在@PhuahYeeKeat给出的宏伟答案中,controllerWillChangeContent
中的ops数组被清除了。我可能是错的,但是没有理由这样做。批处理更新周期可靠地将其清空。只需在controllerWillChangeContent
中什么都不做。
我担心如果新didChange
在处理performBatchUpdates
前一批{em} 时会发生什么情况。
我真的不知道performBatchUpdates
是本地副本还是什么副本-在这种情况下,应该在进行performBatchUpdates
之前删除全局副本?
IDK。
答案 4 :(得分:1)
这里有一些Swift可以与UICollectionViewController的installsStandardGestureForInteractiveMovement一起使用,并且有点干扰并打开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()
}
答案 5 :(得分:0)
2019年版情节的答案:
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)
}