【问题标题】:NSFetchedResultsControllerDelegate returning wrong NSFetchedResultsChangeTypeNSFetchedResultsControllerDelegate 返回错误的 NSFetchedResultsChangeType
【发布时间】:2017-04-19 04:38:03
【问题描述】:

这是我的委托实现:

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.beginUpdates()
    ItemListTableViewController.logger.log("begin updates")
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.endUpdates()
    ItemListTableViewController.logger.log("end updates")
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    ItemListTableViewController.logger.log(type.rawValue, "\(indexPath) -> \(newIndexPath)")
    switch type {
    case .delete:
        self.tableView.deleteRows(at: [indexPath!], with: .automatic)
    case .insert:
        self.tableView.insertRows(at: [newIndexPath!], with: .automatic)
    case .move:
        self.tableView.moveRow(at: indexPath!, to: newIndexPath!)
    case .update:
        self.tableView.reloadRows(at: [indexPath!], with: .automatic)
    }
}

这是我得到的日志:

ItemListTableViewController >>> "perform insert start" 
ItemListTableViewController >>> "begin updates" 
ItemListTableViewController >>> 1 "nil -> Optional([0, 0])" 
ItemListTableViewController >>> 4 "Optional([0, 1]) -> Optional([0, 2])"
ItemListTableViewController >>> "end updates" 
ItemListTableViewController >>> "perform insert end" 

我正在尝试通过调用context.insert(item) 将新项目插入上下文,并根据日志的第 3 行插入新项目。控制器根据第 4 行移动一些项目。但是NSFetchedResultsChangeType 中原始值“4”的类型应该是update,而不是move

我也测试过其他情况,当我需要更新一个项目时,它给了我一个move 类型。

我对updatemove 的含义有误吗?或者这是一个错误?

我错了。这是一个糟糕的代码。

我使用的是 Xcode 8.3.1

【问题讨论】:

    标签: swift core-data xcode8


    【解决方案1】:

    感谢@Joe Rose,我明白这是如何工作的。我没有处理可能同时插入和移动的任务,所以我没有使用你的解决方案。

    我对 updatemove 的含义有误。所以我再次阅读了Apple的文档。它说:

    使用以下启发式方法报告更改:

    • 在添加和删除操作时,仅报告添加/删除的对象。

      假设受影响对象之后的所有对象也被移动,但这些移动不会被报告。

    • 当对象的更改属性是获取请求中使用的排序描述符之一时,将报告移动。

      在这种情况下假定更新了对象,但不会向委托发送单独的更新消息。

    • 当对象的状态发生更改时会报告更新,但更改的属性不是排序键的一部分。

    所以当move 被报告时,update 也会被报告。那个时候抓取的对象更新了,但是tableView没有更新,所以我需要用indexPath定位cell,newIndexPath获取合适的对象。

    这是我的最终解决方案,它现在表现良好:

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
        ItemListTableViewController.logger.log(type.rawValue, "\(indexPath) -> \(newIndexPath)")
    
        switch type {
        case .insert:
            self.tableView.insertRows(at: [newIndexPath!], with: .automatic)
        case .delete:
            self.tableView.deleteRows(at: [indexPath!], with: .automatic)
        case .move:
            self.tableView.moveRow(at: indexPath!, to: newIndexPath!)
            fallthrough
        case .update:
            self.configure(cell: tableView.cellForRow(at: indexPath!) as! ItemInfoCell, at: newIndexPath!)
        }
    }
    
    
    func configure(cell: ItemInfoCell, at indexPath: IndexPath) {
        let item = fetchController.object(at: indexPath)
        cell.item = item
    }
    

    如果有什么问题,请告诉我。

    【讨论】:

    • 在更新的情况下,您可以只在方法参数中传递anObject,而不必fetchController.object(at: indexPath)
    • 如果更新对象的单元格不在屏幕上,那么 cellForRow 将返回 nil 和你的 as!会崩溃。你需要这样做 if let cell = tableView.cellForRow(..) { configure(..) }
    【解决方案2】:

    4 是update 的枚举值; 3 是move 的值。您可能会对被委派的两个索引路径感到困惑。 indexPath 是更新前的索引(在任何插入或删除之前),newIndexPath 是更新后的索引。不幸的是,Apple 的文档在如何使用 fetchedResults 控制器进行更新时是错误的。对于更新,您应该使用newIndexPath,因为更新是在插入和删除之后处理的。因此,您可能会遇到崩溃或不良行为。

    您处理移动的方式也不正确。见App crashes after updating CoreData model that is being displayed in a UITableView

    【讨论】:

    • 我添加了一些有问题的上下文代码。所以,我只需要为update 类型重新加载 newIndex?我仍然对move 感到困惑。这不是为moveRow(at: to)设计的吗?
    • 您会认为NSFetchedResultsChangeTypeMove 可以转换为moveRow(at: to)。这也是 Apple 文档所说的。可悲的是,他们错了。问题是indexPath 是插入和删除之前的索引,newIndexPath 是插入和删除之后的索引。因此,如果您有 [B, C, D] 和 B 移动并且 A 被插入为 [A, C, B, D],您将得到 insert (0,0) 和 move (0,0) ->(0,2)(不是您所期望的 (0,1)-(0,2))。因此,如果您尝试使用 indexPath 和 newIndexPath 调用 moveRow(at: to),您最终会移动 A 而不是 B。
    • 这就是我的困惑。所以我应该自己计算每个索引?我需要记录之前的每一次插入和删除,并为当前索引做一个偏移量?
    • 我看到了,在insert/delete/update中,indexPath是没有用的,所以我只需要在move的情况下计算即可。
    • 对于delete 使用indexPath;对于insertupdate 使用newIndexPath。我有一个完整的答案,关于如何使 tableView 或 collectionView 永远不会因我在上面的答案中链接到的获取的结果控制器更新而崩溃。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-19
    • 1970-01-01
    • 1970-01-01
    • 2023-01-24
    • 1970-01-01
    • 2019-09-23
    相关资源
    最近更新 更多