Reputation: 506
I'm trying to create a survey using SwiftUI, where the survey can have an arbitrary number of questions.
I'm trying to capture what values the user inputted through state variables such as:
@State var answer: String = ""
ForEach(survey) { surveyQuestion in
Text(surveyQuestion.question)
TextField(surveyQuestion.placeholder, text: $answer)
}
However, since I don't know how many questions are going to be in the survey beforehand, I don't know how many of these state variables to store the answers to make. I could create the variables on the fly inside the ForEach loop but then the variables would be out of scope when I actually go to submit the survey (since the submitting would happen outside of the ForEach loop).
How do I create an arbitrary number of state variables to capture the user's answers to the survey?
EDIT: I had tried making my answers variable a dictionary, where the keys are the IDs to the questions. My code looked like:
@State var answers: [String:String] = [:]
ForEach(survey) { surveyQuestion in
Text(surveyQuestion.question)
TextField(surveyQuestion.placeholder, text: $answers[surveyQuestion.id!])
}
However, I kept getting the error:
Cannot convert value of type 'Binding<String?>' to expected argument type 'Binding<String>'
So then I tried replacing $answers[surveyQuestion.id!]
with $(answers[surveyQuestion.id!]!)
but then the system gets confused and responds with:
'$' is not an identifier; use backticks to escape it
I had also tried adjusting my question model so that there's a field for an answer in the same struct. My code looked like this:
TextField(surveyQuestion.placeholder, text: $surveyQuestion.answer)
I kept getting the error:
Cannot find '$surveyQuestion' in scope
Upvotes: 2
Views: 684
Reputation: 52565
Using the strategy in the edit that you added, with the Dictionary, you could provide a custom Binding, like this:
func bindingForID(id: String) -> Binding<String> {
.init {
answers[id] ?? ""
} set: { newValue in
answers[id] = newValue
}
}
And you could use it like this:
TextField(surveyQuestion.placeholder, text: bindingForID(id: surveyQuestion.id))
In terms of adding this data to Firestore, you could trigger Firestore updates in the set
closure from the custom binding. However, I'd probably recommend moving this logic to a @Published
property on a view model (ObservableObject
) where you could use Combine to do things like Debouncing before you send the data to Firestore (probably a little beyond the scope of this question).
Upvotes: 1
Reputation: 594
Create a struct that holds id, question, and answer. Your @State var should be an array of those.
struct QA: Identifiable {
id: String
question: String
answer: String
}
…
@State private var myQAs: [QA] = myQAs() // or populate them in init, onAppear(if asynchronous) or however you see fit
…
ForEach(myQAs) { qa in
Text(qa.question)
TextField(placeholder, text: $qa.answer)
}
Upvotes: 0