I have a ForEach
block and a Stepper
embedded in a List
view. The contents of the List
view's first section is as follows:
ForEach(record.nodes.indices, id: \.self) { index in
HStack {
TextField("X", text: self.$record.nodes[index].xString)
Spacer()
Divider()
TextField("Y", text: self.$record.nodes[index].yString)
Spacer()
}
}
Stepper("± node", onIncrement: {
self.record.nodes.append(Node(x: 0, y: 0))
}, onDecrement: {
self.record.nodes.removeLast()
})
The issue I am facing is that upon calling self.record.nodes.removeLast()
, the application crashes with an Index out of range
error. I've been trying to solve this for hours, but to no avail.
I originally used onDelete
, however that produced the same issue.
The project can be found at https://github.com/jacobcxdev/Timekeeper, with this error happening in RecordDetailView.swift.
The index out of range error occurs because init(_ data: Range<Int>, content: @escaping (Int) -> Content)
initializer of ForEach
does not allow us to modify the array dynamically. For that, we need to use init(_ data: Data, content: @escaping (Data.Element) -> Content)
initializer as @Asperi explained in the comment. However, it does not work either in this situation, where bindings are nested, as @JacobCXDev clarified in the reply.
Use custom bindings and an additional state. The custom bindings solve the issue of ForEach
over nested bindings. On top of that, you need to modify a state after every touch on the custom binding to re-render the screen. The following is a simplified (untested) code sample.
@State private var xString: String
ForEach(record.nodes) { node in
let xStringBinding = Binding(
get: { node.xString },
set: {
node.xString = $0
self.xString = $0
}
)
TextField("X", text: xStringBinding)
}
Just define a view struct for the children like the following.
ForEach(record.nodes) { node in
NodeView(node: node)
}
struct NodeView: View {
@ObservedObject var node: Node
var body: some View {
TextField("X", text: self.$node.xString)
}
}
class Node: ObservableObject, Identifiable {
@Published var xString: String
let id: String = UUID().uuidString
init(xString: String) {
self.xString = xString
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With