2015-07-13 48 views
41

这是一个错误:的iOS 9 - “尝试删除并重新加载相同的索引路径”

CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete and reload the same index path ({length = 2, path = 0 - 0}) with userInfo (null)

这是我的典型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() 
    } 
} 

回答

8

出于某种原因NSFetchedResultsController调用之后.Move.UpdatecontrollerWillChangeContent:被调用后:这是最初由iCN7,源发现。

只要它看起来是这样的:BEGIN最新通报 - >更新 - >MOVE - >末尾时更新

Happens only under iOS 8.x

在更新的一个会议上在同一小区被重载并删除什么导致崩溃。

最简单的EVER FIX:

下面的代码部分:

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 
     } 
    } 

真正起作用

26

这似乎是iOS 9中的一个bug(它仍然是测试版),并且在Apple Developer Forum

我可以从Xcode的7测试3. 我观察到,对于一个更新的管理对象,所述didChangeObject:委托方法被调用两次确认了iOS 9模拟器问题:一旦与NSFetchedResultsChangeUpdate事件,然后再与NSFetchedResultsChangeMove事件(和indexPath == newIndexPath)进行比较。

添加对indexPath != newIndexPath 显式检验如上述螺纹建议似乎解决该问题:

 case .Move: 
      if indexPath != newIndexPath { 
       tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) 
       tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade) 
     } 
+1

它解决了我的问题。但是当我添加一些行时,还有另一个问题:'尝试删除第12行中的第0行,但在userInfo(null)''更新之前只有10个部分'。你认为这也是一个错误? –

+0

@BartłomiejSemańczyk:这可能是您的代码中的错误或错误。这个问题还没有发生在我身上,所以我不能说。你有没有检查iOS 8模拟器? –

+0

不,它在工作之前,在这里iOS 8.3和Xcode7Beta3不起作用。更重要的是,我不试图删除任何东西,只是添加一些信息... –

20

更新:针对的iOS 9.0或iOS 9.1构建当仅在iOS 8发生所描述的问题(测试版SDK)。

我在使用Xcode 7 beta 6(iOS 9.0 beta 5)之后,今天想出了一些可怕的解决方法,它似乎可行。

您不能使用reloadRowsAtIndexPaths,因为在某些情况下,它被称为太早,可能会导致不一致,而应该手动更新您的单元格。

我仍然认为最好的选择是简单地拨打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/9.0的iOS仅

在Xcode中7/9.0的iOS NSFetchedResultsChangeMove仍被发送,而不是 “更新”。

作为一种简单的解决方法,只是禁用动画该案例:

- (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; 

     // ... 
    } 
} 
+0

这个答案为什么没有得到足够的重视?该解决方法准确描述了问题的表现。 +1 –

+0

这已通过Xcode 7修复GM – Andy

+2

我们仍然可以通过XCode 7 GM再现此错误 –

2

其他的答案很接近我,但我收到了“<无效>(为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列出的相同路径回来),这只是检查它是正确的“插入”,并跳过其他人。

0

问题发生是因为重新加载并删除了相同的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对象的更新会产生一个删除和插入message.To正确更新单元格内容,当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

16

对于这种情况发生在iOS8上,以建立对编译iOS9,通过一些其他的答案解决的问题indexPath==newIndexPath顶部,别的事情发生,这是非常奇怪的

NSFetchedResultsChangeType枚举具有四个可能的值(评论与值是矿):

public enum NSFetchedResultsChangeType : UInt { 
    case Insert // 1 
    case Delete // 2 
    case Move // 3 
    case Update // 4 
} 

..然而,controller:didChangeObject:atIndexPath:forChangeType功能有时具有无效值0x0调用。

斯威夫特似乎在这一点上默认为第一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) 
     } 
    } 

..无效的调用将导致插入,你会得到这样的错误以下:

Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (7) must be equal to the number of rows contained in that section before the update (7), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted)

简单地交换的情况下,使第一种情况是一个相当无害的更新解决了这个问题:

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发布的错误信息,问题是相关的;有一点可能,只要你修复了这个问题,就会弹出这个问题。另外,上述代码块被简化以说明序列;例如,相应的guard块会丢失,请不要按原样使用它们。

现金Apple Developer Forums — iOS 9 CoreData NSFetchedResultsController update causes blank rows in UICollectionView/UITableView

+1

我刚刚实现了'indexPath == newIndexPath'修复。由于我在这里使用Objective-C,我没有遇到这个_problem_,但是我仍然看到一个NSFetchedResultsChangeType ''值为'0x0'' switch'没有匹配'case'因此没有问题,但是这仍然是需要注意的事情 –

+3

@AdamS我感到震惊,Swift执行一个'case',表示_doesn't_不匹配提供'switch'的值 – magma

+0

同意,行为是关于的,编译器必须强制switch/case是详尽的 - 如果它不是(并且不能在编译时被捕获,如下),这应该是一个运行时错误,或者至少它不应该执行任何情况! –

相关问题