错误:
CoreData: error:严重的应用错误。在调用-controllerDidChangeContent时,从NSFetchedResultsController的委托捕获了一个异常。尝试用userInfo (null)
删除和重新加载相同的索引路径({length = 2, path = 0 - 0})
这是我典型的NSFetchedResultsControllerDelegate
:
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
let indexSet = NSIndexSet(index: sectionIndex)
switch type {
case .Insert:
tableView.insertSections(indexSet, withRowAnimation: .Fade)
case .Delete:
tableView.deleteSections(indexSet, withRowAnimation: .Fade)
case .Update:
fallthrough
case .Move:
tableView.reloadSections(indexSet, withRowAnimation: .Fade)
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: NSManagedObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
if let newIndexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
case .Delete:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
case .Update:
if let indexPath = indexPath {
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}
case .Move:
if let indexPath = indexPath {
if let newIndexPath = newIndexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
}
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
in viewDidLoad()
:
private func setupOnceFetchedResultsController() {
if fetchedResultsController == nil {
let context = NSManagedObjectContext.MR_defaultContext()
let fetchReguest = NSFetchRequest(entityName: "DBOrder")
let dateDescriptor = NSSortDescriptor(key: "date", ascending: false)
fetchReguest.predicate = NSPredicate(format: "user.identifier = %@", DBAppSettings.currentUser!.identifier )
fetchReguest.sortDescriptors = [dateDescriptor]
fetchReguest.fetchLimit = 10
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchReguest, managedObjectContext: context, sectionNameKeyPath: "identifier", cacheName: nil)
fetchedResultsController.delegate = self
try! fetchedResultsController.performFetch()
}
}
这似乎是iOS 9(仍处于测试阶段)的一个bug, Apple开发者论坛也对此进行了讨论
- iOS 9 CoreData NSFetchedResultsController update导致UICollectionView/UITableView中的空白行
我可以从Xcode 7 beta 3确认iOS 9模拟器的问题。我观察到,对于更新的管理对象,didChangeObject:
委托方法被调用两次:一次使用NSFetchedResultsChangeUpdate
事件,然后再次使用NSFetchedResultsChangeMove
事件(和indexPath == newIndexPath
)。
为indexPath != newIndexPath
添加显式检查如上所建议的线程似乎解决了问题:
case .Move:
if indexPath != newIndexPath {
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
}
更新:所描述的问题仅在iOS 8上基于iOS 9.0或iOS 9.1(测试版)SDK构建时发生。
我今天在玩了Xcode 7 beta 6 (iOS 9.0 beta 5)之后想出了一些可怕的解决方案,看起来它是有效的。
你不能使用reloadRowsAtIndexPaths
,因为在某些情况下,它被调用得太早,可能会导致不一致,相反,你应该手动更新你的单元格。
我仍然认为最好的选择是简单地调用reloadData
。
我相信你可以毫不费力地把我的代码改编成swift,我这里有一个objective-c项目。
@property NSMutableIndexSet *deletedSections, *insertedSections;
// ...
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
self.deletedSections = [[NSMutableIndexSet alloc] init];
self.insertedSections = [[NSMutableIndexSet alloc] init];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:sectionIndex];
switch(type) {
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
[self.deletedSections addIndexes:indexSet];
break;
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
[self.insertedSections addIndexes:indexSet];
break;
default:
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:@[ newIndexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeMove:
// iOS 9.0b5 sends the same index path twice instead of delete
if(![indexPath isEqual:newIndexPath]) {
[self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView insertRowsAtIndexPaths:@[ newIndexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
}
else if([self.insertedSections containsIndex:indexPath.section]) {
// iOS 9.0b5 bug: Moving first item from section 0 (which becomes section 1 later) to section 0
// Really the only way is to delete and insert the same index path...
[self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
}
else if([self.deletedSections containsIndex:indexPath.section]) {
// iOS 9.0b5 bug: same index path reported after section was removed
// we can ignore item deletion here because the whole section was removed anyway
[self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
}
break;
case NSFetchedResultsChangeUpdate:
// On iOS 9.0b5 NSFetchedResultsController may not even contain such indexPath anymore
// when removing last item from section.
if(![self.deletedSections containsIndex:indexPath.section] && ![self.insertedSections containsIndex:indexPath.section]) {
// iOS 9.0b5 sends update before delete therefore we cannot use reload
// this will never work correctly but at least no crash.
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
[self _configureCell:cell forRowAtIndexPath:indexPath];
}
break;
}
}
Xcode 7/iOS 9.0 only
在Xcode 7/iOS 9.0中NSFetchedResultsChangeMove
仍然被发送而不是"update"
作为一个简单的解决方案,只需禁用动画:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableViewRowAnimation animation = UITableViewRowAnimationAutomatic;
switch(type) {
case NSFetchedResultsChangeMove:
// @MARK: iOS 9.0 bug. Move sent instead of update. indexPath = newIndexPath.
if([indexPath isEqual:newIndexPath]) {
animation = UITableViewRowAnimationNone;
}
[self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:animation];
[self.tableView insertRowsAtIndexPaths:@[ newIndexPath ] withRowAnimation:animation];
break;
// ...
}
}
关于在iOS8上发生的这种情况,在针对iOS9编译的构建之上,indexPath==newIndexPath
问题由其他一些答案解决,其他一些事情发生了,这是非常奇怪的 。
NSFetchedResultsChangeType
enum有四个可能的值(注释的值是mine):
public enum NSFetchedResultsChangeType : UInt {
case Insert // 1
case Delete // 2
case Move // 3
case Update // 4
}
. .然而,controller:didChangeObject:atIndexPath:forChangeType
函数有时会以无效值0x0
调用。
Swift似乎默认为第一个switch
大小写,所以如果你有以下结构:
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert: tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
case .Delete: tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
case .Update: tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.None)
case .Move: tableView.moveRowAtIndexPath(ip, toIndexPath: nip)
}
}
. .无效调用将导致Insert,您将得到如下错误:
无效的更新:无效的行数在第0节。的数量更新(7)后的现有节中包含的行必须是对象之前的该节中包含的行数Update(7),加上或减去插入或删除的行数(1插入,0删除)
简单地交换案例,使第一个案例是一个相当无害的更新修复这个问题:
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Update: tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.None)
case .Insert: tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
case .Delete: tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
case .Move: tableView.moveRowAtIndexPath(ip, toIndexPath: nip)
}
}
另一个选项是检查type.rawValue
是否有无效值。
注释:虽然这解决了与OP发布的错误消息略有不同的错误消息,但问题是相关的;有可能一旦你修复了indexPath==newIndexPath
的问题,这个问题就会弹出。此外,对上述代码块进行简化以说明所述序列;例如,缺少适当的guard
块-请不要原样使用它们。
Credits:这最初是由iCN7发现的,来源:Apple Developer Forums - iOS 9 CoreData NSFetchedResultsController update导致UICollectionView/UITableView
出于某种原因NSFetchedResultsController
调用.Update
,然后.Move
在controllerWillChangeContent:
被调用后。
只是看起来像这样:开始更新 -> 更新 -> 移动 -> 结束更新。
只在ios8 .x下发生
在一次更新会话期间,重新加载并删除导致崩溃的相同单元格。
最简单的修复:
以下部分代码:
case .Update:
if let indexPath = indexPath {
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
替换为:
case .Update:
if let indexPath = indexPath {
// 1. get your cell
// 2. get object related to your cell from fetched results controller
// 3. update your cell using that object
//EXAMPLE:
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? WLTableViewCell { //1
let wishlist = fetchedResultsController.objectAtIndexPath(indexPath) as! WLWishlist //2
cell.configureCellWithWishlist(wishlist) //3
}
}
。
其他答案对我来说很接近,但我收到的是"<无效> (0x0)"作为NSFetchedResultsChangeType。我注意到它被解释为"插入"更改。因此,以下修复对我有效:无效>
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
// iOS 9 / Swift 2.0 BUG with running 8.4
if indexPath == nil {
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
}
(etc...)
}
由于每次"插入"只返回newIndexPath而没有indexPath(并且这个奇怪的额外插入委托调用返回时为newIndexPath和indexPath列出了相同的路径),因此这只是检查它是正确的"插入"类型,并跳过其他类型。
问题的发生是因为重新加载和删除了相同的indexPath(这是apple产生的一个错误),所以我改变了处理NSFetchedResultsChangeUpdate
消息的方式。
代替:
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
我手动更新了单元格的内容:
MyChatCell *cell = (MyChatCell *)[self.tableView cellForRowAtIndexPath:indexPath];
CoreDataObject *cdo = [[self fetchedResultsController] objectAtIndexPath:indexPath];
// update the cell with the content: cdo
[cell updateContent:cdo];
结果很好。
顺便说一句:CoreData对象的更新将产生一个删除和一个插入消息。为了正确更新单元格内容,当indexPath
等于newIndexPath
(section和row都相等)时,我通过
重新加载单元格。[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
if (![self isViewLoaded]) return;
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:{
MyChatCell *cell = (MyChatCell *)[self.tableView cellForRowAtIndexPath:indexPath];
CoreDataObject *cdo = [[self fetchedResultsController] objectAtIndexPath:indexPath];
// update the cell with the content: cdo
[cell updateContent:cdo];
}
break;
case NSFetchedResultsChangeMove:
if (indexPath.row!=newIndexPath.row || indexPath.section!=newIndexPath.section){
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
}else{
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
}
}
我把上面的示例代码放在要点上:https://gist.github.com/dreamolight/157266c615d4a226e772