在SwiftUI中继续代码流之前,如何等待Firebase函数完成



我在一个类(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调用都是异步的,这就是为什么您需要注册状态侦听器或使用回调。

两个旁注:

  1. 您不应该将ObservableObjects实现为singleton。请改用@StateObject,以确保SwiftUI能够正确管理其状态
  2. 您不再需要直接使用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个简单技巧

最新更新