Can I pass a Binding variable as a parameter in a function? (do I need to in this case?)

Viewed 52

I'm new to Swift / Firebase and need some help with my code.

I have a simple create new account page, and I am using Firebase.

I want to create a user when a button is tapped - change button colour/text to reflect this - then return to HomeView

Now, I want to include all the Auth.auth() in a separate function on a separate swift file - to keep my View code clean.

Currently, I'm setting the isShowingNewAccountView = false to return back to HomeView (via a NavigationLink on HomeView) - isShowingAccountView is a Binding variable from HomeView.

If I put all the Auth.auth() code into a separate function, is it possible to change the value of the isShowingAccountView to 'false' when a user is created from within the function? Is there a more elegant alternative

Button(action: {
    Auth.auth().createUser(withEmail: newUser.userEmail, password: newUser.userPassword) { authDataResult, error in
        if error != nil {
            print("Error detected")
            print(error!.localizedDescription)

            isShowingNewAccountView = true         //Binding variable from HomeView - Keep showing NewAccountView
            newUser.successNewAccountCreated = false
            return
        }
        else
        {
            print("Account Successfully Created")
            isShowingNewAccountView = false        //Binding variable from HomeView - Revert back to HomeView
            newUser.successNewAccountCreated = true
            return
        }
    }
}, label: {
    CustomButton(buttonText: (!newUser.successNewAccountCreated ? "Create New Account" : "New Account Created"), colourVar: newUser.successNewAccountCreated ? .green : (isInputAppropriate() ? .accentColor : .gray)
})

On HomeView I have:

NavigationLink(destination: NewAccountView(isShowingNewAccountView: $isShowingNewAccountView), isActive: $isShowingNewAccountView) {
    //passing isShowingAccountView as a binding $
    EmptyView()
}

Thanks in advance.

1 Answers

Maybe this approach may help you:

Three views (just as an example):

  1. ContentView: It shows either Page1 or Page2, depending on the var isShowingNewAccountView.
  2. Page1: It is the page you use to create a new user, it will be shown when isShowingNewAccountView is false. It calls the function signUp from the AuthenticationViewModel.
  3. Page2: It is the page that will be opened when a new User has been created and isShowingNewAccountView is true

One ViewModel (also just as an example)

The authentication is handled in the AuthenticationViewModel. It is handling the functions to signUp, signIn and signOut. It also holds isShowingNewAccountView.

Views

import SwiftUI
import FirebaseFirestore
import Firebase
import FirebaseFirestoreSwift

struct Page1: View {

    @EnvironmentObject var authenticationViewModel: AuthenticationViewModel
    @State private var buttonPressed = false
    @State private var email: String = "sebastian@hello.me"
    @State private var password: String = "password"
    
    var body: some View {
        VStack(){
            
            Text("Register")
                .font(Font.system(size: 24, weight: .bold))
                .padding()
                .padding(.vertical, 50)
            
            TextField("E-Mail", text: $email)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
            
            SecureField("Password", text: $password)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
            
            Button(action: {
                buttonPressed.toggle()
                authenticationViewModel.signUp(email: email, password: password){ success, uid in
                    if success {
                        print("UID: \(uid)")
                    } else {
                        print("Uh-oh")
                    }
                    buttonPressed = false
                }
            }) {
                Text(buttonPressed ? "In Progress" : "Sign Up - Create User")
                    .foregroundColor(buttonPressed ? Color.pink : Color.cyan)
            }
        }
    }
}

struct Page2: View {

    @EnvironmentObject var authenticationViewModel: AuthenticationViewModel
    var body: some View {
        
        Button(action: {
            authenticationViewModel.isShowingNewAccountView.toggle()
        }) {
            Text("Back to other page")
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var authenticationViewModel: AuthenticationViewModel
    var body: some View {
        if authenticationViewModel.isShowingNewAccountView {
            Page2()
        } else {
            Page1()
        }
    }
}

ViewModel for Authentication

import Foundation
import FirebaseFirestore
import Firebase
import FirebaseFirestoreSwift

class AuthenticationViewModel: ObservableObject {
    
    let db = Firestore.firestore()
    
    @Published var isShowingNewAccountView: Bool = false
    
    // Sign Up
    func signUp(email: String, password: String, completion: @escaping (Bool, String)->Void) {
        
        Auth.auth().createUser(withEmail: email, password: password) { authResult, error in
            
            // ERROR AND SUCCESS HANDLING
            if error != nil {
                // ERROR HANDLING
                print(error?.localizedDescription as Any)
                completion(false, "ERROR")
            } else {
                // SUCCESS HANDLING
                self.isShowingNewAccountView = true
                completion(true, authResult?.user.uid ?? "")
            }
        }
    }
    
    // Sign In
    func signIn(email: String, password: String, completion: @escaping (Bool, String)->Void) {
        
        Auth.auth().signIn(withEmail: email, password: password) { (authResult, error) in
            
            // ERROR AND SUCCESS HANDLING
            if error != nil {
                // ERROR HANDLING
                print(error?.localizedDescription as Any)
                completion(false, "ERROR")
            }
            
            completion(true, authResult?.user.uid ?? "")
        }
    }
    
    // Sign Out
    func signOut(_ completion: @escaping (Bool) ->Void) {
        try! Auth.auth().signOut()
        completion(true)
    }
    
    // Create new user
    func createNewUser(name: String, id: String) {
        do {
            let newUser = User(name: name, id: id)
            try db.collection("users").document(newUser.id!).setData(from: newUser) { _ in
                print("User \(name) created")
            }
        } catch let error {
            print("Error writing user to Firestore: \(error)")
        }
    }
}

Alternative with a NavigationLink

In your approach you are using a NavigationLink. That would work as well. In that case the ContentView as well as the Page1 view are slightly different (Page2 stays as it is):

ContentView Update

In the ContentView it is not necessary do make the decision which view will be shown.

struct ContentView: View {
    @EnvironmentObject var authenticationViewModel: AuthenticationViewModel
    var body: some View {
       Page1()
    }
}

Page1 Update

On Page1 we now need the NavigationView and the NavigationLink. As soon as isShowingNewAccountView becomes true, it opens Page2, but for me, the NavigationLink is something you can press, like a button. But anyway:

struct Page1: View {

    @EnvironmentObject var authenticationViewModel: AuthenticationViewModel
    @State private var buttonPressed = false
    @State private var email: String = "sebastian@hello.me"
    @State private var password: String = "password"
    
    var body: some View {
        NavigationView(){
            VStack(){
                
                Text("Register")
                    .font(Font.system(size: 24, weight: .bold))
                    .padding()
                    .padding(.vertical, 50)
                
                TextField("E-Mail", text: $email)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()
                
                SecureField("Password", text: $password)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()
                
                Button(action: {
                    buttonPressed.toggle()
                    authenticationViewModel.signUp(email: email, password: password){ success, uid in
                        if success {
                            print("UID: \(uid)")
                        } else {
                            print("Uh-oh")
                        }
                        buttonPressed = false
                    }
                }) {
                    Text(buttonPressed ? "In Progress" : "Sign Up - Create User")
                        .foregroundColor(buttonPressed ? Color.pink : Color.cyan)
                }
                
                NavigationLink(destination: Page2(), isActive: $authenticationViewModel.isShowingNewAccountView){
                                EmptyView()}
            }
        }
    }
}

Please see the gif:

SignUp with NavigationView

Related