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