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是父组件传入的只读值,不需要子组件持有状态。
修复方案
- 首先确保你的
Task结构体遵守Identifiable协议(你代码里已经用到了task.id,说明已经有id属性,直接加遵守即可):
struct Task: Identifiable { let id = UUID() // 保留你原本的id定义即可 var name: String var finished: Bool // 其他原有属性保持不变 }
- 修改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() }
- 修复deleteTask方法,根据传入的task匹配索引后删除:
func deleteTask(task: Task) { guard let index = data.tasks.firstIndex(where: { $0.id == task.id }) else { return } data.tasks.remove(at: index) }
- 修复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
相关产品推荐
相关产品推荐

