मैं वर्तमान में एक ऐसे मुद्दे में भाग रहा हूं जो मुझे भ्रमित कर रहा है। मैं अपने ऐप के लिए फायरबेस का उपयोग करके एक उपयोगकर्ता बनाने की कोशिश कर रहा हूं, जो एक स्विफ्टयूआई ऐप है। मेरे पास एक UserDataController है जो एक @Published var profile: Profile? वैरिएबल पर है।

मैं जो देख रहा हूं वह यह है कि फ़ायरबेस में Profile बनाने के बाद मुझे डेटा के साथ कॉलबैक मिलता है और इसे मेरे मॉडल में डीकोड करता है। फिर मैंने उस डिकोडेड मॉडल को अपनी प्रकाशित संपत्ति पर सेट किया। हालांकि, जब मैं ऐसा करता हूं तो स्विफ्टयूआई दृश्य नहीं बदलता है जैसा कि मैं उम्मीद करता हूं।

मैं परीक्षण डेटा के साथ Firebase शुरू करने से पहले इस कार्यक्षमता का परीक्षण किया है। जब मैं प्रकाशित संपत्ति को परीक्षण डेटा के साथ सेट करता हूं तो मुझे तदनुसार स्विफ्टयूआई दृश्य अपडेट दिखाई देता है। मैं देखता हूं कि ऐसा तब भी होता है जब मैं नेटवर्क अनुरोध को अनुकरण करने के लिए DispatchQueue.main.asyncAfter ब्लॉक में प्रकाशित संपत्ति को अपडेट करता हूं।

क्या मैं कुछ गलत कर रहा हूं जो स्विफ्टयूआई को अपडेट करने की अनुमति नहीं दे रहा है?

यह भी ध्यान रखें कि मैं अपने UserDataController इंजेक्शन के लिए Resolver का उपयोग कर रहा हूं। @InjectedObject एक @ObservedObject को SwiftUI दृश्यों में उपयोग के लिए पकड़ लेता है।

यहाँ मेरा कोड है:

App.swift

import Resolver
import SwiftUI

@main
struct MyApp: App {

    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    @InjectedObject var userController: UserDataController

    init() {
          // This is the method that calls `DispatchQueue.main.asyncAfter` which causes the
          // view to update correctly.
//        userController.authenticate()
    }

    var body: some Scene {
        WindowGroup {
            // This is where SwiftUI should be updating to show the profile instead of 
            // the LandingView since we have been logged in.
            if let profile = userController.profile {
                ProfileView()
                    .environmentObject(ProfileViewModel(profile: profile))
            } else {
                // This is where the login form is
                LandingView()
            }
        }
    }
}

UserDataController.swift

import Firebase
import FirebaseAuth
import FirebaseFirestoreSwift
import Foundation

// This AuthError also will not show as an alert when set from a completion block
enum AuthError: Error, Identifiable {

    var id: AuthError { self }

    case noUser
    case emailExists
    case couldNotSignOut
    case generic
}

final class UserDataController: ObservableObject {

    @Published var profile: Profile? {
        didSet {
            print("Profile: \(profile)")
        }
    }
    @Published var user: User?

    @Published var authError: AuthError?

    private lazy var db = Firestore.firestore()

    private var authStateListener: AuthStateDidChangeListenerHandle?
    private var profileListener: ListenerRegistration?

    // MARK: Auth

    func authenticate() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            self.profile = TestData.amyAlmond
        }
    }

    func login(email: String, password: String) {
        applyStateListener()

        Auth.auth().signIn(withEmail: email, password: password) { [weak self] result, error in
            if let error = error {
                self?.authError = .generic
            } else if let user = result?.user {
                self?.addSnapshotListener(for: user)
            } else {
                self?.authError = .noUser
            }
        }
    }

    func signUp(email: String, password: String, firstName: String, lastName: String) {
        applyStateListener()

        Auth.auth().createUser(withEmail: email, password: password) { [weak self] result, error in
            if let error = error {
                self?.authError = .generic
            } else if let user = result?.user {
                self?.addSnapshotListener(for: user)
                self?.createProfile(for: user, firstName: firstName, lastName: lastName)
            } else {
                self?.authError = .noUser
            }
        }
    }
}

// MARK: - Private

private extension UserDataController {

    func applyStateListener() {
        guard authStateListener == nil else { return }

        authStateListener = Auth.auth().addStateDidChangeListener { [weak self] auth, user in
            guard let self = self else { return }

            if let user = auth.currentUser {
                self.user = user
            } else {
                self.user = nil
                self.profile = nil

                self.profileListener?.remove()
                self.profileListener = nil

                if let stateListener = self.authStateListener {
                    Auth.auth().removeStateDidChangeListener(stateListener)
                    self.authStateListener = nil
                }
            }
        }
    }

    func addSnapshotListener(for user: User) {
        guard profileListener == nil else { return }

        profileListener = db.collection("profiles").document(user.uid).addSnapshotListener { [weak self] snapshot, error in
            guard let self = self else { return }
            guard let snapshot = snapshot else { return }

            do {
                // Setting the profile here does not change the SwiftUI view
                // These blocks happen on the main thread as well, so wrapping this
                // in a `DispatchQueue.main.async` does nothing.
                self.profile = try snapshot.data(as: Profile.self)
            } catch {
                print("Error Decoding Profile: \(error)")
            }
        }
    }

