我有一个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循环而不是“任务”本身传递索引时才开始发生。我必须这样做,以便可以在子视图“ TaskRow”中使用@Binding var task: Task
“删除操作”由子视图中的按钮触发。
viewModel.deleteTask
的工作方式如下(使用dataManager):
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)
}
}
}
我在我的应用程序中使用协议,但为简单起见,在此已将其删除。 任何帮助将不胜感激。
答案 0 :(得分:0)
直接在 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
的情况下支付性能损失。