从 SwiftUI 的列表中删除列表元素

New*_*Dev 11 swift swiftui

SwiftUI 似乎有一个相当烦人的限制,这使得很难创建一个List或一段ForEach时间来绑定到每个元素以传递给子视图。

我见过的最常建议的方法是迭代索引,并获得与的绑定$arr[index](事实上​​,当他们删除的一致性时,Apple 提出了类似的建议):BindingCollection

@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)") } )
   }
}
Run Code Online (Sandbox Code Playgroud)

一直有效,直到数组的大小发生变化,然后它因索引超出范围运行时错误而崩溃。

这是一个会崩溃的例子:

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)") } )
      }
  }
}
Run Code Online (Sandbox Code Playgroud)

处理从 List 中删除的正确方法是什么,同时仍然保持使用 Binding 修改其元素的能力?

New*_*Dev 17

使用来自@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)
   }
}
Run Code Online (Sandbox Code Playgroud)

用法是:

@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")
      }
   }
}
Run Code Online (Sandbox Code Playgroud)