从 SwiftUI 的列表中删除列表元素 [英] Deleting list elements from SwiftUI's List
问题描述
SwiftUI 似乎有一个相当烦人的限制,这使得在绑定到每个元素以传递给子视图的同时,很难创建 List
或 ForEach
.
SwiftUI seems to have a rather annoying limitation that makes it hard to create a List
or a ForEach
while getting a binding to each element to pass to child views.
我见过的最常建议的方法是迭代索引,并使用 $arr[index]
获取绑定(实际上,类似的东西是 Apple 在删除 Binding
> 符合 Collection
):
The most often suggested approach I've seen is to iterate over indices, and get the binding with $arr[index]
(in fact, something similar was suggested by Apple when they removed Binding
's conformance to Collection
):
@State var arr: [Bool] = [true, true, false]
var body: some View {
List(arr.indices, id: \.self) { index in
Toggle(isOn: self.$arr[index], label: { Text("\(idx)") } )
}
}
这会直到数组的大小发生变化,然后它会因索引超出范围运行时错误而崩溃.
That works until the array changes in size, and then it crashes with index out of range run-time error.
这是一个会崩溃的例子:
Here's an example that will crash:
class ViewModel: ObservableObject {
@Published var arr: [Bool] = [true, true, false]
init() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.arr = []
}
}
}
struct ContentView: View {
@ObservedObject var vm: ViewModel = .init()
var body: some View {
List(vm.arr.indices, id: \.self) { idx in
Toggle(isOn: self.$vm.arr[idx], label: { Text("\(idx)") } )
}
}
}
处理从 List 中删除的正确方法是什么,同时仍然保持使用 Binding 修改其元素的能力?
What's the right way to handle deletion from a List, while still maintaining the ability to modify elements of it with a Binding?
推荐答案
利用来自 @pawello2222 和 @Asperi 的见解,我想出了一种我认为行之有效的方法,而且不会过于讨厌(仍然有点老套).
Using insights from @pawello2222 and @Asperi, I came up with an approach that I think works well, without being overly nasty (still kinda hacky).
我想让该方法更通用,而不仅仅是针对问题中的简化示例,也不是打破关注点分离的方法.
I wanted to make the approach more general than just for the simplified example in the question, and also not one that breaks separation of concerns.
因此,我创建了一个新的包装器视图,该视图创建了到其自身内部的数组元素的绑定(这似乎根据@pawello2222 的观察修复了状态失效/更新顺序),并将绑定作为参数传递给内容闭包.
So, I created a new wrapper view that creates a binding to an array element inside itself (which seems to fix the state invalidation/update ordering as per @pawello2222's observation), and passes the binding as a parameter to the content closure.
我最初预计需要对索引进行安全检查,但事实证明这个问题不需要这样做.
I initially expected to be needing to do safety checks on the index, but turns out it wasn't required for this problem.
struct Safe<T: RandomAccessCollection & MutableCollection, C: View>: View {
typealias BoundElement = Binding<T.Element>
private let binding: BoundElement
private let content: (BoundElement) -> C
init(_ binding: Binding<T>, index: T.Index, @ViewBuilder content: @escaping (BoundElement) -> C) {
self.content = content
self.binding = .init(get: { binding.wrappedValue[index] },
set: { binding.wrappedValue[index] = $0 })
}
var body: some View {
content(binding)
}
}
用法是:
@ObservedObject var vm: ViewModel = .init()
var body: some View {
List(vm.arr.indices, id: \.self) { index in
Safe(self.$vm.arr, index: index) { binding in
Toggle("", isOn: binding)
Divider()
Text(binding.wrappedValue ? "on" : "off")
}
}
}
这篇关于从 SwiftUI 的列表中删除列表元素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!