表单中的 SwiftUI 选择器 - 索引超出范围

SwiftUI Picker in Form - Index out of Range

当我尝试在 Text() 中显示选择器的选定数据时,发生了一个名为“索引超出范围”的错误。

但是,当我评论显示所选数据的 Text() 时,它工作正常。以下是表单中选择器的代码。

struct VMPickerView: View {
    
    @State var vmIndex = 0
    @ObservedObject var stockViewModel = StockViewModel()

    var body: some View {
        
        let allVM = self.stockViewModel.arrKey

        return VStack {
        
            Form {
                
                Section {
                    
                    Picker(selection: $vmIndex, label: Text("Location")) {
                        
                        ForEach(0..<allVM.count, id: \.self) {
                            
                            Text(allVM[[=10=]]).tag([=10=])
                        }
                    }
                    //Text(allVM[vmIndex])
                }
            }
        }
    }
}

下面是我评论“Text(allVM[vmIndex])”时我的应用程序的图像

下面是我用来从 firebase 检索数据并将其存储到数组中的代码。

class StockViewModel: ObservableObject {
    
    @Published var itemList = [ItemList]()
    @Published var arrKey = [String]()
    
    init() {
        retrieveAllVM()
    }
    
    func retrieveAllVM() {
        
        var arrKey = [String]()
       
        let ref = Database.database().reference().child("VM")

        ref.observeSingleEvent(of: .value, with: { snapshot in
            for items in snapshot.children {
                let itemSnap = items as! DataSnapshot
                let allKey = itemSnap.key
                arrKey.append(allKey)
            }
            self.arrKey = arrKey
            print(self.arrKey)
       })
    }
}

*我修改后的代码:

class StockViewModel: ObservableObject {
    
    @Published var itemList = [ItemList]()
    @Published var arrKey = [String]()
    
    func retrieveAllVM() {
        
        var arrKey = [String]()
       
        let ref = Database.database().reference().child("VM")

        ref.observeSingleEvent(of: .value, with: { snapshot in
            for items in snapshot.children {
                let itemSnap = items as! DataSnapshot
                let allKey = itemSnap.key
                arrKey.append(allKey)
            }
            DispatchQueue.main.async {
                self.arrKey = arrKey
                print(self.arrKey)
            }
            //self.arrKey = arrKey
       })
    }
}
struct VMPickerView: View {
    
    @State var vmIndex = 0
    @ObservedObject var stockViewModel: StockViewModel

    var body: some View {
        
        let allVM = self.stockViewModel.arrKey

        return VStack {
        
            Form {
                
                Section {
                    
                    Picker(selection: $vmIndex, label: Text("Location")) {
                        
                        ForEach(0..<allVM.count, id: \.self) {
                            
                            Text(allVM[[=13=]]).tag([=13=])
                        }
                    }
                    //Text(allVM[vmIndex])
                }
            }
        }.onAppear {
            self.stockViewModel.retrieveAllVM()
        }
    }
}

observeSingleEvent 方法看起来是 异步的 。确保在主线程上更新 @Published 属性。

替换:

self.arrKey = arrKey

与:

DispatchQueue.main.async {
    self.arrKey = arrKey
}

每次创建 ViewModel 时,您在 init 中的代码都会 运行。

class StockViewModel: ObservableObject {
    ...
    init() {
        retrieveAllVM()
    }

您可以将调用 retrieveAllVM 移动到 .onAppear:

struct VMPickerView: View {
    @State var vmIndex = 0
    @ObservedObject var stockViewModel = StockViewModel()

    var body: some View {
        let allVM = self.stockViewModel.arrKey

        return VStack {
            ...
        }.onAppear {
            self.stockViewModel.retrieveAllVM()
        }
    }
}

或者不要直接在 VMPickerView 中创建 ViewModel。在父视图中创建 ViewModel 并将其传递给 VMPickerView:

struct VMPickerView: View {
    @State var vmIndex = 0
    @ObservedObject var stockViewModel: StockViewModel // pass only
    ...
}

或者,如果您使用的是 SwiftUI 2.0,则可以使用 @StateObject:

struct VMPickerView: View {
    @State var vmIndex = 0
    @StateObject var stockViewModel = StockViewModel()
    ...
}

编辑

处理指数是有风险的。如果由于任何其他原因您的代码失败,请尝试使用 ifguard 语句以确保您永远不会访问无效索引。

而不是:

Form {
    Section {
        ...
        Text(allVM[vmIndex])
    }
}

您可以在选择器视图中添加计算的 属性 返回当前键视图:

@ViewBuilder
var currentKeyText: some View {
    if vmIndex < stockViewModel.arrKey.count {
        Text(stockViewModel.arrKey[vmIndex])
    }
}

并像这样访问它:

Form {
    Section {
        ...
        currentKeyText
    }
}