使用 NSFetchedResultsController 更新部分

Posted

技术标签:

【中文标题】使用 NSFetchedResultsController 更新部分【英文标题】:Updating sections with NSFetchedResultsController 【发布时间】:2016-09-18 22:35:07 【问题描述】:

我有一个名为 ToDoItem 的实体。其中具有三个属性:

@NSManaged var toDoString: String?
@NSManaged var creationDate: NSDate?
@NSManaged var isComplete: NSNumber?

我正在尝试创建一个NSFetchedResultsController,它将根据isComplete 变量显示两个部分。我能够做到这一点成功购买这样做:

lazy var fetchedResultsController: NSFetchedResultsController = 

    let questionAnswerFetchRequest = NSFetchRequest(entityName: "ToDoItem")

    let questionAnswerFetchSortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
    questionAnswerFetchRequest.sortDescriptors = [questionAnswerFetchSortDescriptor]

    let frc = NSFetchedResultsController(
        fetchRequest: questionAnswerFetchRequest,
        managedObjectContext: (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext,
        sectionNameKeyPath:"isComplete",
        cacheName: nil)

    frc.delegate = self
    return frc
()

我也实现了这样的删除方法:

//
// MARK: Fetched Results Controller Delegate Methods
//
func controllerWillChangeContent(controller: NSFetchedResultsController) 
    tableView.beginUpdates()


func controllerDidChangeContent(controller: NSFetchedResultsController) 
    tableView.endUpdates()


func controller(controller: NSFetchedResultsController,
                didChange sectionInfo: NSFetchedResultsSectionInfo,
                          atSectionIndex sectionIndex: Int,
                                         for type: NSFetchedResultsChangeType) 

    switch (type) 
    case .Insert:
        self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        break
    case .Delete:
        self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        break
    default:
        break
    



func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) 
    switch (type) 
    case .Insert:
        if let indexPath = newIndexPath 
            tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        
        break;
    case .Delete:
        if let indexPath = indexPath 
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        
        break;
    case .Update:
        if let indexPath = indexPath 
            tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
        
        break;
    case .Move:
        if let indexPath = indexPath 
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        

        if let newIndexPath = newIndexPath 
            tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
        
        break;
    

问题是当我更新isComplete 属性时,我得到了这个错误:

CoreData:错误:严重的应用程序错误。捕获到异常 在调用期间从 NSFetchedResultsController 的委托 -controllerDidChangeContent:。无效更新:无效的节数。之后表格视图中包含的节数 更新 (1) 必须等于包含在 更新前的表视图(0),加减数 插入或删除的部分(0 插入,0 删除)。与用户信息 (空)

我理解 what 这是在说什么,我只是不明白 为什么 它会导致该错误。我已经按照文档实现了委托方法.....


我已经更新了我的 NSFetchedResultsController 创建,以按照 @wain 的回答添加另一个 sortDescriptor,如下所示:

lazy var fetchedResultsController: NSFetchedResultsController = 

        let questionAnswerFetchRequest = NSFetchRequest(entityName: "ToDoItem")

        let isCompleteSortDescriptor   = NSSortDescriptor(key: "isComplete", ascending: false)
        let creationDateSortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)

        questionAnswerFetchRequest.sortDescriptors = [isCompleteSortDescriptor, creationDateSortDescriptor]

        let frc = NSFetchedResultsController(
            fetchRequest: questionAnswerFetchRequest,
            managedObjectContext: (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext,
            sectionNameKeyPath:"isComplete",
            cacheName: nil)

        frc.delegate = self
        return frc
    ()

我添加了一些断点,结果发现这个委托方法永远不会被调用:

func controller(controller: NSFetchedResultsController,
                didChange sectionInfo: NSFetchedResultsSectionInfo,
                          atSectionIndex sectionIndex: Int,
                                         for type: NSFetchedResultsChangeType) 

    switch (type) 
    case .Insert:
        self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        break
    case .Delete:
        self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        break
    default:
        break
    

这会导致与上面发布的相同的错误。关于为什么永远不会调用它的任何想法?

【问题讨论】:

【参考方案1】:

您需要为isComplete 添加一个排序描述符。它应该是第一个排序描述符,然后是你的日期。

基本上,排序和部分需要兼容,而您的当前不兼容。

【讨论】:

您还应该准确检查您获得的委托回调,因为您应该被告知该部分的更新和添加,因此您可能会对视图进行误导性更新,复合更改是难以进行精细管理 我已经更新了任何问题。添加其他排序描述符不起作用:( 您使用的是哪个版本的 swift?看起来func签名应该是func controller(_ controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) 啊!你是对的!我从这里复制了 func 签名:developer.apple.com/reference/coredata/… 这与您发布的不同。不确定这是否是文档中的印刷错误。 (目前正在运行 swift 2)

以上是关于使用 NSFetchedResultsController 更新部分的主要内容,如果未能解决你的问题,请参考以下文章

核心数据保存竞争条件错误

在 Swift 3 中难以配置 NSFetchedResultsController

为啥 beginUpdates/endUpdates 会重置表视图位置以及如何阻止它这样做?

测试使用

第一篇 用于测试使用

在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?