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

SwiftUI点击按钮添加含TextField/单选按钮的行时崩溃如何解决?

核心崩溃原因

  • 点击添加按钮时仅向skillList追加了元素,没有同步给evalList追加对应元素,两个数组长度不一致,后续通过下标访问evalList时直接触发数组越界崩溃
  • @State属性定义在了ContentView结构体外部,不符合SwiftUI状态属性的使用规范
  • 删除条目时仅移除了skillList的对应元素,没有同步移除evalList的同下标元素,也会触发数组越界

修正后代码

1. ContentView.swift

struct ContentView: View {
    // 状态属性必须放在View结构体内部
    @State var skills = [String]()
    @State var skillsEvals = [String]()
    
    var body: some View {
        NavigationView{
            VStack(alignment: .leading, spacing: 8) {
                HStack{
                    Text("Skills").font(.headline)
                    Spacer()
                }
                HStack{
                    Text("Add up to 5 skills").font(.callout)
                    Spacer()
                }
                SkillListEditor(evalList: $skillsEvals, skillList: $skills)
                Spacer()
            }
            .padding()
        }
    }
}

2. DynamicSkills.swift

struct SkillListEditor: View {
    @Binding var evalList: [String]
    @Binding var skillList: [String]

    var body: some View {
        ForEach(0..<skillList.count, id: \.self) { index in
            // SwiftUI支持直接用$数组[index]获取对应元素的Binding,无需自定义绑定方法
            ListItem(skillEvaluation: $evalList[index], text: $skillList[index]) {
                // 删除时同步操作两个数组,保证长度一致
                skillList.remove(at: index)
                evalList.remove(at: index)
            }
        }
        // 增加最多5条的限制,匹配界面提示
        if skillList.count < 5 {
            AddButton{
                // 添加时同步给两个数组追加元素
                skillList.append("")
                evalList.append("1") // 默认赋值1分,也可根据需求放空字符串
            }
        }
    }
}

fileprivate struct ListItem: View {
    @Binding var skillEvaluation: String
    @Binding var text: String
    var removeAction: () -> Void

    var body: some View {
        VStack(alignment: .leading, spacing: 8){
            Spacer().frame(height: 8)
            HStack{
                Button(action: removeAction) {
                    Image(systemName: "minus.circle.fill")
                        .foregroundColor(.red)
                        .padding(.horizontal)
                }
                TextField("Skill", text: $text)
                    .padding(.all)
                    .background(Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0))
                    .cornerRadius(6)
            }
            HStack{
                Text("Evaluation").font(.callout)
                Spacer()
            }
            HStack{
                RadioButtonField(id: "1", label: "1", isMarked: skillEvaluation == "1") { selected in
                    skillEvaluation = selected
                }
                Spacer()
                RadioButtonField(id: "2", label: "2", isMarked: skillEvaluation == "2") { selected in
                    skillEvaluation = selected
                }
                Spacer()
                RadioButtonField(id: "3", label: "3", isMarked: skillEvaluation == "3") { selected in
                    skillEvaluation = selected
                }
                Spacer()
                RadioButtonField(id: "4", label: "4", isMarked: skillEvaluation == "4") { selected in
                    skillEvaluation = selected
                }
                Spacer()
                RadioButtonField(id: "5", label: "5", isMarked: skillEvaluation == "5") { selected in
                    skillEvaluation = selected
                }
            }
        }
    }
}

fileprivate struct AddButton: View {
    var addAction: () -> Void

    var body: some View {
        HStack{
            Spacer()
            Button(action: addAction) {
                Text("add skill")
                    .padding(.horizontal, 12)
                    .padding(.vertical, 6)
                    .background(.white)
                    .foregroundColor(Color("PassioGreen"))
                    .overlay(RoundedRectangle(cornerRadius: 25).stroke(Color("PassioGreen"), lineWidth: 2))
            }
        }.padding(.top, 14)
    }
}

3. 单选按钮代码无需修改,可直接沿用原有实现


可选优化建议

可以把技能和对应的评分封装到同一个结构体中,不需要维护两个独立数组,逻辑更清晰也不会出现长度不一致的问题:

struct SkillItem: Identifiable {
    let id = UUID()
    var name: String
    var evaluation: String
}

后续状态可直接定义为@State var skillItems: [SkillItem] = [],增删操作只需要处理这一个数组即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 04:24:05