I am trying to display a dynamic list of text fields using a ForEach
. The following code is working as expected: I can add/remove text fields, and the binding is correct. However, when I move the items
in a ObservableObject
view model, it does not work anymore and it crashes with an index out of bounds
error. Why is that? How can I make it work?
struct ContentView: View {
@State var items = ["A", "B", "C"]
var body: some View {
VStack {
ForEach(items.indices, id: \.self) { index in
FieldView(value: Binding<String>(get: {
items[index]
}, set: { newValue in
items[index] = newValue
})) {
items.remove(at: index)
}
}
Button("Add") {
items.append("")
}
}
}
}
struct FieldView: View {
@Binding var value: String
let onDelete: () -> Void
var body: some View {
HStack {
TextField("item", text: $value)
Button(action: {
onDelete()
}, label: {
Image(systemName: "multiply")
})
}
}
}
The view model I am trying to use:
class ViewModel: Observable {
@Published var items: [String]
}
@ObservedObject var viewModel: ViewModel
I found many questions dealing with the same problem but I could not make one work with my case. Some of them do not mention the TextField
, some other are not working (anymore?).
Thanks a lot
By checking the bounds inside the Binding
, you can solve the issue:
struct ContentView: View {
@ObservedObject var viewModel: ViewModel = ViewModel(items: ["A", "B", "C"])
var body: some View {
VStack {
ForEach(viewModel.items.indices, id: \.self) { index in
FieldView(value: Binding<String>(get: {
guard index < viewModel.items.count else { return "" } // <- HERE
return viewModel.items[index]
}, set: { newValue in
viewModel.items[index] = newValue
})) {
viewModel.items.remove(at: index)
}
}
Button("Add") {
viewModel.items.append("")
}
}
}
}
It is a SwiftUI bug, similar question to this for example.