【问题标题】:How to get the index of a dynamic List / ForEach bindable element (new Xcode 13's syntax)?如何获取动态 List / ForEach 可绑定元素的索引(新 Xcode 13 的语法)?
【发布时间】:2021-10-21 08:42:32
【问题描述】:

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

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 以避免在删除最后一行时崩溃。

这很复杂(我不确定它是否非常有效)。 自 WWDC21 以来,我们有了新的语法(可以反向部署):

List($data) { $item in
    HStack {
        Text("Index ?")
        TextField("", text: $item.text)
    }
}

它更干净。

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

编辑:

我试过这个(它有效),但我觉得这不是正确的做法:

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

【问题讨论】:

    标签: swift swiftui swiftui-list xcode13 swiftui-foreach


    【解决方案1】:

    您可以自己构建包装器:

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

    【讨论】:

    • 谢谢@Philip。这是个好主意。但目前,如果您尝试删除最后一行,则使用您的包装器时应用程序会崩溃。
    • 您可以将data[i] 替换为Binding(get: { data.wrappedValue[i] }, set: { data.wrappedValue[i] = $0 }).projectedValue 来解决问题。但我不确定这是否是最清晰的方法以及它如何影响性能。
    • @Adrien 哦,所以我的包装器变得更丑了:D 我不认为这会导致严重的性能问题,从数组读取是 O(1)。我认为新的List($data) 的源代码也应该有类似的东西=)还有你为什么叫projectedValue? AFAIK 只需要“$”就可以工作,只需对我初始化就可以正常工作
    • 您对projectedValue 的看法是正确的。你也对这个编辑有多丑陋是正确的。但除了创建这些自定义绑定实例之外,我不知道有任何其他方法可以避免这种崩溃并出现越界错误。
    • 太棒了。这将非常有用,?
    【解决方案2】:

    如果您的项目是 Equatable,您可以使用 firstIndex(of:) 获取它们的索引:

    List($data) { $item in
        HStack {
            Text("Index \(data.firstIndex(of: item)! + 1)")
            TextField("", text: $item.text)
        }
    }
    

    【讨论】:

    • Items不需要是Equatable,我们可以使用firstIndex (where: {$0.id == id})但是firstIndex的复杂度是O(n) (n = length),所以O(n ^ 2)为整个 ForEach。我们也许能找到更好的。
    猜你喜欢
    • 2015-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-12
    • 1970-01-01
    • 1970-01-01
    • 2017-02-23
    • 1970-01-01
    相关资源
    最近更新 更多