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

Xcode iOS应用Firestore数据无法在iPhone历史页显示求助

问题排查:Firestore数据加载后SwiftUI列表不显示内容

问题描述

我是iOS开发新手,使用Swift在Xcode中开发应用,预期功能为:扫描Firestore中known_UPC集合内的UPC条码后,该产品的UPC会显示在HistoryView的历史列表中。实际测试时,扫描条码后ProductDetailView可正常展示,但返回HistoryView后,历史列表无内容。日志显示已成功获取UPC及产品名称,且代码已进入显示列表的分支,却无法显示UPC。

HistoryView代码

//
//  HistoryView.swift
//

import SwiftUI
import FirebaseAuth
import Firebase
import FirebaseFirestore

struct HistoryView: View {
    
    @Binding var isBarcodeScannerOpen: Bool
    @State var isProductDetailOpen = false
    @State var scannedBarcodeDocumentID: String = ""
    @State var scannedBarcode: String = ""
    @State var userData: [BarcodeData] = [] // Data from Firestore
    @State private var showScanFirstProductScreen = true
    @State private var dataLoaded = false
    
    struct BarcodeData: Identifiable {
        let id = UUID()
        let UPC: String
        let productName: String
    }
    
    
    func fetchDataFromFirestore() {
        // Update so after we check if the user exists, we check if there are any scanned barcode documents that exist. If no, show a blank screen with "hello message"
        guard let user = Auth.auth().currentUser else { return }

        let db = Firestore.firestore()
        let usersCollection = db.collection("users")

        usersCollection.document(user.uid).getDocument { document, error in
            if let document = document, document.exists {
                if let barcodeScanned = document.data()?["barcode_scanned"] as? [[String: Any]] {
                    var tempUserData: [BarcodeData] = []
                    
                    dataLoaded = true
                    
                    for data in barcodeScanned {
                        if let UPC = data["UPC"] as? String {
                            // Fetch product name from known_UPC collection
                            db.collection("known_UPC").document(UPC).getDocument {
                                knownDocument, knownError in
                                DispatchQueue.main.async {
                                    print("UPC in problem area is", UPC)
                                    if let knownDocument = knownDocument, knownDocument.exists {
                                        if let productName = knownDocument.data()?["product_name"] as? String {
                                            showScanFirstProductScreen = false
                                            tempUserData.append(BarcodeData(UPC: UPC, productName: productName))
                                            print("productName in problem area is", productName)
                                            print("data loaded status in problem area is", dataLoaded)
                                        }
                                    } else {
                                        showScanFirstProductScreen = false
                                        print("This product is not in the known_UPC collection, UPC is", UPC)
                                        print("data loaded status in else statement of problem area is", dataLoaded)
                                    }
                                }
                            }
                        }
                    }
                    userData = tempUserData
                }
            } else {
                dataLoaded = true
                print("User does not exist")
                print("data loaded status where user doesn't exist is", dataLoaded)
            }
        }
    }
    
    func barcodeScanned(barcode: String) {
        scannedBarcodeDocumentID = storeBarcode(barcode: barcode)
        isBarcodeScannerOpen = false
        scannedBarcode = barcode
        self.isProductDetailOpen = true
    }
    
    var body: some View {
        NavigationView {
            ZStack(alignment: .bottom){
                Color(red: 241, green: 242, blue: 248).ignoresSafeArea()
                NavigationLink(destination: CameraControllerView(isPresented: $isBarcodeScannerOpen, barcode: barcodeScanned(barcode:)), isActive: $isBarcodeScannerOpen) {
                    EmptyView()
                }
                
                NavigationLink(destination: ProductDetailView(scannedBarcodeDocumentID: scannedBarcodeDocumentID, barcode: scannedBarcode), isActive: $isProductDetailOpen) {
                    EmptyView()
                }
                
                VStack {
                    if showScanFirstProductScreen {
                        Text("Scan your first product")
                            .font(.title)
                            .foregroundColor(.black)
                    } else {
                        if dataLoaded {
                            List(userData) { data in
                                Text(data.UPC)
                            }
                            .listStyle(PlainListStyle())
                            .background(Color.white)
                            
                        } else {
                            //ProgressView("Loading...")
                            Text("Data loaded status in else statement of vstack is false")
                                .font(.title)
                                .foregroundColor(.black)
                        }
                    }
                }
                Spacer().frame(height: 20)
            }
            .onAppear {
                fetchDataFromFirestore()
            }
            }
        
    }
}

struct HistoryView_Previews: PreviewProvider {
    static var previews: some View {
        HistoryView(isBarcodeScannerOpen: .constant(false))
    }
}

测试日志

UPC in problem area is 0851557003132

productName in problem area is Algae Renew Deep Conditioning Mask

data loaded status in problem area is true

问题根源

  1. 异步请求时序错误:db.collection("known_UPC").document(UPC).getDocument是异步操作,循环发起请求后立刻执行userData = tempUserData,此时异步请求尚未完成,tempUserData为空,导致列表无数据。
  2. 数据更新未触发视图刷新:异步请求完成后往tempUserData添加数据时,userData已经被赋值为空数组,且未重新给userData赋值,SwiftUI无法感知数据变化。
  3. 返回HistoryView时未重新加载数据:默认情况下,从ProductDetailView返回时,HistoryView的onAppear不会触发,导致无法获取最新的扫描记录。

修复方案

1. 修正Firestore数据加载逻辑(用DispatchGroup等待异步请求完成)

修改fetchDataFromFirestore函数,确保所有异步请求完成后再更新userData:

func fetchDataFromFirestore() {
    guard let user = Auth.auth().currentUser else { return }

    let db = Firestore.firestore()
    let usersCollection = db.collection("users")

    usersCollection.document(user.uid).getDocument { document, error in
        if let document = document, document.exists {
            if let barcodeScanned = document.data()?["barcode_scanned"] as? [[String: Any]] {
                var tempUserData: [BarcodeData] = []
                let dispatchGroup = DispatchGroup() // 创建调度组
                
                self.dataLoaded = false
                self.showScanFirstProductScreen = true

                for data in barcodeScanned {
                    if let UPC = data["UPC"] as? String {
                        dispatchGroup.enter() // 标记任务开始
                        db.collection("known_UPC").document(UPC).getDocument { knownDocument, knownError in
                            defer { dispatchGroup.leave() } // 标记任务结束
                            DispatchQueue.main.async {
                                print("UPC in problem area is", UPC)
                                if let knownDocument = knownDocument, knownDocument.exists {
                                    if let productName = knownDocument.data()?["product_name"] as? String {
                                        tempUserData.append(BarcodeData(UPC: UPC, productName: productName))
                                        print("productName in problem area is", productName)
                                    }
                                } else {
                                    print("This product is not in the known_UPC collection, UPC is", UPC)
                                }
                            }
                        }
                    }
                }
                
                // 所有异步请求完成后统一更新数据
                dispatchGroup.notify(queue: .main) {
                    self.userData = tempUserData
                    self.dataLoaded = true
                    self.showScanFirstProductScreen = tempUserData.isEmpty // 无数据时显示引导文字
                }
            } else {
                self.dataLoaded = true
                self.showScanFirstProductScreen = true
            }
        } else {
            self.dataLoaded = true
            self.showScanFirstProductScreen = true
            print("User does not exist")
        }
    }
}

2. 返回HistoryView时自动刷新数据

在body中添加onChange监听,当从ProductDetailView返回时重新加载数据:

var body: some View {
    NavigationView {
        ZStack(alignment: .bottom){
            // 原有视图内容...
        }
        .onAppear {
            fetchDataFromFirestore()
        }
        .onChange(of: isProductDetailOpen) { newValue in
            if !newValue {
                // 从ProductDetailView返回,重新加载历史记录
                fetchDataFromFirestore()
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者auchegbu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 11:57:43