web-dev-qa-db-fra.com

La vue SwiftUI ne se met pas à jour avec la modification EnvironmentObject

Je crée une application SwiftUI qui inclut Firebase pour permettre la connexion à un compte, extrêmement simple, juste un formulaire d'interface utilisateur avec des champs de mot de passe et d'email, puis un bouton pour soumettre. Une fois l'utilisateur connecté, je stocke l'objet utilisateur Firebase dans un EnvironmentObject afin que le reste des vues y aient accès. Le problème avec l'application est actuellement qu'une fois que l'utilisateur se connecte et que les données utilisateur sont stockées dans EnvironmentObject, la vue est censée se mettre à jour à l'état modifié pour afficher un écran différent, mais il semble que la vue pense toujours que EnvironmentObject est égal à zéro. Les vues ne changent-elles pas automatiquement en mises à jour dans un EnvironmentObject comme elles le font peut-être pour les variables d'état?

Je me suis assuré que l'EnvironnementObjet est correctement configuré et passé à la fois à l'aperçu et à SceneDelegate

Assurez-vous que l'application se connecte bien à l'utilisateur en imprimant les informations de compte sur la console lors de la connexion, mais la vue elle-même n'affichera rien pour les informations de compte, il semble qu'elle n'accédera pas à EnvironmentObject mis à jour avec les informations utilisateur.

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
    }

}

Comme vous pouvez le voir dans la vue, il est censé afficher les champs de connexion lorsque l'utilisateur n'est pas connecté, et lorsque l'utilisateur est connecté, la vue doit afficher une autre vue. Cette autre vue ne s'affiche pas.

2
Dylan

Essayez d'utiliser la propriété @Published. Essayez d'implémenter quelque chose comme ceci:

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
    }

}

Cela devrait mettre à jour votre vue lorsqu'une modification a été apportée à l'objet Utilisateur, comme l'e-mail ou le nom d'affichage, car ils sont publiés. J'espère que cela vous aidera, gl

MISE À JOUR:

Étant donné que SwiftUI ne prend pas encore en charge les observables imbriqués, vous devez informer vous-même votre modèle principal.

Voir cet extrait comment travailler avec un ObservableObject imbriqué dans un 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()
        }
    }
}

Lorsque les données d'un sous-modèle changent, le modèle principal se notifie. Cela entraînera une mise à jour de la vue.

Faites-moi savoir si cela vous a aidé .. Goodluck!

6
Tobias Hesselink