iOS8 Swift:deleteRowsAtIndexPaths崩溃

9
我在Swift、iOS 8和Xcode 6 Beta 6中的tableView中删除一行时遇到了麻烦。每次尝试删除一行时,都会出现类似以下错误的信息:
``` Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3302.3.1/UITableView.m:1581 2014-08-30 20:31:00.971 Class Directory[13290:3241692] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (25) must be equal to the number of rows contained in that section before the update (25), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). ``` 我已经阅读了所有关于这个常见问题的答案,并且感觉我已经满足建议的条件。似乎从数据模型中删除了该项--当我重新加载应用程序时,被删除的项已经从表格中消失--但是在相应的sqlite文件中仍存在一些残留物,当然,数学不符合。输出indexPath的println显示正确的Section和Row。 我非常困惑。这应该很简单,但我肯定错过了一些愚蠢的东西,我怀疑是在数据模型删除中。完整项目在Github上。
func numberOfSectionsInTableView(tableView: UITableView!) -> Int {

    return fetchedResultController.sections.count

}


func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
    return fetchedResultController.sections[section].numberOfObjects

    }

func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
    let cell = tableViewMain.dequeueReusableCellWithIdentifier("CellMain", forIndexPath: indexPath) as UITableViewCell

        let personForRow = fetchedResultController.objectAtIndexPath(indexPath) as Person
        cell.textLabel.text = personForRow.fullName()

        return cell

}

func tableView(tableView: UITableView!, canEditRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
    return true
}

func tableView(tableView: UITableView!, editingStyleForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCellEditingStyle {
    return UITableViewCellEditingStyle.Delete
}

 func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
    println("section and row \(indexPath.section) \(indexPath.row) ")
    if (editingStyle == UITableViewCellEditingStyle.Delete) {
    let personForRow : NSManagedObject = fetchedResultController.objectAtIndexPath(indexPath) as Person
    context?.deleteObject(personForRow)
    context?.save(nil)
        tableViewMain.beginUpdates()
    tableViewMain.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
        tableViewMain.endUpdates()
    }

3
可能是 crash on deleteRowsAtIndexPaths 的重复问题。 - durron597
那些回复不够充分。 - Nate Birkholz
2个回答

6

使用Xcode Core Data Master-Detail模板项目很容易重现崩溃。通常情况下,当你使用NSFetchedResultsController时,你应该真正使用NSFetchedResultsControllerDelegate(你已经声明了它但没有使用)。

tableView:commitEditingStyle:forRowAtIndexPath:方法中删除这些行:

tableViewMain.beginUpdates()
tableViewMain!.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
tableViewMain.endUpdates()

并将这些行添加到您的视图控制器类中:

func controllerWillChangeContent(controller: NSFetchedResultsController) {
    tableViewMain.beginUpdates()
}

func controller(controller: NSFetchedResultsController!, didChangeSection sectionInfo: NSFetchedResultsSectionInfo!, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
    switch type {
    case .Insert:
        tableViewMain.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
    case .Delete:
        tableViewMain.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
    default:
        return
    }
}

func controller(controller: NSFetchedResultsController!, didChangeObject anObject: AnyObject!, atIndexPath indexPath: NSIndexPath!, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath!) {
    switch type {
    case .Insert:
        tableViewMain.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
    case .Delete:
        tableViewMain.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
    case .Update:
        return
        //Should also manage this case!!!
        //self.configureCell(tableView.cellForRowAtIndexPath(indexPath), atIndexPath: indexPath)
    case .Move:
        tableViewMain.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        tableViewMain.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
    default:
        return
    }
}

func controllerDidChangeContent(controller: NSFetchedResultsController!) {
    tableViewMain.endUpdates()
}

这应该能解决你的问题。

感谢您详细的回复。在这段代码中,Xcode 给我报错了。由于同时存在 func controller(controller: NSFetchedResultsController!, didChangeSection 和 func controller(controller: NSFetchedResultsController!, didChangeObject),第二个 controller 函数会报错“Definition conflicts with previous value”。如果我注释掉第一个函数,错误就消失了。您有什么想法吗? - Nate Birkholz
糟糕,现在已经很晚了,我把代码粘贴到了 commitEditingStyle 函数的中间,而不是视图控制器类的根部。那样运行得非常完美。非常感激。 - Nate Birkholz
这并不完全是我的问题。我的代码问题在于,tableView 行数函数在删除行后没有更新其计数。这篇帖子提示我去看看我的代码那一部分。 谢谢! - Axe

3
我认为这只是一个缓存问题。您的 fetchedResultController 不会自动重新获取结果,因为它会缓存其结果。这意味着当再次调用 tableView:numberOfRowsInSection: 时,结果计数仍然返回25,即使您删除了一项。

1
@NateBirkholz 它在 deleteRowsAtIndexPaths 内部调用了 tableView:numberOfRowsInSection。这就是它首先打印出错误消息的方式,更重要的是,这是它验证您尝试添加/删除的行是否真正从数据中删除的方式。 - drewag
是的,这就是它崩溃的地方...有关于解决方案的具体想法吗?(我的初始回答是错误的,我添加了更多的调试日志,你是正确的,对我来说很抱歉犯了错误。) - Nate Birkholz
1
@NateBirkholz,也许有一种方法可以强制fetchedResultsController清除其缓存。然而,如果我是你,我会将结果存储在临时数组成员中,并使用该成员作为数据源。这样,你可以自己管理数组(删除元素),而不必担心fetchedResults的实现细节。 - drewag
以前它是加载到一个数组中的,但我遇到了很多问题,我的导师建议直接在数据模型上操作。我会再深入研究一下这个见解。谢谢。 - Nate Birkholz
1
@NateBirkholz,我建议增加额外的抽象层,这样如果需要的话,更容易将您的应用程序切换到使用不同的数据源。也许您将来想要添加Dropbox支持或其他同步服务。通常最好在控制器和实际存储信息的方式之间增加一个抽象层。 - drewag
我会在完成项目后再做那个,那听起来像是一个很好的学习经验。非常感谢您的建议。我也认为抽象化是一个不错的想法。 - Nate Birkholz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接