表格中的SwiftUI Picker-索引超出范围

时间:2020-07-29 18:00:04

标签: firebase-realtime-database swiftui picker swiftui-form

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

Image of error message

但是,当我注释显示所选数据的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[$0]).tag($0)
                        }
                    }
                    //Text(allVM[vmIndex])
                }
            }
        }
    }
}

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

Screen 1

Screen 2

下面是我用来从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[$0]).tag($0)
                        }
                    }
                    //Text(allVM[vmIndex])
                }
            }
        }.onAppear {
            self.stockViewModel.retrieveAllVM()
        }
    }
}

1 个答案:

答案 0 :(得分:0)

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

替换:

self.arrKey = arrKey

具有:

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

您的init代码将在每次创建ViewModel时运行。

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
    }
}