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
相关产品推荐
相关产品推荐

