SwiftUI List .onDelete:索引超出范围

Posted

技术标签:

【中文标题】SwiftUI List .onDelete:索引超出范围【英文标题】:SwiftUI List .onDelete: Index out of range 【发布时间】:2021-03-19 17:03:45 【问题描述】:

我有一个使用视图模型填充的简单列表:

@ObservedObject var sdvm = StepDataViewModel()

[...]

List 
    ForEach (vm.steps.indices, id: \.self)  idx in
        TheSlider(value: self.$vm.steps[idx].theValue, index: self.vm.steps[idx].theIndex)
    
    .onDelete(perform:  indexSet in
        self.vm.removeStep(index: indexSet) // <--- here
    )

视图模型在哪里:

class StepDataViewModel: ObservableObject 
    @Published var steps: [StepData] = []

    func removeStep(index: IndexSet) 
        steps.remove(atOffsets: index)
        

StepData 就是这个:

struct StepData: Equatable, Hashable 
    var theIndex: Int
    var theValue: Double

滑块

struct TheSlider: View 
    @Binding var value: Double
    @State   var index: Int

    var body: some View 
        ZStack 
            Slider(value: $value, in: 0...180, step: 1)
                .padding()
            HStack 
                Text("[\(index)]")
                    .font(.body)
                    .fontWeight(.black)
                    .offset(y: -20.0)

                Text("\(Int(value))")
                    .offset(y: -20.0)
            
        
    

现在,.onDelete 显然已附加到列表中,因此当按删除时我收到了正确的行 index。 应用程序因索引越界而崩溃的原因是什么?传递给我索引的列表是否?

我收到此错误:

致命错误:索引超出范围:文件 Swift/ContiguousArrayBuffer.swift,第 444 行

可能是由“TheSlider”中的直接数组引用引起的?如果是,我如何使用theValue 将其更改为Binding 可更新?

【问题讨论】:

为什么需要HashableIdentifiable?如果你要使用\.self,那么你只需要Hashable 这是崩溃的原因吗? 一开始我需要尽可能多地清理代码,所以我问。 我从问题中删除了Identifiable,当然崩溃也是一样的。 让我控制吧,这不难 【参考方案1】:

这是Deleting list elements from SwiftUI's List 中报告的 SwiftUI 错误。

解决方案是使用来自here 的扩展来防止访问无效绑定:

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)
   

List 
    ForEach(vm.steps.indices, id: \.self)  index in
        Safe(self.$vm.steps, index: index)  binding in
            TheSlider(value: binding.theValue, index: vm.steps[index].theIndex)
        
    
    .onDelete(perform:  indexSet in
        self.vm.removeStep(index: indexSet)
    )

【讨论】:

我不知道您是如何获得这个“安全”解决方案的,但它运作良好。我需要学习很多。谢谢!【参考方案2】:

我相信这与 here 解决的问题相同,即 ForEach 循环中的索引与删除的索引之间的一致性丢失。为索引添加一个单独的数组并将其用于迭代和删除,如下所示:

    @ObservedObject var vm = StepDataViewModel()
    @State var indices: [Int] = Array(0..<3)
    
    var body: some View 
        
        List 
            ForEach (indices, id: \.self)  idx in
                TheSlider(value: self.$vm.steps[idx].theValue, index: self.vm.steps[idx].theIndex)
            
            .onDelete(perform:  indexSet in
                indices.remove(atOffsets: indexSet) // <--- here
            )
        

您将需要一些额外的代码来处理模型中相应项目的删除

【讨论】:

以上是关于SwiftUI List .onDelete:索引超出范围的主要内容,如果未能解决你的问题,请参考以下文章

Swiftui foreach 索引超出范围 ondelete 问题

macOS 上的 SwiftUI:如何为 onDelete 启用 UI(从列表中删除)

SwiftUI:有条件的 onDelete

.ondelete 带有部分的 SwiftUI 列表

SwiftUI .onDelete 从 ForEach 获取 var

如何使用 .reversed() 处理 SwiftUI 列表数组的 .onDelete