NSFetchResultsController -controllerDidChangeContent: - 尝试使用 userInfo 为单元格创建两个动画
Posted
技术标签:
【中文标题】NSFetchResultsController -controllerDidChangeContent: - 尝试使用 userInfo 为单元格创建两个动画【英文标题】:NSFetchResultsController -controllerDidChangeContent: - Attempt to create two animations for cell with userInfo 【发布时间】:2014-03-30 14:01:59 【问题描述】:当我在 TabbarController 的两个选项卡之间切换到 Viewcontrollers 时,这两个选项卡都包含 NSFetchResultsController。在我的一个视图控制器中更新内容时出现以下核心数据错误:
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Attempt to create two animations for cell with userInfo (null)
如果我尝试更新数据,我的 tableview 会变成白色并崩溃。
我的 -controllerDidChangeContent: 看起来像这样:
Viewcontroller(我更新数据的视图控制器)。
-(void)controllerWillChangeContent:(NSFetchedResultsController*)controller
[self.productDetailTableView beginUpdates];
-(void)controllerDidChangeContent:(NSFetchedResultsController*)controller
[self.productDetailTableView endUpdates];
-(void)controller:(NSFetchedResultsController*)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
UITableView *tableView = self.productDetailTableView;
switch (type)
case NSFetchedResultsChangeInsert:
[tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
-(void)controller:(NSFetchedResultsController*)controller didChangeObject: (id)anObject atIndexPath:(NSIndexPath*)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath*)newIndexPath
UITableView *tableView = self.productDetailTableView;
NSIndexPath *tvIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:(indexPath.section + 1)];
NSIndexPath *tvNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:(newIndexPath.section + 1)];
indexPath = tvIndexPath;
newIndexPath = tvNewIndexPath;
switch (type)
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray
arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
[tableView insertRowsAtIndexPaths:[NSArray
arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
Viewcontroller(这个Viewcontroller,TableView变成白色并且出现错误的地方)。
-(void)controllerWillChangeContent:(NSFetchedResultsController *)controller
[self.cartTableView beginUpdates];
-(void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
UITableView *tableView = self.cartTableView;
switch(type)
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:(ProductCell *)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray
arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
[tableView insertRowsAtIndexPaths:[NSArray
arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
-(void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
UITableView *tableView = self.cartTableView;
switch(type)
case NSFetchedResultsChangeInsert:
[tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller
[self.cartTableView endUpdates];
如何解决此问题并让“尝试使用 userInfo 为单元格创建两个动画”消失,这是什么原因?
【问题讨论】:
两个视图控制器是否使用相同的 NSFetchedResultsController 对象? 没有 2 个单独的 NSFetchedResultsControllers。但同样的 NSMangedObjectContext。 你是不是同时更新了两个视图控制器? 是的,我更新了一些绑定到我的第一个视图控制器的对象和一些在我的第二个视图控制器中。 您能否从您的第一个视图控制器发布代码,您可以在其中对托管对象上下文进行更改?作为指导,如果您执行以下操作,通常会出现此错误: [self.itemList removeObjectAtIndex:0]; [self.tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:0 inSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]];然后 NSFetchedResultsController 尝试做另一个表格动画。 【参考方案1】:这不是核心数据的问题,和核心数据一点关系都没有。这是一个表格视图问题。 CoreData 正在调用您的委托方法,其中 UIKit 正在抛出异常,因为您尝试为同一个单元格设置两次动画。
为所有异常设置符号断点并尝试复制此行为。请注意,当 CoreData 解决合并冲突时,它会抛出完全正常的异常,但调试器将停止这些异常。继续前进,直到您的 tableview 异常。这将缩小哪个表视图是问题的范围。
查看驱动单元格动画的数据。从您的代码看来,您似乎是在制作动画之前修改了索引路径。这可能是问题的一部分。想象一下,如果你有 NSFetchedResultsChangeMove
和 tvIndexPath
等于 tvNewIndexPath
。这将产生您所描述的内容。
CoreData 绝对不是这里的问题,它是你的 tableview 动画。 CoreData 只是信使。
【讨论】:
正确。我目前正在处理一种情况,我们有一个按上次打开日期排序的文档列表。当您打开文档时,核心数据会通知我们该项目已更新(上次打开日期已更改)并已移动(排序位置已更改)。只需将更新传递给 tableview 就会触发此断言,因为两个更新都涉及同一个单元格。不确定解决方案是什么...【参考方案2】:看点:
a) 你应该确保 NSFetchResultsController 的委托指向正确的视图控制器;你可能设置错了,两个 FRC 都指向同一个视图控制器,并导致双重动画
b) 确保视图控制器的 controllerWillChangeContent 通过设置断点被调用一次(或两次)
c) 请注意您的didChangeObject
代码对于两个视图控制器有何不同?确保您知道 indexPaths 设置正确
如果没有任何效果: - 关于如何设置 FRC 的邮政编码 - 发布关于如何将对象插入 NSManagedObjectContext 的代码
【讨论】:
【参考方案3】:我刚刚在我公司的代码中解决了这个问题。我必须编写一个类来清理获取结果控制器的输出,以便在表格视图中使用。
NSFetchResultsController
API 与 UITableViewController
API 匹配得非常好,看起来很明显它们是如何一起工作的,但是 这是一个陷阱! 表格视图无法处理所有获取结果控制器可以生成的更新组合。
在我们的例子中,我们显示了一个按上次打开日期排序的文档列表。当您打开一个文档时,它的dateLastOpened
字段会发生变化(触发Update
通知)并且它在排序列表中的顺序会发生变化(触发Move
通知)。如果您尝试在同一个 tableView 更新中应用这两个更改,您将触发臭名昭著的断言。
所以我的过滤器类会监听NSFetchResultsController
委托回调并记录索引路径。更新完成后,它将Move
事件转换为Delete
加上Add
,并丢弃任何触及已移动或已删除索引路径的Update
事件。生成的更改集作为批处理应用到UITableView
。
【讨论】:
【参考方案4】:由于您使用的是同一个 NSManagedObject,因此当数据更改时,可能会在两个控制器上调用 didChangeObject。
因此,如果您在第一个视图控制器上并添加一个条目,则会调用此行:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
当这个动画发生时,第二个控制器上的这条线也会被调用:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationNone];
我认为这就是您收到错误的原因。您应该检查选择的选项卡,并且只在当前选择的控制器上调用beginUpdates
、endUpdates
、insertRowsAtIndexPath
和deleteRowsAtIndexPath
。
您可以使用:tabBarController.selectedIndex
获取当前选择的标签
【讨论】:
以上是关于NSFetchResultsController -controllerDidChangeContent: - 尝试使用 userInfo 为单元格创建两个动画的主要内容,如果未能解决你的问题,请参考以下文章
使用 NSFetchResultsController 时 UICollectionView 滚动不流畅
NSFetchResultsController,将新数据填充到表格视图中
NSFetchResultsController 委托没有被调用
NSFetchResultsController 委托没有被调用
NSFetchResultsController -controllerDidChangeContent: - 尝试使用 userInfo 为单元格创建两个动画