繁体   English   中英

SwiftUI 视图未更新为 EnvironmentObject 更改

[英]SwiftUI view is not updating to EnvironmentObject change

我正在创建一个 SwiftUI 应用程序,其中包括 Firebase 以启用登录帐户,非常简单,只需一个带有密码和 email 字段的 ui 表单,然后是提交按钮。 用户登录后,我将 firebase 用户 object 存储在 EnvironmentObject 中,以便视图的 rest 可以访问它。 该应用程序当前的问题是,一旦用户登录并将用户数据存储在 EnvironmentObject 中,视图应该更新为更改后的 state 以显示不同的屏幕,但似乎视图仍然认为 EnvironmentObject等于零。 视图是否不会像 state 变量那样自动更改为 EnvironmentObject 中的更新?

我确保 EnvironmentObject 设置正确并传递给预览和 SceneDelegate

通过在登录时将帐户信息打印到控制台来确保应用程序确实成功登录了用户,但视图本身只会显示帐户信息的 nil,它似乎不会使用用户信息访问更新的 EnvironmentObject。

import SwiftUI
import Firebase
import Combine

struct ContentView: View {

    @EnvironmentObject var session: SessionStore

    @State var emailTextField: String = ""
    @State var passwordTextField: String = ""

    @State var loading = false
    @State var error = false

    var body: some View {
        VStack {
            if (session.session != nil) {
                Home()
            } else {
                Form {
                    TextField("Email", text: $emailTextField)
                    SecureField("Password", text: $passwordTextField)
                    Button(action: signIn) {
                        Text("Sign in")
                    }
                }

                Text("Session: \(session.session?.email ?? "no user")")
            }
        }.onAppear(perform: getUser)
    }

    func getUser () {
        session.listen()
    }

    func signIn () {
        loading = true
        error = false
        session.signIn(email: emailTextField, password: passwordTextField) { (result, error) in
            self.loading = false
            if error != nil {
                self.error = true
            } else {
                self.emailTextField = ""
                self.passwordTextField = ""
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(SessionStore())
    }
}



class SessionStore : ObservableObject {

    var didChange = PassthroughSubject<SessionStore, Never>()
    var session: User? { didSet { self.didChange.send(self) }}
    var handle: AuthStateDidChangeListenerHandle?

    func listen () {
        // monitor authentication changes using firebase
        handle = Auth.auth().addStateDidChangeListener { (auth, user) in
            if let account = user {
                // if we have a user, create a new user model
                print("Got user: \(account)")
                self.session = User(
                    uid: account.uid,
                    displayName: account.displayName,
                    email: account.email
                )
                print("Session: \(self.session?.email ?? "no user")")
            } else {
                // if we don't have a user, set our session to nil
                self.session = nil
            }
        }
    }

    func signUp(
        email: String,
        password: String,
        handler: @escaping AuthDataResultCallback
        ) {
        Auth.auth().createUser(withEmail: email, password: password, completion: handler)
    }

    func signIn(
        email: String,
        password: String,
        handler: @escaping AuthDataResultCallback
        ) {
        Auth.auth().signIn(withEmail: email, password: password, completion: handler)
    }

    func signOut () -> Bool {
        do {
            try Auth.auth().signOut()
            self.session = nil
            return true
        } catch {
            return false
        }
    }

    func unbind () {
        if let handle = handle {
            Auth.auth().removeStateDidChangeListener(handle)
        }
    }
}

class User {
    var uid: String
    var email: String?
    var displayName: String?

    init(uid: String, displayName: String?, email: String?) {
        self.uid = uid
        self.email = email
        self.displayName = displayName
    }

}

正如您在视图中看到的,它应该在用户未登录时呈现登录字段,而当用户登录时,视图应该显示另一个视图。 没有显示其他视图。

尝试使用 @Published 属性。 尝试实现这样的事情:

class SessionStore : ObservableObject {
    @Published var session: User
}

class User: ObservableObject {
    @Published var uid: String
    @Published var email: String?
    @Published var displayName: String?

    init(uid: String, displayName: String?, email: String?) {
        self.uid = uid
        self.email = email
        self.displayName = displayName
    }

}

当用户 object 发生更改时,这应该会更新您的视图,例如 email 或显示名称,因为它们已发布。 希望这会有所帮助,gl

更新:

因为 SwiftUI 还不支持嵌套的 Observables,所以你需要自己通知你的主 model。

请参阅此片段如何在 ObservableObject 中使用嵌套的 ObservableObject:

class Submodel1: ObservableObject {
  @Published var count = 0
}

class Submodel2: ObservableObject {
  @Published var count = 0
}

class Model: ObservableObject {
  @Published var submodel1: Submodel1 = Submodel1()
  @Published var submodel2: Submodel2 = Submodel2()

    var anyCancellable: AnyCancellable? = nil
    var anyCancellable2: AnyCancellable? = nil

    init() {

        anyCancellable = submodel1.objectWillChange.sink { (_) in
            self.objectWillChange.send()
        }

        anyCancellable2 = submodel2.objectWillChange.sink { (_) in
            self.objectWillChange.send()
        }
    }
}

当子模型内的数据发生变化时,主 Model 将通知自己。 这将导致视图更新。

让我知道这是否对您有所帮助.. 祝你好运!

在实践中,这个先前的解决方案是可行的,但它是一个非常简单且不切实际的案例场景。 当您想在非常深的视图中执行选项卡切换时会发生什么?

通过使用 EnvironmentKey,您可以从任何视图中获取当前选项卡状态并从那里更改它。 我不会写一个例子,因为这里解释得很好: https://www.thirdrocktechkno.com/blog/using-swiftui-environments-values-to-change-tab/

认为您将ObservableObjectBindableObject 可以试试这个:

@Published var session: User?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM