从SwiftUI'中删除列表元素;s列表
SwiftUI似乎有一个相当恼人的限制,这使得创建从SwiftUI'中删除列表元素;s列表,swift,swiftui,Swift,Swiftui,SwiftUI似乎有一个相当恼人的限制,这使得创建列表或ForEach时很难将每个元素的绑定传递给子视图 我见过的最常建议的方法是迭代索引,并使用$arr[index]获得绑定(事实上,当它们删除绑定与集合的一致性时类似): 这将一直工作到数组大小发生变化,然后由于索引超出范围运行时错误而崩溃 下面是一个将崩溃的示例: class ViewModel: ObservableObject { @Published var arr: [Bool] = [true, true, false]
列表
或ForEach
时很难将每个元素的绑定传递给子视图
我见过的最常建议的方法是迭代索引,并使用$arr[index]
获得绑定(事实上,当它们删除绑定与集合的一致性时类似):
这将一直工作到数组大小发生变化,然后由于索引超出范围运行时错误而崩溃
下面是一个将崩溃的示例:
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)") } )
}
}
}
ContentView {
...
@ViewBuilder
func toggleView(forIndex index: Int) -> some View {
if index < vm.arr.count {
Toggle(isOn: $vm.arr[index], label: { Text("\(vm.arr[index].description)") })
}
}
}
处理从列表中删除的正确方法是什么,同时仍然保持使用绑定修改列表元素的能力?SwiftUI 2.0
使用Xcode 12/iOS 14测试-崩溃不可再现
SwiftUI 1.0+
由于挂起到已删除元素的绑定(可能是无效/更新顺序错误的原因),会发生崩溃。
这是一个安全的解决办法。使用Xcode 11.4/iOS 13.4进行测试
struct ContentView: View {
@ObservedObject var vm: ToggleViewModel = .init()
var body: some View {
List(vm.arr.indices, id: \.self, rowContent: row(for:))
}
// helper function to have possibility to generate & inject proxy binding
private func row(for idx: Int) -> some View {
let isOn = Binding(
get: {
// safe getter with bounds validation
idx < self.vm.arr.count ? self.vm.arr[idx] : false
},
set: { self.vm.arr[idx] = $0 }
)
return Toggle(isOn: isOn, label: { Text("\(idx)") } )
}
}
struct ContentView:View{
@ObservedObject变量vm:ToggleViewModel=.init()
var body:一些观点{
列表(vm.arr.index,id:\.self,行内容:行(for:)
}
//帮助函数可以生成和注入代理绑定
私有func行(对于idx:Int)->某些视图{
让我来看看(
获取:{
//带边界验证的安全getter
idx
看起来您的切换开关在列表
之前被刷新(可能是一个bug,在SwiftUI 2.0中修复)
您可以将行提取到另一个视图,并检查索引是否仍然存在
struct ContentView: View {
@ObservedObject var vm: ViewModel = .init()
var body: some View {
List(vm.arr.indices, id: \.self) { index in
ToggleView(vm: self.vm, index: index)
}
}
}
struct ToggleView: View {
@ObservedObject var vm: ViewModel
let index: Int
@ViewBuilder
var body: some View {
if index < vm.arr.count {
Toggle(isOn: $vm.arr[index], label: { Text("\(vm.arr[index].description)") })
}
}
}
利用@pawello2222和@Asperi的见解,我提出了一种我认为效果很好的方法,而且不会太令人讨厌(仍然有点粗糙)
我想让这个方法比问题中的简化示例更通用,也不是打破关注点分离的方法
因此,我创建了一个新的包装器视图,该视图创建了一个到自身内部数组元素的绑定(根据@pawello2222的观察,它似乎修复了状态无效/更新顺序),并将绑定作为参数传递给内容闭包
我最初预计需要对索引进行安全检查,但结果表明,这个问题并不需要这样做
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)
}
}
使用Xcode 12/iOS时没有崩溃14@Asperi-有趣。谢谢你的发现。不确定这是否是苹果的故意修复还是其他有趣的事情。如果您使用List
它不会崩溃,但是如果您用具有相同签名的ForEach
替换List
,它会崩溃(xCode 12 Beta 5)太棒了!通过将@ViewBuilder
添加到ToggleView.body
并删除组
使其更加优雅。@Asperi感谢您的建议,使用@ViewBuilder
@pawello2222肯定会更好,感谢您的回复。事实上,似乎有什么东西被评估出了问题。您的解决方案适用于此示例,但在理想情况下,最好不要打破关注点的分离,因为ToggleView
在理想情况下不应该了解其父视图模型的任何信息。@NewDev True,幸运的是它在SwiftUI 2.0中得到了修复。看起来不错。我认为您只需要将@ViewBuilder内容
参数转义:@escaping(BoundElement)->C
。您的Safe.init
需要为第一个参数添加一个“binding”标签-在您的示例中没有(Safe(self.$vm.arr,…
)。我不知道这是如何工作的,也不知道为什么需要它,但它帮助我解决了索引超出范围的问题。这很好。正如其他评论中所指出的,我只在使用ForEach
时遇到了这个问题,并且在最新版本中似乎解决了List
的问题。但是,在许多情况下,List
是错误的作业的组件。将使用此解决方案,直到Apple解决此问题。
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")
}
}
}