如何获取动态 List/ForEach 可绑定元素的索引(新的 Xcode 13 语法)?

人气:310 发布:2022-10-16 标签: swiftui swift swiftui-list swiftui-foreach xcode13

问题描述

到目前为止,要在动态集合中的元素和 List 的行之间设置绑定,我们必须这样做:

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 }
        ))
    }
}

我们需要:绑定元素的索引;+ List 的元素标识符(以避免奇怪的动画);和自定义 Binding 以避免在删除最后一行时崩溃.

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.

这很复杂(我不确定它是否非常有效).从 WWDC21 开始,我们有了新的语法(可以向后部署):

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)
    }
}

更干净.

虽然强烈建议使用这种新语法,但如果能够访问闭包中元素的索引,那就太好了.你知道我们怎么做吗?

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
    }
}

用法:

ListIndexed($items) { i, $item in
    HStack {
        Text("Index \(i)")
        TextField("", text: $item.text)
    }
}

355