I have a complex data structure which uses value types (structs and enums), and I'm facing major issues getting basic CRUD to work. Specifically:
- How best to "Re-bind" a value in a ForEach for editing by a child view
- How to remove/delete a value
Rebinding
If I have an array of items as @State
or @Binding
, why isn't there a simple way to bind each element to a view? For example:
import SwiftUI
struct Item: Identifiable {
var id = UUID()
var name: String
}
struct ContentView: View {
@State var items: [Item]
var body: some View {
VStack {
ForEach(items, id: \.id) { item in
TextField("name", text: $item) // Cannot find '$item' in scope
}
}
}
}
Workaround
I've been able to work around this by introducing a helper function to find the correct index for the item within a loop:
struct ContentView: View {
@State var items: [Item]
func index(of item: Item) -> Int {
items.firstIndex { $0.id == item.id } ?? -1
}
var body: some View {
VStack {
ForEach(items, id: \.id) { item in
TextField("name", text: $items[index(of: item)].name)
}
}
}
}
However, that feels clunky and possibly dangerous.
Deletion
A far bigger issue: how are you supposed to correctly delete an element? This sounds like such a basic question, but consider the following:
struct ContentView: View {
@State var items: [Item]
func index(of item: Item) -> Int {
items.firstIndex { $0.id == item.id } ?? -1
}
var body: some View {
VStack {
ForEach(items, id: \.id) { item in
TextField("name", text: $items[index(of: item)].name)
Button( action: {
items.remove(at: index(of: item))
}) {
Text("Delete")
}
}
}
}
}
Clicking the "Delete" button on the first few items works as expected, but trying to Delete the last item results in Fatal error: Index out of range
...
My particular use case doesn't map to a List, so I can't use the deletion helper there.
Reference types
I know that reference types make much of this easier, especially if they can conform to @ObservableObject
. However, I have a massive, nested, pre-existing value type which is not easily converted to classes.
Any help would be most appreciated!
Update: Suggested solutions
- Deleting List Elements from SwiftUI's list: The accepted answer proposes a complex custom binding wrapper. Swift is powerful, so it's possible to solve many problems with elaborate workarounds, but I don't feel like an elaborate workaround should be necessary to have a list of editable items.
- Mark Views as "deleted" using State or a private variable, then conditionally hide them, to avoid out-of-bounds errors. This can work, but feels like a hack, and something that should be handled by the framework.