索引超出范围的 SwiftUI 问题

Posted

技术标签:

【中文标题】索引超出范围的 SwiftUI 问题【英文标题】:SwiftUI trouble with index out of range 【发布时间】:2021-03-15 21:18:51 【问题描述】:

我知道这是一个非常简单的问题,但我只是坚持使用 atm,所以任何建议都将不胜感激,因为我是 SwiftUI 的新手。

我正在尝试从 firebase 下载文本并将其渲染到视图中,但我不断收到超出范围的错误:

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

代码如下:

var body: some View
    
    ZStack 
        
        if fetch.loading == false 
            LoadingView()
        
        
        else
            Text(names[0])
                .bold()
        
    
    .onAppear 
        self.fetch.longTask()
    
    

这是获取内容页面:

@Published var loading = false

func longTask() 

    DispatchQueue.main.asyncAfter(deadline: .now()) 
        
        let db = Firestore.firestore()
        db.collection("Flipside").getDocuments  (snapshot, err) in
            if let err = err 
                print("Error getting documents: \(err)")
                
                return
             else 
                for document in snapshot!.documents 
                    let name = document.get("Name") as! String
                    let description = document.get("Description") as! String
                    //name = items[doc]
                    print("Names: ", name)
                    print("Descriptions: ", description)
                    names.append(name)
                    descriptions.append(description)
                
            
        
        self.loading = true
    

所以基本上当视图出现时,从 Firebase 获取数据,当数据下载后显示 menuPage() 直到然后显示 Loading Data 文本。

欢迎任何帮助!

【问题讨论】:

这不会解决您的问题,但仅供参考,您的 completed() 调用将在 Firebase 调用完成之前发生,因为 getDocuments 是一个异步函数。您需要在 getDocuments 闭包中调用 completed 以使其表现得像您正在寻找的那样。 @jnpdx 非常感谢您的回复!我现在补充一下!你知道我还需要做什么才能让它按预期工作吗? 【参考方案1】:

正如 Rob Napier 提到的,问题是您在填充数组之前访问数组索引。

我建议对您的代码进行一些改进。此外,您可以创建一个结构来将所有属性保存在一个位置,而不是维护单独的数组(namesdescriptions、...)。这将允许您只为您的项目使用一个数组。

struct Item 
    let name: String
    let description: String

class Fetch: ObservableObject 
    @Published var items: [Item] = [] // a single array to hold your items, empty at the beginning
    @Published var loading = false // indicates whether loading is in progress

    func longTask() 
        loading = true // start fetching, set to true
        let db = Firestore.firestore()
        db.collection("Flipside").getDocuments  snapshot, err in
            if let err = err 
                print("Error getting documents: \(err)")
                DispatchQueue.main.async 
                    self.loading = false // loading finished
                
             else 
                let items = snapshot!.documents.map  document in // use `map` to replace `snapshot!.documents` with an array of `Item` objects
                    let name = document.get("Name") as! String
                    let description = document.get("Description") as! String
                    print("Names: ", name)
                    print("Descriptions: ", description)
                    return Item(name: name, description: description)
                
                DispatchQueue.main.async  // perform assignments on the main thread
                    self.items = items
                    self.loading = false // loading finished
                
            
        
    

struct ContentView: View 
    @StateObject private var fetch = Fetch() // use `@StateObject` in ios 14+

    var body: some View 
        ZStack 
            if fetch.loading  // when items are being loaded, display `LoadingView`
                LoadingView()
             else if fetch.items.isEmpty  // if items are loaded empty or there was an error
                Text("No items")
             else  // items are loaded and there's at least one item
                Text(fetch.items[0].name)
                    .bold()
            
        
        .onAppear 
            self.fetch.longTask()
        
    

请注意,可能不需要通过下标访问数组。如果只有一项并且您尝试访问 items[1],您的代码仍然会失败。

相反,您可能可以使用first 来访问第一个元素:

ZStack 
    if fetch.loading 
        LoadingView()
     else if let item = fetch.items.first 
        Text(item.name)
            .bold()
     else 
        Text("Items are empty")
    

或使用ForEach 显示所有项目:

ZStack 
    if fetch.loading 
        LoadingView()
     else if fetch.items.isEmpty 
        Text("Items are empty")
     else 
        VStack 
            ForEach(fetch.items, id: \.name)  item in
                Text(item.name)
                    .bold()
            
        
    

此外,如果可能,请避免强制展开选项。如果snapshot == nil,代码snapshot!.documents 将终止您的应用程序。此答案中提供了许多有用的解决方案:

What does “Fatal error: Unexpectedly found nil while unwrapping an Optional value” mean?

【讨论】:

太棒了,非常感谢!清晰易懂!【参考方案2】:

基本问题是您在names 数组被填充之前评估names[0]。如果数组为空,那么您会看到此崩溃。您可能想要的是:

Item(title: names.first ?? "", ...)

您过早评估names[0] 的原因是您在获取实际完成之前调用了completed。您正在与初始方法调用同步调用它。

也就是说,您必须始终考虑存在连接错误或数据为空或数据已损坏的情况。一般来说,你应该避免下标数组(更喜欢.first之类的东西),当你下标数组时,你必须首先确保你知道有多少元素。

【讨论】:

感谢您的回复!我真的很感激!如何确保在获取完成之前未呈现视图并且未填充数组? 首先,将completed() 移动到仅在获取完成后调用(它需要在完成处理程序中,在您设置名称之后)。除此之外,这取决于你的代码的其余部分,但如果这就是一切,那么这就足够了。 您好,我现在尝试了几种不同的方法,但似乎仍然无法正常工作。你能告诉我我做错了什么吗? 我们需要看看您尝试了什么,以及“似乎无法正常工作”是什么意思。当前代码是什么,既然您已经移动了 completed() 调用并删除了 names[0]?什么是新错误(不太可能完全相同)。如果有更多信息,请更新您的问题。 在完成处理程序中else 块中的for 循环之后到getDocuments。在这段代码中添加print() 语句并查看它何时运行。它没有按照您认为的顺序运行。 (我的怀疑是您不了解完成处理程序的工作原理,但不清楚。)

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

SwiftUI - 索引超出范围

SwiftUI:JSON 索引总是超出范围

SwiftUI 网格索引超出范围

索引超出范围 SwiftUI

SwiftUI:从 ForEach 中删除项目导致索引超出范围

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