SwiftUI seems to have a rather annoying limitation that makes it hard to create a List
or a ForEach
while getting a binding to each element to pass to child views.
The most often suggested approach I've seen is to iterate over indices, and get the binding with $arr[index]
(in fact, something similar was suggested by Apple when they removed Binding
's conformance to Collection
):
@State var arr: [Bool] = [true, true, false]
var body: some View {
List(arr.indices, id: \.self) { index in
Toggle(isOn: self.$arr[index], label: { Text("\(idx)") } )
}
}
That works until the array changes in size, and then it crashes with index out of range run-time error.
Here's an example that will crash:
class ViewModel: ObservableObject {
@Published var arr: [Bool] = [true, true, false]
init() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.arr = []
}
}
}
struct ContentView: View {
@ObservedObject var vm: ViewModel = .init()
var body: some View {
List(vm.arr.indices, id: \.self) { idx in
Toggle(isOn: self.$vm.arr[idx], label: { Text("\(idx)") } )
}
}
}
What's the right way to handle deletion from a List, while still maintaining the ability to modify elements of it with a Binding?