You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift UITableView移动行报错:尝试移动至不存在的索引路径

解决UITableView跨Section移动行的FRC相关错误

嘿,我帮你梳理下这个问题的根源和解决办法,你遇到的两个错误都是没搞清楚NSFetchedResultsController(简称FRC)和UITableView之间的协作逻辑~

第一个错误:"Attempt to move index path to index path that does not exist"

你直接调用tableView.moveRowAtIndexPath是错误的核心原因!因为当使用FRC时,UITableView的数据源完全由FRC提供,你不能直接操作UI层的行移动——此时FRC里的数据还没变化,目标section里根本没有对应的行,自然会报错说目标indexPath不存在。

第二个错误:行数不匹配的更新异常

你手动调用controller(_:didChangeObject:...)的操作完全搞错了方向,这个方法是FRC自己用来通知代理数据变化的,不是让我们手动调用的。你手动执行delete+insert,但FRC的数据源(Core Data里的数据)并没有被修改,导致tableView的行数计算和实际数据源不一致,触发了系统的更新校验错误。

正确的解决流程(基于FRC+Core Data)

核心思路是:先修改Core Data对象中区分section的属性,保存上下文后让FRC自动触发tableView的更新,而不是直接操作UI。

步骤1:确保FRC的Section划分正确

假设你的两个section是根据对象的某个布尔属性(比如isArchived,false在section0,true在section1)来划分的,那FRC的初始化要指定sectionNameKeyPath:

// 示例:初始化FRC时指定section划分的属性
let fetchRequest: NSFetchRequest<YourManagedObject> = YourManagedObject.fetchRequest()
// 先按section属性排序,再按其他业务字段排序
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "isArchived", ascending: true), NSSortDescriptor(key: "createDate", ascending: false)]

let frc = NSFetchedResultsController(fetchRequest: fetchRequest,
                                     managedObjectContext: yourManagedContext,
                                     sectionNameKeyPath: "isArchived",
                                     cacheName: nil)
frc.delegate = self

步骤2:修改moveRow方法的逻辑

在moveRowAt方法里,不要操作tableView,而是修改移动对象的section属性,然后保存上下文:

override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    // 获取要移动的Core Data对象
    let movedObject = fetchedResultsController.object(at: sourceIndexPath) as! YourManagedObject
    
    // 根据目标section修改对应的属性(这里假设section1对应isArchived=true)
    movedObject.isArchived = (destinationIndexPath.section == 1)
    
    // 保存上下文,触发FRC检测数据变化
    do {
        try movedObject.managedObjectContext?.save()
    } catch {
        print("保存上下文失败:\(error.localizedDescription)")
    }
}

步骤3:正确实现FRC的代理方法

确保你已经实现了FRC的三个核心代理方法,让它自动更新tableView:

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.beginUpdates()
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    switch type {
    case .delete:
        if let indexPath = indexPath {
            tableView.deleteRows(at: [indexPath], with: .automatic)
        }
    case .insert:
        if let newIndexPath = newIndexPath {
            tableView.insertRows(at: [newIndexPath], with: .automatic)
        }
    case .move:
        if let indexPath = indexPath, let newIndexPath = newIndexPath {
            tableView.moveRow(at: indexPath, to: newIndexPath)
        }
    case .update:
        if let indexPath = indexPath {
            tableView.reloadRows(at: [indexPath], with: .automatic)
        }
    @unknown default:
        break
    }
}

func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    tableView.endUpdates()
}

为什么这个方法能解决问题?

当你修改Core Data对象的属性并保存后,FRC会自动检测到数据变化,通过代理方法通知tableView执行对应的更新(这里会触发.move类型的变化,或者.delete+.insert,取决于你的排序规则),此时数据源和UI完全同步,不会出现indexPath不存在或行数不匹配的问题。

内容的提问来源于stack exchange,提问作者Nathaniel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 03:55:55