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

SwiftUI删除数组元素后onReceive触发数组越界致命错误

问题分析与解决方案

问题根源

删除模块后触发索引越界,核心原因有两点:

  1. 视图销毁滞后:直接修改ViewModel数组删除模块时,SwiftUI视图更新存在短暂延迟,已删除模块的子视图(如CustomTextField)仍持有失效的绑定引用,触发订阅时访问了不存在的数据。
  2. 冗余绑定操作:CustomTextField中onReceive里的value = newValue完全多余(TextField已通过双向绑定同步值),该操作会在绑定对象失效时引发非法访问。

修复方案

1. 将删除逻辑封装到ViewModel,保证数据一致性

把模块删除操作移到ViewModel中,避免视图层直接操作数组索引,让SwiftUI更精准地跟踪数据变化:

class ViewModel: ObservableObject {
    @Published var classModules: [ClassModule] = [/* 原有初始化数据 */]
    
    // 添加统一删除方法
    func deleteModule(withId id: String) {
        classModules.removeAll { $0.id == id }
    }
}

在ContentView中调用该方法替代原有的索引删除:

struct ContentView: View {
    @ObservedObject var viewModel: ViewModel = ViewModel()
    
    var body: some View {
        ForEach($viewModel.classModules) { $classModule in
            ClassModuleView(classModule: $classModule, remove: {
                viewModel.deleteModule(withId: classModule.id)
            })
        }
    }
}

2. 修复CustomTextField的订阅逻辑

移除冗余的赋值操作,若需处理文本变更,改用onChange(仅在视图有效时触发,避免访问失效绑定):

struct CustomTextField: View {
    @Binding var value: String
    var body: some View {
        let _ = print("Rendering textfield: \(value)")
        TextField("", text: $value)
            .onChange(of: value) { newValue in
                print("onChange", newValue)
                // 这里添加自定义文本处理逻辑,比如过滤、格式化
                value = newValue.trimmingCharacters(in: .whitespaces)
            }
    }
}

3. 可选:明确ForEach的标识(增强稳定性)

虽然ClassModule已实现Identifiable,明确指定id可进一步避免视图识别错误:

ForEach($viewModel.classModules, id: \.id) { $classModule in
    ClassModuleView(classModule: $classModule, remove: {
        viewModel.deleteModule(withId: classModule.id)
    })
}

完整修正代码

import SwiftUI
import Combine

class ViewModel: ObservableObject {
    @Published var classModules: [ClassModule] = [
        ClassModule(name: "Maths", students: [
            Student(name: "John"),
            Student(name: "Emma"),
            Student(name: "James")
        ]),
        ClassModule(name: "Science", students: [
            Student(name: "David"),
            Student(name: "Kerry"),
            Student(name: "Anna")
        ]),
        ClassModule(name: "Art", students: [
            Student(name: "Georgina"),
            Student(name: "Bob"),
            Student(name: "Fred")
        ])
    ]
    
    func deleteModule(withId id: String) {
        classModules.removeAll { $0.id == id }
    }
}

struct ClassModule: Identifiable, Hashable {
    let id: String = UUID().uuidString
    var name: String
    var students: [Student]
}

struct Student: Identifiable, Hashable {
    let id: String = UUID().uuidString
    var name: String
}

struct ContentView: View {
    @ObservedObject var viewModel: ViewModel = ViewModel()
    
    var body: some View {
        ForEach($viewModel.classModules, id: \.id) { $classModule in
            ClassModuleView(classModule: $classModule, remove: {
                viewModel.deleteModule(withId: classModule.id)
            })
        }
    }
}

struct ClassModuleView: View {
    @Binding var classModule: ClassModule
    var remove: () -> Void
    
    var body: some View {
        VStack {
            HStack {
                Text(classModule.name)
                    .font(.title)
                Spacer()
                Button(action: remove, label: {
                    Text("Delete")
                })
            }
            Group {
                ForEach($classModule.students) { $student in
                    StudentView(student: $student)
                }
            }
        }
    }
}

struct StudentView: View {
    @Binding var student: Student
    
    var body: some View {
        CustomTextField(value: $student.name)
    }
}

struct CustomTextField: View {
    @Binding var value: String
    var body: some View {
        let _ = print("Rendering textfield: \(value)")
        TextField("", text: $value)
            .onChange(of: value) { newValue in
                print("onChange", newValue)
                // 自定义文本处理示例:去除首尾空格
                value = newValue.trimmingCharacters(in: .whitespaces)
            }
    }
}

#Preview {
    ContentView()
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 01:05:55