The Problem
We have a words
state, an array of String, and we want to bind to multiple TextField
s. To make the problem more interesting, we trigger a mutation of state only when tapping a button.
This is a naive attempt:
struct ContentView: View {
@State var words = [String]()
var body: some View {
VStack {
Button("Mutate words state") {
self.words = ["Circuit", "Breaker"]
}
List {
ForEach(words.indices) {
Text("\(self.words[$0])")
}
}
}
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
There are 2 difficulties in achieving a complete solution:
- The way to loop with
ForEach
- Get the
Binding<String>
for the text field
Error: ForEach
only for constant data
The runtime error with the code above, when the button is tapped, is:
ForEach<Range
, Int, Text> count (2) != its initial count (0). `ForEach(_:content:)` should only be used for *constant* data. Instead conform data to `Identifiable` or use `ForEach(_:id:content:)` and provide an explicit `id`!
This is because there is a runtime check on the count, to ensure constant data.
The suggested solution is to have the data conform to Identifiable
. You might try:
ForEach(words, id: \.self) { word in
// word is String (cannot get the binding!)
}
But the issue with the above is that you cannot get the binding to the words.
Solution: Use Range<Int>
ForEach(0..<words.count, id: \.self) { i in
TextField("", text: self.$words[i]) // <-- and the binding
}
Yup, we iterate the indexes, and so we can access $words[i]
, which is the Binding<String>
as needed by TextField.
A thing about binding
$words[i]
is a binding to the array item – a Binding<String>
– powered by the binding @dynamicMemberLookup.
We have so far used a simple example. But if the model is deep, eg self.$viewModel.sentence.words[i]
– the $
(projected value) will still magically provide the binding to the property!
You can think of the $
as being applied to viewModel.sentence.words[i]
. Or applied to viewModel.whatever.is.here
.
It will also works if the model is @ObservedObject
, with @Published
array of other struct items!