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

iOS Swift中UITableView使用拖拽功能时触发NSRangeException崩溃

问题现象

实现UITableView行拖拽重排功能时运行触发崩溃,崩溃日志如下:

Terminating app due to uncaught exception 'NSRangeException', reason: 'Attempted to scroll the table view to an out-of-bounds row (3) when there are only 1 rows in section 0.

报错截图:
报错截图

现有代码

ViewDidLoad配置

tableView.dragDelegate = self
tableView.dragInteractionEnabled = true
tableView.dataSource = self
tableView.delegate = self

已实现的代理方法

func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
    let dragItem = UIDragItem(itemProvider: NSItemProvider())
   // dragItem.localObject = data[indexPath.row]
    return [ dragItem ]
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
   // 此处已编写本地数据库更新逻辑
    self.tableView.reloadData()
    print("Drag from :\(sourceIndexPath.section) and Record Number:\(sourceIndexPath.row) TO :\(destinationIndexPath.section)")
}

预期效果

  • 拖拽前效果:
    拖拽前效果
  • 拖拽完成后预期行顺序更新效果:
    拖拽后预期效果
  • 已在moveRowAt代理方法中正确更新本地数据库记录,操作完成后调用了tableView.reloadData(),但拖拽操作时仍触发上述越界崩溃。
崩溃原因
  1. 核心触发点:在tableView(_:moveRowAt:to:)回调执行时直接调用reloadData()。该回调触发时系统拖拽交互动画尚未结束,UITableView内部仍持有拖拽流程对应的索引路径状态,此时全量刷新表会导致系统持有的旧索引(如报错中的row=3)和刷新后实际的行数(section0仅1行)不匹配,触发越界崩溃。
  2. 缺失必要逻辑:注释了dragItem.localObject的对象绑定,未实现tableView(_:canMoveRowAt:)数据源方法,也未配置Drop代理,系统无法正确识别拖拽行的状态、完成本地移动操作。
修复方案
  • 移除moveRowAt方法中的self.tableView.reloadData()调用:拖拽重排完成后系统会自动更新单元格位置,无需手动全量刷新。
  • 在moveRowAt方法中优先同步内存中的数据源,再执行数据库更新,保证数据源和UI状态一致:
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    // 先更新内存中的数据源数组
    let movedItem = data.remove(at: sourceIndexPath.row)
    data.insert(movedItem, at: destinationIndexPath.row)
    // 再执行本地数据库更新逻辑
    // 原有数据库更新代码保留...
    print("Drag from :\(sourceIndexPath.section) and Record Number:\(sourceIndexPath.row) TO :\(destinationIndexPath.section)")
}
  • 补全拖拽相关配置:
    1. 在ViewDidLoad中增加tableView.dropDelegate = self配置
    2. 取消itemsForBeginning方法中dragItem.localObject的注释,绑定当前拖拽的数据对象
    3. 实现canMoveRowAt数据源方法,允许行移动
    4. 实现Drop代理方法,支持当前tableView内的移动操作
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
    // 根据业务需求返回可移动的行,默认全部可移动直接返回true
    return true
}

func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
    let dragItem = UIDragItem(itemProvider: NSItemProvider())
    dragItem.localObject = data[indexPath.row]
    return [ dragItem ]
}

func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
    // 指定仅支持当前tableView内的行移动操作
    return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}

func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
    // 重排逻辑已在moveRowAt中处理,无需额外代码
}
  • 特殊场景说明:如果拖拽完成后确实需要刷新UI,不要直接调用reloadData(),可将刷新逻辑异步派发到主线程,等系统拖拽流程完全结束后再执行,或使用performBatchUpdates做增量更新,避免打断系统拖拽状态。

内容的提问来源于stack exchange,提问作者Mr.Javed Multani

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 15:18:19