SwiftUI Firebase Auth错误处理首次点击不触发弹窗问题
问题现象
接入 Firebase Auth 实现注册登录功能时,使用无效凭证首次点击「注册/登录」按钮不会弹出错误警告,必须第二次点击按钮才能正常触发错误弹窗,注册、登录模块均存在该问题。
相关实现代码
AuthViewModel 核心代码
@Published var userSession: User? @Published var currentUser: AppUser? @Published var signupError: Error? @Published var loginError: Error? static let shared = AuthViewModel() init() { self.userSession = Auth.auth().currentUser fetchUser() } func fetchUser() { guard let uid = userSession?.uid else { return } USER_COLLECTION.document(uid).getDocument { snapshot, _ in guard let user = try? snapshot?.data(as: AppUser.self) else { return } self.currentUser = user } } func registerUser(withEmail email: String, password: String) { Auth.auth().createUser(withEmail: email, password: password) { result, error in if let error = error { DispatchQueue.main.async { self.signupError = error print("error") return } } guard let user = result?.user else { return } let data: [String: Any] = ["uid": user.uid, "email": user.email ?? ""] USER_COLLECTION.document(user.uid).setData(data) { err in self.userSession = user self.fetchUser() } } } func login(withEmail email: String, password: String) { Auth.auth().signIn(withEmail: email, password: password) { result, error in if let error = error { DispatchQueue.main.async { self.loginError = error return } } guard let user = result?.user else { return } self.userSession = user self.fetchUser() } }
注册视图按钮实现
Button { authViewModel.registerUser(withEmail: email, password: password) print("called") if let error = authViewModel.signupError { alertTitle = "注册失败" alertMessage = error.localizedDescription alertShowing = true print(alertShowing) } print(alertShowing) } label: { CustomAuthButton(text: "注册") }
弹窗挂载代码
.alert(alertTitle, isPresented: $alertShowing) { Button("确定") { authViewModel.signupError = nil } } message: { Text(alertMessage) }
控制台日志输出
called false 2022-07-06 12:59:54.424510+0530 InfoMax[62528:2751506] [boringssl] boringssl_metrics_log_metric_block_invoke(153) Failed to log metrics error
问题原因
Firebase 的 createUser、signIn 方法均为异步执行的网络请求:点击按钮调用注册方法后,代码会立刻同步执行后续的错误检查逻辑,此时网络请求尚未返回,signupError 仍为空值,不会触发弹窗。等网络请求返回错误、完成signupError赋值时,按钮闭包内的检查逻辑早已执行完毕。第二次点击按钮时,上一次请求留存的错误值已存在,同步检查时可以拿到错误内容,才会触发弹窗。日志中 boringssl 相关输出为Xcode系统无关日志,不影响功能逻辑。
修复方案
不要在按钮点击的同步闭包里手动检查错误值,直接用 SwiftUI 的 onChange 修饰符监听 signupError 的变化,错误值更新时自动触发弹窗即可,修改逻辑如下:
- 移除按钮闭包里的错误检查逻辑,按钮只保留调用注册方法的代码:
Button { authViewModel.registerUser(withEmail: email, password: password) } label: { CustomAuthButton(text: "注册") }
- 给视图添加
onChange修饰符监听错误状态变化,保留原有弹窗逻辑:
.onChange(of: authViewModel.signupError) { newError in guard let error = newError else { return } alertTitle = "注册失败" alertMessage = error.localizedDescription alertShowing = true } .alert(alertTitle, isPresented: $alertShowing) { Button("确定") { authViewModel.signupError = nil } } message: { Text(alertMessage) }
注意:所有 @Published 状态属性的更新必须在主线程执行,之前添加的 DispatchQueue.main.async 包裹错误赋值的逻辑需要保留,避免 SwiftUI 状态更新不在主线程导致的页面渲染异常。
登录模块的同类问题处理逻辑完全一致,监听loginError的变化触发登录失败弹窗即可。
内容的提问来源于stack exchange,提问作者Aakash Kothari
相关产品推荐
相关产品推荐

