Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/19.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
从SwiftUI'中删除列表元素;s列表_Swift_Swiftui - Fatal编程技术网

从SwiftUI'中删除列表元素;s列表

从SwiftUI'中删除列表元素;s列表,swift,swiftui,Swift,Swiftui,SwiftUI似乎有一个相当恼人的限制,这使得创建列表或ForEach时很难将每个元素的绑定传递给子视图 我见过的最常建议的方法是迭代索引,并使用$arr[index]获得绑定(事实上,当它们删除绑定与集合的一致性时类似): 这将一直工作到数组大小发生变化,然后由于索引超出范围运行时错误而崩溃 下面是一个将崩溃的示例: class ViewModel: ObservableObject { @Published var arr: [Bool] = [true, true, false]

SwiftUI似乎有一个相当恼人的限制,这使得创建
列表
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
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)") })
        }
    }
}

看起来您的
切换
列表
之前被刷新(可能是一个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)
   }
}

利用@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)而不使用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
添加到
ToggleView.body
并删除
使其更加优雅。@Asperi感谢您的建议,使用
@ViewBuilder
@pawello2222肯定会更好,感谢您的回复。Inde