SwiftUI中Firebase数据仅二次打开页面才显示的问题求助
SwiftUI Firebase数据加载延迟问题修复
问题现象
- 首次打开
MasteredCategoryView时,页面显示空状态 - 切换到其他页面再返回
MasteredCategoryView,数据才会正常展示 - 另一页面
StatsView逻辑类似,但数据能正常加载展示
核心原因
MasteredCategoryView的getMasteredQuestionsArray函数中,过滤数据的代码写在了异步回调外面。Firebase数据获取是异步操作,回调执行前masteredQuestions还是空数组,导致过滤逻辑提前执行,filteredMasteredQuestions始终为空。而第二次进入页面时,masteredQuestions已经缓存了数据,过滤逻辑才能正确执行。
另外还有两个潜在问题:
QuestionModel中getMasteredQuestionsArray使用了addSnapshotListener(实时监听),但当前场景只需要一次性获取数据,应该用getDocuments避免不必要的监听更新MasteredCategoryView的ForEach中额外加了item.category == selectedCategory判断,完全可以在过滤时直接处理,避免冗余判断
修复步骤
1. 修正QuestionModel的getMasteredQuestionsArray(改用一次性获取)
把addSnapshotListener换成getDocuments,改用DispatchGroup确保所有异步请求完成后再返回结果:
func getMasteredQuestionsArray(completion: @escaping ([Question]) -> Void) { guard let user = Auth.auth().currentUser else { // 处理未登录错误 completion([]) return } var masteredQuestions = [Question]() let db = Firestore.firestore() db.collection("users").document(user.uid) .collection("mastered_questions") .getDocuments { querySnapshot, error in if let error = error { print("DEBUG: 获取已掌握题目ID失败: \(error.localizedDescription)") completion([]) return } guard let docs = querySnapshot?.documents else { completion([]) return } let group = DispatchGroup() for document in docs { group.enter() let questionId = document.data()["question_id"] as! String db.collection("questions").document(questionId) .getDocument { questionSnapshot, error in defer { group.leave() } if let error = error { print("DEBUG: 获取题目详情失败: \(error.localizedDescription)") return } guard let questionData = questionSnapshot?.data() else { return } let masteredQuestionObject = Question( id: questionSnapshot!.documentID, category: questionData["category"] as? String ?? "", question: questionData["question"] as? String ?? "", type: questionData["type"] as? String ?? "", timestamp: questionData["timestamp"] as? Date ?? Date() ) masteredQuestions.append(masteredQuestionObject) } } group.notify(queue: .main) { completion(masteredQuestions) } } }
2. 修正MasteredCategoryView的加载逻辑
把过滤逻辑移到异步回调内部,去掉冗余状态变量:
struct MasteredCategoryView: View { @StateObject var questionModel = QuestionModel() @State var filteredMasteredQuestions = [Question]() let selectedCategory: String // 外部传入参数,无需@State var body: some View { VStack { ScrollView(.vertical, showsIndicators: false) { if filteredMasteredQuestions.isEmpty { VStack{ Spacer() Image("empty_state") .resizable() .scaledToFit() .ignoresSafeArea(.all, edges: .top) .padding(.horizontal, 30) Text("这里空空如也") .foregroundColor(.text_900) .font(.system(size: 28, design: .rounded)) .fontWeight(.medium) .multilineTextAlignment(.center) .padding(.horizontal, 20) Text("你掌握的题目会显示在这里") .padding(.top, 1) .foregroundColor(.text_700) .font(.system(size: 19, design: .rounded)) .fontWeight(.medium) .multilineTextAlignment(.center) .padding(.horizontal, 20) Spacer() } } else { ForEach(filteredMasteredQuestions) { item in QuestionForAnalyticsView(question: item.question, id: item.id) } } } .padding(.top, 20) Spacer() } .onAppear(perform: loadAndFilterQuestions) } func loadAndFilterQuestions() { questionModel.getMasteredQuestionsArray { masteredItems in // 直接过滤当前分类的题目 self.filteredMasteredQuestions = masteredItems.filter { $0.category == self.selectedCategory } print("DEBUG: 过滤后题目数量: \(filteredMasteredQuestions.count)") } } }
3. 实时监听优化(可选)
如果需要实时同步已掌握题目的变化,可保留监听并管理生命周期:
// 在QuestionModel中新增监听方法 func listenToMasteredQuestions(completion: @escaping ([Question]) -> Void) -> ListenerRegistration { guard let user = Auth.auth().currentUser else { completion([]) return ListenerRegistration() } let db = Firestore.firestore() return db.collection("users").document(user.uid) .collection("mastered_questions") .addSnapshotListener { querySnapshot, error in // 内部逻辑与getMasteredQuestionsArray一致,最终调用completion } } // 在MasteredCategoryView中管理监听 @State private var listener: ListenerRegistration? .onAppear { listener = questionModel.listenToMasteredQuestions { masteredItems in filteredMasteredQuestions = masteredItems.filter { $0.category == selectedCategory } } } .onDisappear { listener?.remove() }
为什么StatsView能正常工作?
因为StatsView的fetchMasteredQuestions是在getMasteredQuestionsArray的异步回调内部调用的,确保了数据加载完成后才更新statsForMasteredQuestions状态,触发视图刷新。
内容的提问来源于stack exchange,提问作者Stepan
相关产品推荐
相关产品推荐

