索引超出范围的 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 提到的,问题是您在填充数组之前访问数组索引。
我建议对您的代码进行一些改进。此外,您可以创建一个结构来将所有属性保存在一个位置,而不是维护单独的数组(names
、descriptions
、...)。这将允许您只为您的项目使用一个数组。
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
将终止您的应用程序。此答案中提供了许多有用的解决方案:
【讨论】:
太棒了,非常感谢!清晰易懂!【参考方案2】:基本问题是您在names
数组被填充之前评估names[0]
。如果数组为空,那么您会看到此崩溃。您可能想要的是:
Item(title: names.first ?? "", ...)
您过早评估names[0]
的原因是您在获取实际完成之前调用了completed
。您正在与初始方法调用同步调用它。
也就是说,您必须始终考虑存在连接错误或数据为空或数据已损坏的情况。一般来说,你应该避免下标数组(更喜欢.first
之类的东西),当你下标数组时,你必须首先确保你知道有多少元素。
【讨论】:
感谢您的回复!我真的很感激!如何确保在获取完成之前未呈现视图并且未填充数组? 首先,将completed()
移动到仅在获取完成后调用(它需要在完成处理程序中,在您设置名称之后)。除此之外,这取决于你的代码的其余部分,但如果这就是一切,那么这就足够了。
您好,我现在尝试了几种不同的方法,但似乎仍然无法正常工作。你能告诉我我做错了什么吗?
我们需要看看您尝试了什么,以及“似乎无法正常工作”是什么意思。当前代码是什么,既然您已经移动了 completed()
调用并删除了 names[0]
?什么是新错误(不太可能完全相同)。如果有更多信息,请更新您的问题。
在完成处理程序中else
块中的for
循环之后到getDocuments
。在这段代码中添加print()
语句并查看它何时运行。它没有按照您认为的顺序运行。 (我的怀疑是您不了解完成处理程序的工作原理,但不清楚。)以上是关于索引超出范围的 SwiftUI 问题的主要内容,如果未能解决你的问题,请参考以下文章