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

SwiftUI中认证后预加载Firestore用户数据解决视图默认值问题

解决方案

1. 重构SessionStore,加入加载状态与异步数据获取逻辑

核心是在认证完成后立即触发用户数据拉取,并通过@Published变量跟踪加载状态,避免视图提前使用默认值:

class SessionStore: ObservableObject {
    @Published var session: SessionUser?
    @Published var isLoading = false // 新增:跟踪数据加载状态
    private var handle: AuthStateDidChangeListenerHandle?
    private let db = Firestore.firestore()

    func listen() {
        handle = Auth.auth().addStateDidChangeListener { [weak self] auth, user in
            guard let self = self else { return }
            if let user = user {
                self.isLoading = true
                // 认证状态变更时,异步拉取用户数据
                Task {
                    await self.fetchOrCreateSessionUser(uid: user.uid)
                    self.isLoading = false
                }
            } else {
                self.session = nil
                self.isLoading = false
            }
        }
    }

    func unbind() {
        if let handle = handle {
            Auth.auth().removeStateDidChangeListener(handle)
        }
    }

    func handleGoogleLogin() {
        GIDSignIn.sharedInstance.signIn(withPresenting: getRootViewController()) { [weak self] signInResult, err in
            guard let self = self, let signInResult = signInResult else { return }
            
            signInResult.user.refreshTokensIfNeeded { user, error in
                guard let idToken = user.idToken?.tokenString else { return }
                let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: user.accessToken.tokenString)
                
                Auth.auth().signIn(with: credential) { [weak self] result, err in
                    guard let self = self, let user = result?.user else { return }
                    self.isLoading = true
                    // 登录成功后立即拉取用户数据
                    Task {
                        await self.fetchOrCreateSessionUser(uid: user.uid)
                        self.isLoading = false
                    }
                }
            }
        }
    }

    // 封装:拉取或创建用户数据
    private func fetchOrCreateSessionUser(uid: String) async {
        do {
            let docRef = db.collection("users").document(uid)
            let docSnapshot = try await docRef.getDocument()
            
            if docSnapshot.exists {
                let user = try docSnapshot.data(as: User.self)
                self.session = SessionUser(uid: uid, user: user)
            } else {
                // 创建新用户默认数据
                let newUser = User(isUserOnboarded: false, /* 补充其他默认字段 */)
                try docRef.setData(from: newUser)
                self.session = SessionUser(uid: uid, user: newUser)
            }
        } catch {
            print("获取用户数据失败:\(error.localizedDescription)")
            self.session = nil
        }
    }

    // 辅助方法:获取根视图控制器
    private func getRootViewController() -> UIViewController {
        guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
            return .init()
        }
        guard let root = screen.windows.first?.rootViewController else {
            return .init()
        }
        return root
    }
}

2. 修正SessionUser的初始化与数据更新逻辑

确保isUserOnboarded直接绑定到user的属性,避免默认值干扰,同时简化结构:

class SessionUser: ObservableObject {
    @Published var user: User
    var uid: String
    // 直接通过user的属性推导,无需单独存储默认值
    var isUserOnboarded: Bool { user.isUserOnboarded }

    // 初始化时直接传入已获取的User对象,避免空值默认
    init(uid: String, user: User) {
        self.uid = uid
        self.user = user
    }

    // 如果需要后续更新用户数据,可保留此方法
    func updateUser() async {
        do {
            let docRef = Firestore.firestore().collection("users").document(uid)
            let docSnapshot = try await docRef.getDocument()
            if docSnapshot.exists {
                self.user = try docSnapshot.data(as: User.self)
            }
        } catch {
            print("更新用户数据失败:\(error.localizedDescription)")
        }
    }
}

3. 调整ContentView,基于加载状态与真实数据渲染视图

先显示加载动画,等数据拉取完成后再根据isUserOnboarded跳转,彻底避免使用默认值:

struct ContentView: View {
    @StateObject private var sessionStore = SessionStore()

    var body: some View {
        Group {
            if sessionStore.isLoading {
                // 加载中显示ProgressView
                ProgressView("加载中...")
                    .scaleEffect(1.5)
            } else if let session = sessionStore.session {
                // 数据加载完成,根据真实状态跳转
                if session.isUserOnboarded {
                    MainView()
                } else {
                    OnboardingView()
                }
            } else {
                // 未登录状态
                StartPageView()
            }
        }
        .onAppear {
            sessionStore.listen()
        }
        .onDisappear {
            sessionStore.unbind()
        }
    }
}

关键修复点说明

  • 加载状态管理:新增isLoading变量,确保视图在数据拉取完成前不渲染判断逻辑,彻底规避默认值问题。
  • 异步数据同步:在Auth状态变更和登录成功后,通过Task异步拉取Firestore数据,完成后再更新session变量,保证视图拿到的是真实数据。
  • SessionUser简化:isUserOnboarded直接依赖user的属性,无需单独维护默认值,避免数据不一致。
  • 视图渲染顺序:优先判断加载状态,再判断用户是否存在,最后根据真实的isUserOnboarded跳转,逻辑清晰且安全。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 10:36:01