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

SwiftUI使用Binding和Toggle时删除任务触发Index out of range错误如何解决

问题根因

  • ForEach遍历data.tasks.indices且用\.self作为id,本质是把数组索引作为稳定标识符。但删除数组元素后索引会动态变化,旧的行视图持有的绑定仍然指向原来的索引位置,当原有索引超出新数组长度时就会触发索引越界崩溃。
  • deleteTask方法逻辑错误:remove(at:需要传入数组的下标索引,你当前传入的是data.tasks[task_index].id也就是任务的id,两者完全不相等,这也是触发越界的直接原因之一。
  • TaskRow中task属性用@State修饰不合理,@State用于修饰当前视图自身管理的状态,这里的task是父组件传入的只读值,不需要子组件持有状态。

修复方案

  1. 首先确保你的Task结构体遵守Identifiable协议(你代码里已经用到了task.id,说明已经有id属性,直接加遵守即可):
struct Task: Identifiable {
    let id = UUID() // 保留你原本的id定义即可
    var name: String
    var finished: Bool
    // 其他原有属性保持不变
}
  1. 修改TaskList中的ForEach写法,不再遍历索引,直接遍历任务元素获取绑定:
// 替换原来的ForEach(data.tasks.indices...) 部分代码
ForEach($data.tasks) { $task in
    TaskRow(
        isSet: $task.finished,
        task: task,
        onChange: { self.data.save() },
        onDelete: { self.deleteTask(task: task) }
    )
    Divider()
}
  1. 修复deleteTask方法,根据传入的task匹配索引后删除:
func deleteTask(task: Task) {
    guard let index = data.tasks.firstIndex(where: { $0.id == task.id }) else { return }
    data.tasks.remove(at: index)
}
  1. 修复TaskRow中的属性定义,去掉task的@State修饰:
struct TaskRow: View {
    @Binding var isSet: Bool
    let task: Task // 改为普通let常量即可,不需要@State包装
    var onChange: () -> ()
    var onDelete: () -> ()
  
    // 原有body代码保持不变
}

兼容旧版本SwiftUI的替代写法

如果你的项目需要支持SwiftUI 3以下版本,无法直接遍历绑定数组,可以用下面的写法替代:

ForEach(data.tasks) { task in
    let index = data.tasks.firstIndex(where: { $0.id == task.id })!
    TaskRow(
        isSet: $data.tasks[index].finished,
        task: task,
        onChange: { self.data.save() },
        onDelete: { self.deleteTask(task: task) }
    )
    Divider()
}

因为Task的id唯一,这里的强制解包是安全的。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 11:30:06