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

Posted

技术标签:

【中文标题】表单中的 SwiftUI 选择器 - 索引超出范围【英文标题】:SwiftUI Picker in Form - Index out of Range 【发布时间】:2020-07-29 18:00:04 【问题描述】:

当我尝试在 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[$0]).tag($0)
                        
                    
                    //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[$0]).tag($0)
                        
                    
                    //Text(allVM[vmIndex])
                
            
        .onAppear 
            self.stockViewModel.retrieveAllVM()
        
    

【问题讨论】:

【参考方案1】:

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
    

【讨论】:

结果好像还是一样。 我尝试了前 3 个解决方案,最后一个解决方案不适用于我,因为我没有使用 SwiftUI 2.0。 我应用了您的解决方案,但结果仍然相同。我打印了从数组中获得的数据 --> ["Bukit Jalil", "Sri Petaling"],我认为该数组没有任何问题。 @BingoLingo 您的视图或数组本身没有问题。我在没有 firebase 的情况下运行了您的代码,并且运行良好。一般规则是将@Published 属性的每次更新都包含在DispatchQueue.main.async 中(如果它正在后台更新)。最好不要依赖 ViewModel 初始化 - SwiftUI 可能会刷新视图并重新加载您的 ViewModel - 这将导致 retrieveAllVM 触发。在视图中使用.onAppear 我使用 .onAppear 来调用 func 而不是 init() 但结果是一样的。

以上是关于表单中的 SwiftUI 选择器 - 索引超出范围的主要内容,如果未能解决你的问题,请参考以下文章

ForEach 中的 SwiftUI 索引超出范围

在 SwiftUI 中符合 RandomAccessCollection 时,下标中的索引超出范围

SwiftUI .onDelete 抛出致命错误:索引超出范围

索引超出范围 SwiftUI

SwiftUI - 索引超出范围

索引超出范围的 SwiftUI 问题