Until now, to setup bindings between the elements in a dynamic collection and the rows of a List we had to do like that :
List(Array(zip(data.indices, data)), id: \.1.id) { index, _ in
HStack {
Text((index + 1).description)
TextField("", text: Binding(
get: { data[index].text },
set: { data[index].text = $0 }
))
}
}
We need : the index of the element for the binding ; + the element identifier for the List
(to avoid weird animations) ; and a custom Binding
to avoid a crash when deleting the last row.
It's complicated (and I'm not sure it's very efficient). Since WWDC21, we have a new syntax (which can be back-deployed):
List($data) { $item in
HStack {
Text("Index ?")
TextField("", text: $item.text)
}
}
It's cleaner.
But while it is strongly recommended to use this new syntax, it would be nice to be able to access the element's index in the closure. Do you know how we can do it?
I tried this (it works), but I feel like it's not the right way to do it :
let d = Binding(get: {
Array(data.enumerated())
}, set: {
data = $0.map {$0.1}
})
List(d, id: \.1.id) { $item in
HStack {
Text("\(item.0 + 1)")
TextField("", text: $item.1.text)
}
}
You can build wrapper by yourself:
struct ListIndexed<Content: View>: View {
let list: List<Never, Content>
init<Data: MutableCollection&RandomAccessCollection, RowContent: View>(
_ data: Binding<Data>,
@ViewBuilder rowContent: @escaping (Data.Index, Binding<Data.Element>) -> RowContent
) where Content == ForEach<[(Data.Index, Data.Element)], Data.Element.ID, RowContent>,
Data.Element : Identifiable,
Data.Index : Hashable
{
list = List {
ForEach(
Array(zip(data.wrappedValue.indices, data.wrappedValue)),
id: \.1.id
) { i, _ in
rowContent(i, Binding(get: { data.wrappedValue[i] }, set: { data.wrappedValue[i] = $0 }))
}
}
}
var body: some View {
list
}
}
Usage:
ListIndexed($items) { i, $item in
HStack {
Text("Index \(i)")
TextField("", text: $item.text)
}
}