iOS 9 – “尝试删除并重新加载相同的索引path”
这是一个错误:
CoreData:错误:严重的应用程序错误。 在调用-controllerDidChangeContent:期间,NSFetchedResultsController的委托捕获到exception。 尝试使用userInfo(null)删除并重新加载相同的索引path({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() }
在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(这仍然是testing版)中的错误,也在苹果开发者论坛讨论
- iOS 9 CoreData NSFetchedResultsController更新会导致UICollectionView / UITableView中的空白行
我可以从Xcode 7testing版3确认iOS 9模拟器的问题。我观察到,对于更新的托pipe对象, didChangeObject:
delegate方法被调用两次:一次使用NSFetchedResultsChangeUpdate
事件,然后使用indexPath == newIndexPath
事件(以及indexPath == newIndexPath
)。
添加一个显式的检查indexPath != newIndexPath
build议在上面的线程似乎解决了这个问题:
case .Move: if indexPath != newIndexPath { tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade) }
更新:在iOS 9.0或iOS 9.1(beta)SDK上构build时,所描述的问题仅在iOS 8上发生。
今天玩Xcode 7 beta 6(iOS 9.0 beta 5)后,我想到了一个可怕的解决方法,它似乎工作。
您不能使用reloadRowsAtIndexPaths
因为在某些情况下,它被称为太早,可能会导致不一致,而应该手动更新您的单元格。
我仍然认为最好的select是简单地调用reloadData
。
我相信你可以毫不费力地适应我的代码,我在这里有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
在Xcode 7 / iOS 9.0 NSFetchedResultsChangeMove
仍在发送,而不是“更新”。
作为一个简单的解决方法,只需禁用该情况下的animation:
- (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编译的构build ,在其他一些答案解决的indexPath==newIndexPath
问题之上, 发生了一些非常奇怪的事情 。
NSFetchedResultsChangeType
枚举有四个可能的值( 带值的注释是我的 ):
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) } }
..无效的调用将导致一个插入,你会得到如下错误:
无效的更新:部分0中的行数无效。更新(7)后,现有部分中包含的行数必须等于更新前(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
问题,这个会popup。 而且,上面的代码块被简化以说明序列; 例如,请不要使用它们。
学分 :这是最初由iCN7发现,来源: Apple开发者社区 – iOS 9 CoreData NSFetchedResultsController更新导致UICollectionView / UITableView中的空行
出于某种原因, .Update
调用.Update
然后调用controllerWillChangeContent:
之后的.Update
。
看起来像这样: BEGIN UPDATES – > UPDATE – > MOVE – > END UPDATES 。
只发生在iOS 8.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 } }
这真的很有效 。
其他的答案对我来说很接近,但是我收到NSFetchedResultsChangeType的“<invalid>(0x0)”。 我注意到它被解释为“插入”的变化。 所以下面的修复工作适合我:
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相同的path),这只是检查它是正确的“插入”types和跳过其他人。
问题发生是因为重新加载并删除了相同的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];
事实certificate,运作良好。
顺便说一句:CoreData对象的更新将产生一个删除和插入消息。要正确更新单元格内容,当indexPath
等于newIndexPath
(节和行都相等),我重新加载的单元格
[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