【问题标题】:Deleting item in ForEach loop causes fatal error: Index out of range在 ForEach 循环中删除项目会导致致命错误:索引超出范围
【发布时间】: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 相同。

标签: swiftui


【解决方案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 的情况下支付性能损失。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-22
    • 2018-04-20
    相关资源
    最近更新 更多