我在一个类(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
实现为singleton。请改用@StateObject
,以确保SwiftUI能够正确管理其状态 - 您不再需要直接使用
PassthroughSubject
。使用@Published
属性包装器更容易
也就是说,这里有几个代码片段,展示了如何使用SwiftUI:实现电子邮件/密码身份验证
主视图
主视图显示您是否已登录。如果您未登录,它将显示一个按钮,打开一个单独的登录屏幕。
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
显示了一个带有按钮的简单电子邮件/密码表单。这里需要注意的一点是,它侦听对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()
}
}
}
CCD_ 11具有通过调用CCD_ 13来执行实际登录处理的方法CCD_。如您所见,如果用户已通过身份验证,它将把视图模型的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
}
}
}
备选方案:使用联合收割机
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 { $0.user }
.replaceError(with: nil)
.print("User signed in")
.map { $0 != 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
}
}
}
更多详细信息
我写了一篇关于这方面的文章,在文章中我更详细地解释了各个技术:从Swift调用异步Firebase API——回调、合并和异步/等待。如果你更喜欢看视频,我也为你介绍了:调用异步API 的3个简单技巧