I am using SwiftUI to build a feature where a user can click on a button to create rows and each row has multiple views (Textfields, Radio buttons…) [see image below]
I created a swift class DynamicSkills where I handle the creation of the items and the "Add" button, but when I click "Add" the App crashes completely, below is my code so far and an image showing how it should be, I just started with SwiftUI and I am not fully grasping the concept of Binding<>.
What should I do exactly to get it up and running?
//DynamicSkills.swift
struct SkillListEditor: View {
@Binding var evalList: [String]
@Binding var skillList: [String]
func getBindingS(forIndex index: Int) -> Binding<String> {
return Binding<String>(get: { skillList[index] },
set: { skillList[index] = $0 })
}
func getBindingE(forIndex index: Int) -> Binding<String> {
return Binding<String>(get: { evalList[index] },
set: { evalList[index] = $0 })
}
var body: some View {
ForEach(0..<skillList.count, id: \.self) { index in
ListItem(skillEvaluation: getBindingE(forIndex: index), text: getBindingS(forIndex: index)) { self.skillList.remove(at: index) }
}
AddButton{ self.skillList.append("") }
}
}
fileprivate struct ListItem: View {
@Binding var skillEvaluation: String
@Binding var text: String
var removeAction: () -> Void
var body: some View {
VStack(alignment: .leading){
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.wrappedValue == "1" ? true : false) { selected in
self.skillEvaluation = selected
}
Spacer()
RadioButtonField(id: "2", label: "2", isMarked: $skillEvaluation.wrappedValue == "2" ? true : false) { selected in
self.skillEvaluation = selected
}
Spacer()
RadioButtonField(id: "3", label: "3", isMarked: $skillEvaluation.wrappedValue == "3" ? true : false) { selected in
self.skillEvaluation = selected
}
Spacer()
RadioButtonField(id: "4", label: "4", isMarked: $skillEvaluation.wrappedValue == "4" ? true : false) { selected in
self.skillEvaluation = selected
}
Spacer()
RadioButtonField(id: "5", label: "5", isMarked: $skillEvaluation.wrappedValue == "5" ? true : false) { selected in
self.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)
}
}
And in my main screen in ContentView I am showing it this way:
//ContentView.swift
@State var skills = [String]()
@State var skillsEvals = [String]()
struct ContentView: View {
NavigationView{
VStack{
HStack{
Text("Skills").font(.headline)
Spacer()
}
HStack{
Text("Add up to 5 skills").font(.callout)
Spacer()
}
SkillListEditor(evalList: $skillsEvals, skillList: $skills)
}
}
}
Update: this is the custom radio button class CustomRadio.swift
//Custom Radio button used:
struct RadioButtonField: View {
let id: String
let label: String
let isMarked: Bool
let callback: (String)->()
init(
id: String,
label:String,
isMarked: Bool = false,
callback: @escaping (String)->()
) {
self.id = id
self.label = label
self.isMarked = isMarked
self.callback = callback
}
var body: some View {
Button(action:{
self.callback(self.id)
}) {
VStack(alignment: .center) {
Text(label).foregroundColor(self.isMarked ? .white : .black).bold().padding(.horizontal, 8).padding(.vertical, 8)
}
}
.padding()
.background(self.isMarked ? Color("PassioGreen") : Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0))
.cornerRadius(10)
}
}
>Solution :
Your AddButton action appends an item to skillList, but does not append an item to evalList. So when ListItem uses its skillEvaluation binding, the binding’s getter tries to access an element of evalList that doesn’t exist.
Try this:
AddButton {
self.evalList.append("")
self.skillList.append("")
}
