You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中Firebase数据仅二次打开页面才显示的问题求助

SwiftUI Firebase数据加载延迟问题修复

问题现象

  • 首次打开MasteredCategoryView时,页面显示空状态
  • 切换到其他页面再返回MasteredCategoryView,数据才会正常展示
  • 另一页面StatsView逻辑类似,但数据能正常加载展示

核心原因

MasteredCategoryView的getMasteredQuestionsArray函数中,过滤数据的代码写在了异步回调外面。Firebase数据获取是异步操作,回调执行前masteredQuestions还是空数组,导致过滤逻辑提前执行,filteredMasteredQuestions始终为空。而第二次进入页面时,masteredQuestions已经缓存了数据,过滤逻辑才能正确执行。

另外还有两个潜在问题:

  1. QuestionModel中getMasteredQuestionsArray使用了addSnapshotListener(实时监听),但当前场景只需要一次性获取数据,应该用getDocuments避免不必要的监听更新
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 09:47:07