在 SwiftUI 中继续我的代码流之前,如何等待 Firebase 函数完成?
How do I wait for a Firebase function to complete before continuing my code flow in SwiftUI?
我在 class (listenToUser) 中有一个 Firebase 函数,它工作正常,但我注意到下一个代码(IF ELSE)没有等待它完成就继续。如何在继续我的代码之前等待我的功能完成?
我的主视图部分代码:
...
@EnvironmentObject var firebaseSession: FirebaseSession_VM
...
.onAppear {
firebaseSession.listenToUser()
if firebaseSession.firebaseUser == nil {
showSignInView = true
} else {
showSignInStep1View = true
}
}
我的函数:
import SwiftUI
import Combine
import FirebaseAuth
class FirebaseSession_VM: ObservableObject {
static let instance = FirebaseSession_VM()
var didChange = PassthroughSubject<FirebaseSession_VM, Never>()
@Published var firebaseUser: FirebaseUser_M? {
didSet {
self.didChange.send(self)
}
}
var handle: AuthStateDidChangeListenerHandle?
func listenToUser () {
// monitor authentication changes using firebase
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
self.firebaseUser = FirebaseUser_M(
id: user.uid,
email: user.email
)
} else {
self.firebaseUser = nil
}
}
}
}
大多数 Firebase API 调用都是异步的,这就是为什么您需要注册状态侦听器或使用回调的原因。
两个旁注:
- 您不应将
ObservableObjects
实现为单例。请改用 @StateObject
,以确保 SwiftUI 可以正确管理其状态。
- 您不再需要直接使用
PassthroughSubject
。使用 @Published
属性 包装器更容易。
也就是说,这里有几个代码片段展示了如何使用 SwiftUI 实现 Email/Password 身份验证:
主视图
主视图会显示您是否已登录。如果您未登录,它将显示一个按钮,用于打开单独的登录屏幕。
import SwiftUI
struct ContentView: View {
@StateObject var viewModel = ContentViewModel()
var body: some View {
VStack {
Text(" Hello!")
.font(.title3)
switch viewModel.isSignedIn {
case true:
VStack {
Text("You're signed in.")
Button("Tap here to sign out") {
viewModel.signOut()
}
}
default:
VStack {
Text("It looks like you're not signed in.")
Button("Tap here to sign in") {
viewModel.signIn()
}
}
}
}
.sheet(isPresented: $viewModel.isShowingLogInView) {
SignInView()
}
}
}
主视图的视图模型侦听任何身份验证状态更改并相应地更新 isSignedIn
属性。这会驱动 ContentView
及其显示内容。
import Foundation
import Firebase
class ContentViewModel: ObservableObject {
@Published var isSignedIn = false
@Published var isShowingLogInView = false
init() {
// listen for auth state change and set isSignedIn property accordingly
Auth.auth().addStateDidChangeListener { auth, user in
if let user = user {
print("Signed in as user \(user.uid).")
self.isSignedIn = true
}
else {
self.isSignedIn = false
}
}
}
/// Show the sign in screen
func signIn() {
isShowingLogInView = true
}
/// Sign the user out
func signOut() {
do {
try Auth.auth().signOut()
}
catch {
print("Error while trying to sign out: \(error)")
}
}
}
登录视图
SignInView
显示了一个带有按钮的简单 email/password 表单。这里要注意的有趣的事情是它监听 viewModel.isSignedIn
属性 的任何更改,并调用 dismiss
操作(它从环境中提取)。另一种选择是将回调实现为视图模型的 signIn()
方法的尾随闭包。
struct SignInView: View {
@Environment(\.dismiss) var dismiss
@StateObject var viewModel = SignInViewModel()
var body: some View {
VStack {
Text("Hi!")
.font(.largeTitle)
Text("Please sign in.")
.font(.title3)
Group {
TextField("Email", text: $viewModel.email)
.disableAutocorrection(true)
.autocapitalization(.none)
SecureField("Password", text: $viewModel.password)
}
.padding()
.background(Color(UIColor.systemFill))
.cornerRadius(8.0)
.padding(.bottom, 8)
Button("Sign in") {
viewModel.signIn()
}
.foregroundColor(Color(UIColor.systemGray6))
.padding(.vertical, 16)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.accentColor)
.cornerRadius(8)
}
.padding()
.onChange(of: viewModel.isSignedIn) { signedIn in
dismiss()
}
}
}
SignInViewModel
有一个方法 signIn
,它通过调用 Auth.auth().signIn(withEmail:password:)
执行实际的登录过程。如您所见,如果用户已通过身份验证,它会将视图模型的 isSignedIn
属性 更改为 true
。
import Foundation
import FirebaseAuth
class SignInViewModel: ObservableObject {
@Published var email: String = ""
@Published var password: String = ""
@Published var isSignedIn: Bool = false
func signIn() {
Auth.auth().signIn(withEmail: email, password: password) { authDataResult, error in
if let error = error {
print("There was an issue when trying to sign in: \(error)")
return
}
guard let user = authDataResult?.user else {
print("No user")
return
}
print("Signed in as user \(user.uid), with email: \(user.email ?? "")")
self.isSignedIn = true
}
}
}
备选方案:使用 Combine
import Foundation
import FirebaseAuth
import FirebaseAuthCombineSwift
class SignInViewModel: ObservableObject {
@Published var email: String = ""
@Published var password: String = ""
@Published var isSignedIn: Bool = false
// ...
func signIn() {
Auth.auth().signIn(withEmail: email, password: password)
.map { [=14=].user }
.replaceError(with: nil)
.print("User signed in")
.map { [=14=] != nil }
.assign(to: &$isSignedIn)
}
}
备选方案:使用 async/await
import Foundation
import FirebaseAuth
class SignInViewModel: ObservableObject {
@Published var email: String = ""
@Published var password: String = ""
@Published var isSignedIn: Bool = false
@MainActor
func signIn() async {
do {
let authDataResult = try 3 await 1 Auth.auth().signIn(withEmail: email, password: password)
let user = authDataResult.user
print("Signed in as user \(user.uid), with email: \(user.email ?? "")")
self.isSignedIn = true
}
catch {
print("There was an issue when trying to sign in: \(error)")
self.errorMessage = error.localizedDescription
}
}
}
更多详情
我写了一篇关于这个的文章,其中我更详细地解释了各个技术:Calling asynchronous Firebase APIs from Swift - Callbacks, Combine, and async/await. If you'd rather watch a video, I've got you covered as well: 3 easy tips for calling async APIs
我在 class (listenToUser) 中有一个 Firebase 函数,它工作正常,但我注意到下一个代码(IF ELSE)没有等待它完成就继续。如何在继续我的代码之前等待我的功能完成?
我的主视图部分代码:
...
@EnvironmentObject var firebaseSession: FirebaseSession_VM
...
.onAppear {
firebaseSession.listenToUser()
if firebaseSession.firebaseUser == nil {
showSignInView = true
} else {
showSignInStep1View = true
}
}
我的函数:
import SwiftUI
import Combine
import FirebaseAuth
class FirebaseSession_VM: ObservableObject {
static let instance = FirebaseSession_VM()
var didChange = PassthroughSubject<FirebaseSession_VM, Never>()
@Published var firebaseUser: FirebaseUser_M? {
didSet {
self.didChange.send(self)
}
}
var handle: AuthStateDidChangeListenerHandle?
func listenToUser () {
// monitor authentication changes using firebase
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
self.firebaseUser = FirebaseUser_M(
id: user.uid,
email: user.email
)
} else {
self.firebaseUser = nil
}
}
}
}
大多数 Firebase API 调用都是异步的,这就是为什么您需要注册状态侦听器或使用回调的原因。
两个旁注:
- 您不应将
ObservableObjects
实现为单例。请改用@StateObject
,以确保 SwiftUI 可以正确管理其状态。 - 您不再需要直接使用
PassthroughSubject
。使用@Published
属性 包装器更容易。
也就是说,这里有几个代码片段展示了如何使用 SwiftUI 实现 Email/Password 身份验证:
主视图
主视图会显示您是否已登录。如果您未登录,它将显示一个按钮,用于打开单独的登录屏幕。
import SwiftUI
struct ContentView: View {
@StateObject var viewModel = ContentViewModel()
var body: some View {
VStack {
Text(" Hello!")
.font(.title3)
switch viewModel.isSignedIn {
case true:
VStack {
Text("You're signed in.")
Button("Tap here to sign out") {
viewModel.signOut()
}
}
default:
VStack {
Text("It looks like you're not signed in.")
Button("Tap here to sign in") {
viewModel.signIn()
}
}
}
}
.sheet(isPresented: $viewModel.isShowingLogInView) {
SignInView()
}
}
}
主视图的视图模型侦听任何身份验证状态更改并相应地更新 isSignedIn
属性。这会驱动 ContentView
及其显示内容。
import Foundation
import Firebase
class ContentViewModel: ObservableObject {
@Published var isSignedIn = false
@Published var isShowingLogInView = false
init() {
// listen for auth state change and set isSignedIn property accordingly
Auth.auth().addStateDidChangeListener { auth, user in
if let user = user {
print("Signed in as user \(user.uid).")
self.isSignedIn = true
}
else {
self.isSignedIn = false
}
}
}
/// Show the sign in screen
func signIn() {
isShowingLogInView = true
}
/// Sign the user out
func signOut() {
do {
try Auth.auth().signOut()
}
catch {
print("Error while trying to sign out: \(error)")
}
}
}
登录视图
SignInView
显示了一个带有按钮的简单 email/password 表单。这里要注意的有趣的事情是它监听 viewModel.isSignedIn
属性 的任何更改,并调用 dismiss
操作(它从环境中提取)。另一种选择是将回调实现为视图模型的 signIn()
方法的尾随闭包。
struct SignInView: View {
@Environment(\.dismiss) var dismiss
@StateObject var viewModel = SignInViewModel()
var body: some View {
VStack {
Text("Hi!")
.font(.largeTitle)
Text("Please sign in.")
.font(.title3)
Group {
TextField("Email", text: $viewModel.email)
.disableAutocorrection(true)
.autocapitalization(.none)
SecureField("Password", text: $viewModel.password)
}
.padding()
.background(Color(UIColor.systemFill))
.cornerRadius(8.0)
.padding(.bottom, 8)
Button("Sign in") {
viewModel.signIn()
}
.foregroundColor(Color(UIColor.systemGray6))
.padding(.vertical, 16)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.accentColor)
.cornerRadius(8)
}
.padding()
.onChange(of: viewModel.isSignedIn) { signedIn in
dismiss()
}
}
}
SignInViewModel
有一个方法 signIn
,它通过调用 Auth.auth().signIn(withEmail:password:)
执行实际的登录过程。如您所见,如果用户已通过身份验证,它会将视图模型的 isSignedIn
属性 更改为 true
。
import Foundation
import FirebaseAuth
class SignInViewModel: ObservableObject {
@Published var email: String = ""
@Published var password: String = ""
@Published var isSignedIn: Bool = false
func signIn() {
Auth.auth().signIn(withEmail: email, password: password) { authDataResult, error in
if let error = error {
print("There was an issue when trying to sign in: \(error)")
return
}
guard let user = authDataResult?.user else {
print("No user")
return
}
print("Signed in as user \(user.uid), with email: \(user.email ?? "")")
self.isSignedIn = true
}
}
}
备选方案:使用 Combine
import Foundation
import FirebaseAuth
import FirebaseAuthCombineSwift
class SignInViewModel: ObservableObject {
@Published var email: String = ""
@Published var password: String = ""
@Published var isSignedIn: Bool = false
// ...
func signIn() {
Auth.auth().signIn(withEmail: email, password: password)
.map { [=14=].user }
.replaceError(with: nil)
.print("User signed in")
.map { [=14=] != nil }
.assign(to: &$isSignedIn)
}
}
备选方案:使用 async/await
import Foundation
import FirebaseAuth
class SignInViewModel: ObservableObject {
@Published var email: String = ""
@Published var password: String = ""
@Published var isSignedIn: Bool = false
@MainActor
func signIn() async {
do {
let authDataResult = try 3 await 1 Auth.auth().signIn(withEmail: email, password: password)
let user = authDataResult.user
print("Signed in as user \(user.uid), with email: \(user.email ?? "")")
self.isSignedIn = true
}
catch {
print("There was an issue when trying to sign in: \(error)")
self.errorMessage = error.localizedDescription
}
}
}
更多详情
我写了一篇关于这个的文章,其中我更详细地解释了各个技术:Calling asynchronous Firebase APIs from Swift - Callbacks, Combine, and async/await. If you'd rather watch a video, I've got you covered as well: 3 easy tips for calling async APIs