    func createProfile(for user: User, firstName: String, lastName: String) {
        let profile = Profile(uid: user.uid, firstName: firstName, lastName: lastName, farms: [], preferredFarmId: nil)

        do {
            try db.collection("profiles").document(user.uid).setData(from: profile)
        } catch {
            print(error)
        }
    }
}

LandingView.swift

import Resolver
import SwiftUI

struct LandingView: View {

    @InjectedObject private var userController: UserDataController

    var body: some View {
        VStack(spacing: 10) {
            LightText("Title")
                .font(.largeTitle)

            Spacer()

            AuthenticationView()

            Spacer()
        }
        .frame(maxWidth: .infinity)
        .padding()
        .alert(item: $userController.authError) { error -> Alert in
            Alert(title: Text("Oh Boy"), message: Text("Something went wrong"), dismissButton: .cancel())
        }
    }
}

AuthenticationView.swift

import SwiftUI

struct AuthenticationView: View {

    @StateObject private var viewModel = AuthenticationViewModel()

    var body: some View {
        VStack {
            VStack {
                Group {
                    switch viewModel.mode {
                    case .login:
                        loginForm
                    case .signUp:
                        signUpForm
                    }
                }
                .textFieldStyle(RoundedBorderTextFieldStyle())
            }
            .padding()
            .background(
                RoundedRectangle(cornerRadius: 20)
                    .foregroundColor(Color.gray)
            )

            Button(action: viewModel.switchMode) {
                Text(viewModel.switchModeTitle)
            }
            .padding(.bottom, 10)

            Button(action: viewModel.submitAction) {
                Text(viewModel.submitButtonTitle)
            }
            .disabled(!viewModel.isValid)
        }
        .padding()
    }
}

private extension AuthenticationView {

    @ViewBuilder
    var loginForm: some View {
        TextField("Email Address", text: $viewModel.emailAddress)
        TextField("Password", text: $viewModel.password)
    }

    @ViewBuilder
    var signUpForm: some View {
        TextField("First Name", text: $viewModel.firstName)
        TextField("Last Name", text: $viewModel.lastName)
        TextField("Email Address", text: $viewModel.emailAddress)
        TextField("Password", text: $viewModel.password)
        TextField("Confirm Password", text: $viewModel.confirmPassword)
    }
}

AuthenticationViewModel.swift

import Foundation
import Resolver

final class AuthenticationViewModel: ObservableObject {

    @Injected private var userController: UserDataController

    enum Mode {
        case login, signUp
    }

    @Published var firstName: String = ""
    @Published var lastName: String = ""
    @Published var emailAddress: String = ""
    @Published var password: String = ""
    @Published var confirmPassword: String = ""

    @Published var mode: Mode = .login
}

extension AuthenticationViewModel {

    var isValid: Bool {
        switch mode {
        case .login:
            return !emailAddress.isEmpty && isPasswordValid
        case .signUp:
            return !firstName.isEmpty
                && !lastName.isEmpty
                && !emailAddress.isEmpty
                && isPasswordValid
                && !confirmPassword.isEmpty
                && password == confirmPassword
        }
    }

    var submitButtonTitle: String {
        switch mode {
        case .login:
            return "Login"
        case .signUp:
            return "Create Account"
        }
    }

    var switchModeTitle: String {
        switch mode {
        case .login:
            return "Create a New Account"
        case .signUp:
            return "Login"
        }
    }

    func switchMode() {
        if mode == .login {
            mode = .signUp
        } else {
            mode = .login
        }
    }

    func submitAction() {
        switch mode {
        case .login:
            loginUser()
        case .signUp:
            createUser()
        }
    }
}

private extension AuthenticationViewModel {

    var isPasswordValid: Bool {
        !password.isEmpty && password.count > 8
    }

    func loginUser() {
        userController.login(email: emailAddress, password: password)
    }

    func createUser() {
        userController.signUp(email: emailAddress, password: password, firstName: firstName, lastName: lastName)
    }
}
1
jpowell 13 मार्च 2021, 02:49

1 उत्तर

सबसे बढ़िया उत्तर

मुझे इसका कारण पता चला कि यह अपडेट क्यों नहीं हो रहा था और इसका ऊपर के सेटअप से कोई लेना-देना नहीं था।

यह पहली बार है जब मैं निर्भरता इंजेक्शन के लिए Resolver का उपयोग कर रहा हूं और मैंने भोलेपन से सोचा कि किसी वस्तु को पंजीकृत करने से एक उदाहरण बन जाता है। मेरा स्विफ्टयूआई व्यू अपडेट नहीं होने का कारण यह है कि मेरे पास UserDataController के दो अलग-अलग इंस्टेंस थे और जो प्रोफाइल सेट करता था वह स्विफ्टयूआई व्यू में नहीं था।

मैंने अपने UserDataController को Resolver के साथ पंजीकृत करते समय .scope(.application) फ़ंक्शन का उपयोग करके इसे ठीक किया। वह दायरा इसे सिंगलटन की तरह कार्य करता है जो कि मैं पहली बार में जा रहा था।

0
jpowell 13 मार्च 2021, 08:24