在 ForEach 循环中删除项目会导致致命错误:索引超出范围
Posted
技术标签:
【中文标题】在 ForEach 循环中删除项目会导致致命错误:索引超出范围【英文标题】:Deleting item in ForEach loop causes fatal error: Index out of range 【发布时间】:2020-06-24 23:56:18 【问题描述】:我有一个 ScrollView,它使用数组中的 ForEach 循环显示行列表。当我从数组中删除一个项目时,我收到错误:索引超出范围。
ScrollView
ForEach(viewModel.tasks.indices, id: \.self) index in
TaskRow(
task: self.$viewModel.tasks[index],
deleteAction:
self.viewModel.deleteTask(task: self.viewModel.tasks[index])
)
此错误仅在我切换到从 ForEach 循环而不是“任务”本身传递索引时才开始发生。我必须这样做才能在子视图中使用@Binding var task: Task
:“TaskRow”
“删除操作”由子视图中的按钮触发。
viewModel.deleteTask
的工作方式如下(使用数据管理器):
final class StackDetailViewModel: ObservableObject
@Published var tasks = [Task]()
var dataManager: DataManagerProtocol
init(dataManager: DataManagerProtocol = DataManager.shared)
self.dataManager = dataManager
fetchTasks()
extension StackDetailViewModel
func fetchTasks()
tasks = dataManager.fetchTasks()
func deleteTask(task: Task)
dataManager.deleteTask(task: task)
fetchTasks()
dataManager 在哪里执行此操作:
Class DataManager
...
private var tasks = [Task]()
...
func fetchTasks() -> [Task]
tasks
func deleteTask(task: Task)
if let index = tasks.firstIndex(where: $0.id == task.id )
tasks.remove(at: index)
我在我的应用程序中使用协议,但为了简单起见,我在此处删除了它们。 任何帮助将不胜感激。
【问题讨论】:
你能展示你对viewModel.deleteTask
的实现吗?
@NewDev 我已经更新了 Q。谢谢
根据您显示的内容,它应该可以工作。我怀疑这个错误介于DataManager
和您的ViewModel
之间。
一般来说,您不希望在单步执行时从数组中删除
@NewDev 来自我所做的测试。仅当我从使用 ForEach(viewModel.tasks) task in
更改为上面显示的内容时,才会出现该错误。保持 dataManager 和 ViewModel 相同。
【参考方案1】:
直接在ForEach
中使用数组索引作为标识符并不是一个好习惯,因为索引是位置性的,并且不能识别它们所代表的项目。这可能会导致 SwiftUI 中出现重绘问题和崩溃。
例如。当您在常规List
中使用“滑动删除”时,SwiftUI 知道给定位置的项目已被删除,并且可能不会再次询问完整的 ID 列表(在这种不正确的情况下是索引)重绘内容。 SwiftUI 只会为下一次重绘提供剩余 ID 的列表,这会导致越界崩溃。
我并不是说这就是这里的确切情况,因为我无法重现您的崩溃。
在您自己编写时,当您修改 ForEach
以使用索引而不是您的任务时,就会出现问题。
代替
ForEach(viewModel.tasks.indices, id: \.self) index in
使用
ForEach(Array(viewModel.tasks.enumerated()), id: \.1.id) (index, task) in
这使您可以访问Binding
的索引,以访问TaskRow
所需的任务和关联的任务。此外,通过将任务 ID 指定为每一行的标识符,SwiftUI 将不再依赖位置索引。
但请注意:此解决方案可能不适用于非常大的任务数组,因为EnumeratedSequence
(由您的任务.enumerated()
返回的Array
)被扁平化为一个新的Array
.
因此,如果您要处理大量任务,我会推荐您的初始版本:ForEach(viewModel.tasks)
,并最终仅在 deleteAction
的情况下支付性能损失。
【讨论】:
以上是关于在 ForEach 循环中删除项目会导致致命错误:索引超出范围的主要内容,如果未能解决你的问题,请参考以下文章
将 GCP 订阅者与 Spring Boot 集成会导致内存致命错误
视图模型数组上的 ForEach 循环问题导致 Xcode 编译错误 - SwiftUI
SwiftUI - 致命错误:从数组中删除元素时索引超出范围