简体   繁体   English

SwiftUI 视图未更新为 EnvironmentObject 更改

[英]SwiftUI view is not updating to EnvironmentObject change

I'm creating a SwiftUI app that includes Firebase to enable logging into an account, extremely simple, just a ui form with password and email fields, then a button to submit.我正在创建一个 SwiftUI 应用程序,其中包括 Firebase 以启用登录帐户,非常简单,只需一个带有密码和 email 字段的 ui 表单,然后是提交按钮。 Once the user signs in I store the firebase user object in an EnvironmentObject so the rest of the views will have access to it.用户登录后,我将 firebase 用户 object 存储在 EnvironmentObject 中,以便视图的 rest 可以访问它。 The problem with the app currently is that once the user logs in and the user data is stored in the EnvironmentObject, the view is supposed to update to the changed state of this to show a different screen, but it seems the view still thinks the EnvironmentObject is equal to nil.该应用程序当前的问题是,一旦用户登录并将用户数据存储在 EnvironmentObject 中,视图应该更新为更改后的 state 以显示不同的屏幕,但似乎视图仍然认为 EnvironmentObject等于零。 Do views not automatically change to updates in an EnvironmentObject like they do for state variables perhaps?视图是否不会像 state 变量那样自动更改为 EnvironmentObject 中的更新?

I've made sure the EnvironmentObject is setup properly and passed to both the preview and SceneDelegate我确保 EnvironmentObject 设置正确并传递给预览和 SceneDelegate

Made sure that the app is indeed successfully logging in the user by printing account information to the console upon sign in, yet the view itself will only display nil for account information, it seems it wont access the updated EnvironmentObject with the user info.通过在登录时将帐户信息打印到控制台来确保应用程序确实成功登录了用户,但视图本身只会显示帐户信息的 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
    }

}

As you can see in the view, it is supposed to render login fields when user is not logged in, and when the user is logged in the view should display another view.正如您在视图中看到的,它应该在用户未登录时呈现登录字段,而当用户登录时,视图应该显示另一个视图。 That other view is not displaying.没有显示其他视图。

Try to make use of the @Published property.尝试使用 @Published 属性。 Try to implement something like this:尝试实现这样的事情:

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
    }

}

This should update your view when a change was made in the User object, like the email or displayname because they're Published.当用户 object 发生更改时,这应该会更新您的视图,例如 email 或显示名称,因为它们已发布。 Hope this will help, gl希望这会有所帮助,gl

UPDATED:更新:

Because SwiftUI doesn't support nested Observables yet, you need to notify your main model by yourself.因为 SwiftUI 还不支持嵌套的 Observables,所以你需要自己通知你的主 model。

See this snippet how to work with a nested ObservableObject inside a ObservableObject:请参阅此片段如何在 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()
        }
    }
}

When data inside a submodel changes, the main Model will notify itself.当子模型内的数据发生变化时,主 Model 将通知自己。 This will result in a update on the view.这将导致视图更新。

Let me know if this helped you out.. Goodluck!让我知道这是否对您有所帮助.. 祝你好运!

In practice this previous solution works, but is a very simplistic a no realistic case scenario.在实践中,这个先前的解决方案是可行的,但它是一个非常简单且不切实际的案例场景。 What happens when you want to perform tab switch in a very deep view??当您想在非常深的视图中执行选项卡切换时会发生什么?

By using EnvironmentKey you can take current tab status from any View and change it from there.通过使用 EnvironmentKey,您可以从任何视图中获取当前选项卡状态并从那里更改它。 I will not write an example because is properly explained here: https://www.thirdrocktechkno.com/blog/using-swiftui-environments-values-to-change-tab/我不会写一个例子,因为这里解释得很好: https://www.thirdrocktechkno.com/blog/using-swiftui-environments-values-to-change-tab/

Think you are mixing up ObservableObject with BindableObject ?认为您将ObservableObjectBindableObject Might try this instead:可以试试这个:

@Published var session: User?

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

相关问题 SwiftUI EnvironmentObject 与 View 不同步 - SwiftUI EnvironmentObject out of sync with View 如何在 Swiftui View 函数中使用 EnvironmentObject? - how to use EnvironmentObject in a Swiftui View function? 从视图外部设置 SwiftUI @EnvironmentObject - Setting a SwiftUI @EnvironmentObject from outside a view SwiftUI - 如何将 EnvironmentObject 传递到视图模型中? - SwiftUI - How to pass EnvironmentObject into View Model? 从视图模型中更新 EnvironmentObject - Updating EnvironmentObject from within the View Model SwiftUI A View.environmentObject(_:) for 可能作为此视图的祖先丢失。:文件 - SwiftUI A View.environmentObject(_:) for may be missing as an ancestor of this view.: file SwiftUI EnvironmentObject 不会重绘当前视图,而是重绘其父视图 - SwiftUI EnvironmentObject does not redraw the current view but its parent view SwiftUI iOS 14 视图不会使用 @EnvironmentObject 更新 @Published 数组 - SwiftUI iOS 14 View does not Update @Published Array with @EnvironmentObject 无法使用 @EnvironmentObject SwiftUI 更新先前视图上的文本 - Can't Update Text on Previous View using @EnvironmentObject SwiftUI 在 SwiftUI 中,我可以在不在 SceneDelegate 中传递 @EnvironmentObject 吗? 喜欢在任何视图中传递它 - In SwiftUI, can I pass @EnvironmentObject not in the SceneDelegate? Like pass it in any View
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM