User Sign Up and Login with Firebase – Swift

In this tutorial, I am going show you how to create a user sign up page, as well as a user login page. The user’s account will be saved and authenticated in Firebase. For the sake of keeping the post specific to the topic, I am going to assume you already have a Firebase account attached to your Xcode project.

In Main.storyboard, add 2 view controllers to the already existing one (there should be 3 view controllers after this).Screen Shot 2018-08-17 at 11.03.58 AM.png

The left view controller will be the login page, the middle view controller will be the sign up page, and the right view controller will be the home page.

On the left view controller, add 2 textfields (one for login, one for password) and 2 buttons (one for signing in, one for signing up).

On the middle view controller, add a textfield for each the user’s name, email, and password at least. I added a confirm password textfield as well, but that is optional. Add 2 buttons on this view controller as well (one to submit registration, and one to go back to the login page).

On the last view controller, add 2 labels (one to display the user’s name, one to display the user’s email), and a button for sign out.

Screen Shot 2018-08-17 at 11.15.00 AM.png

Now we want to connect each view controller. From the first view controller, do ctrl-click-drag the sign up button onto the middle view controller (click “Show” when prompted). From the middle view controller, do ctrl-click-drag the done button onto the last view controller (click “Show” when prompted).

It’s now time to create the files to code in for each view controller. In your Xcode project, click File > New > File > Cocoa Touch Class > Next > and give your view controller the title LoginViewController (leave everything else in the window the same) > Next > save to your project file > Create. Do this for the other two files as well (named RegisterViewController and HomeViewController respectively).

Back in Main.storyboard, connect your view controllers to their respective files in the Identity Inspector.

Connect your buttons and textfields to your respective files, and import Firebase.

Below I’ve attached each file along with detailed comments explaining why I added the code I did.

LoginViewController:

import UIKit
import Firebase
import FirebaseAuth
import SwiftKeychainWrapper

class LoginViewController: UIViewController, UITextFieldDelegate {
//connecting and naming the textfields from Main.storyboard
    @IBOutlet weak var email: UITextField!
    @IBOutlet weak var password: UITextField!

//in here, we want to write everything that should be called when
//the view loads.
    override func viewDidLoad() {
        super.viewDidLoad()
//we want the email and password delegates to come from the LoginViewController
        self.email.delegate = self
        self.password.delegate = self
//calling the function from the extension to hide the keyboard when
//the user taps around it.
        self.hideKeyboardWhenTappedAround()
    }
/* This function authenticates the user when the Sign In button is pressed. The Auth function provided by Firebase reads in the information provided in the email and password textfields, and checks to see if it matches any accounts already registered in Firebase. 
If there are no accounts under this email, the console will print an error message saying that this account does not exist. If the password is incorrect, the console will state that the password is incorrect or the email is incorrect. Otherwise, the user will be logged in and directed to the HomeViewController (I did not include a perform segue function here because the sign in button is the only button from the sign in page that will lead to the HomeViewController, and we already established the segue in Main.storyboard).*/
    @IBAction func signInPressed(_ sender: Any){
//reading in the email and password textfields, and using the sign in function 
//provided by Firebase to authenticate the user.
        Auth.auth().signIn(withEmail: email.text!, password: password.text!) { (user, error) i
         //if error does not exists
            if(error == nil) {
                print("Signed in!")
            } else {
                //error exists
                print("Error logging in: \(error?.localizedDescription)")
                self.dismiss(animated: true, completion: nil)
            }
        }
    }
/*This function allows the app to segue to the RegisterViewController
when the Sign Up button is pressed on the LoginViewController*/
    @IBAction func signUpPressed(_ sender: Any) {
//it is not necessary to include this line unless the sign up button
//could lead to 2 different VC's. 
        self.performSegue(withIdentifier: "toSignUp", sender: self)
    }
/* This function is written here but actually called on the sign out
page. It unwinds the whole app and brings it back to the login page.*/
    @IBAction func unwindToVC1(segue:UIStoryboardSegue) {
//after unwinding the app, I want to reset the email and password
//textfields on the login page
        email.text = ""
        password.text = ""
    }
/* This function causes the blinking "|" text bar
to jump to the next textfield when the user hits the return button
on the keyboard. It makes typing in multiple textfields much more
convenient for the user.*/
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
//if the current textfield is email and the user hits "return"
        if textField == email {
//this textfield is no longer the responder (blinking "|" bar)
            textField.resignFirstResponder()
//this textfield is now the responder
            password.becomeFirstResponder()
        } else if textField == password {
            textField.resignFirstResponder()
        }
//return true so that when the function is called again we know which
//responder to use
        return true
    }
}

/* This extension is used to control when the keyboard appears on
the screen, and when it disappears.*/
extension UIViewController {
//this function causes the keyboard to disappear when the user
//taps on the screen anywhere. It makes it much easier for users
//to hide the keyboard when they are done typing in the textfields.
    func hideKeyboardWhenTappedAround() {
        let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
        tap.cancelsTouchesInView = false
        view.addGestureRecognizer(tap)
    }
    @objc func dismissKeyboard() {
        view.endEditing(true)
    }
}

RegisterViewController

import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase

class registerViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate {
   //connecting textfields from Main.storyboard
    @IBOutlet weak var nameTF: UITextField!
    @IBOutlet weak var emailTF: UITextField!
    @IBOutlet weak var passwordTF: UITextField!
    @IBOutlet weak var confirmTF: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
//calling extension function created in LoginViewController
//extensions can be called anywhere
        self.hideKeyboardWhenTappedAround()
//setting the name/email/password/confirm password 
//delegates to the Register View Controller
        self.nameTF.delegate = self
        self.emailTF.delegate = self
        self.passwordTF.delegate = self
        self.confirmTF.delegate = self
    }

//this function does the same as the other textFieldShouldReturn function
//in the LoginViewController where every time the user hits the enter 
//button on the keyboard, the blinking "|" bar moves to the next textfield
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        if textField == nameTF {
            textField.resignFirstResponder()
            emailTF.becomeFirstResponder()
        } else if textField == emailTF {
            textField.resignFirstResponder()
            passwordTF.becomeFirstResponder()
        } else if textField == passwordTF {
            textField.resignFirstResponder()
            confirmTF.becomeFirstResponder()
        } else if textField == confirmTF {
            textField.resignFirstResponder()
        }
        return true
    }

//if there is a problem and we want to display an error message,
//we call this function. The UIAlertController displays a pop-up
//error message with the title "Oops!" and the userMessage underneath.
    func displayError(userMessage:String)
    {
        var myAlert = UIAlertController(title: "Oops!", message: userMessage, preferredStyle: UIAlertControllerStyle.alert)
        let okAction = UIAlertAction(title:"OK", style: UIAlertActionStyle.default, handler: nil)
        myAlert.addAction(okAction)
        self.present(myAlert, animated: true, completion: nil)
    }

//When the user is done filling out the registration form, and hits the
//done button, we first want to make sure there are no empty textfields,
//and that the password meets the requirements set by Firebase, as well as
//make sure the passwordTF and confirmTF match
    @IBAction func donePressed(_ sender: Any) {
        let email = emailTF.text!
        let password = passwordTF.text!
//if any of the textfields are empty
        if(nameTF.text?.isEmpty)! || (emailTF.text?.isEmpty)! || (passwordTF.text?.isEmpty)! || (confirmTF.text?.isEmpty)! {
            displayError(userMessage: "All fields are required.")
            return
        }
//if passwordTF and confirmTF do not match
        if(passwordTF.text! != confirmTF.text!)
        {
            displayError(userMessage: "Passwords do not match.")
            return
        }
//if the password does not meet security requirements
        if((passwordTF.text?.characters.count)! < 6) {
            displayError(userMessage: "Password must be at least 6 characters long.")
            return
        }

        //creating user by calling the createUser function provided by Firebase
        Auth.auth().createUser(withEmail: email, password: password) { (authResult, error) in
            // there is an error
            if(error != nil)
            {
                print("Error: \(String(describing: error?.localizedDescription))")
                self.dismiss(animated: true, completion: nil)
            } else {
                print("User Created!")
//save the data into the Firebase Database to use and refer to later. In order to do this we will create
//a path, and enter in the data using JSON format (key:value pairs)
                   Database.database().reference().child("users").child((Auth.auth().currentUser?.uid)!).setValue([
                   "Name" : self.nameTF.text!,
                   "Email" : self.emailTF.text!,
                   "Password" : self.passwordTF.text!,              
                   "UID" : (Auth.auth().currentUser?.uid)!
                   ])                       
            } //end else statement
        } //end Auth function
    }//end of done function

//if the user wants to exit the RegisterViewController
    @IBAction func cancelPressed(_ sender: Any) {
        self.dismiss(animated: true, completion: nil)
    }
}

HomeViewController:

import UIKit
import Firebase
import FirebaseDatabase
import FirebaseAuth

class HomeViewController: UIViewController {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var emailLabel: UILabel!
//create a variable to reference the database
    var ref: DatabaseReference!

    override func viewDidLoad() {
        super.viewDidLoad()
//setting ref to the start of the path that will lead to the Firebase Database
        ref = Database.database().reference()
//set userID equal to the current user who is logged in
       let userID = (Auth.auth().currentUser?.uid)!
/*call the path to to the current logged in user, and observe the elements
within the child node. The snapshot will hold all of the key:value pairs*/
       ref.child("users").child(userID).observe(.value) { (snapshot) in
//we only want the values, not the keys.
           let value = snapshot.value as? NSDictionary
//create variables to set the values in, casted as String
           let email = value?["Email"] as? String ?? ""
           let name = value?["Name"] as? String ?? ""
//assign the newly created variables to the labels displayed on the VC.
           self.emailLabel.text = email
           self.nameLabel.text = name
      }
    }

//Call this function when the user hits the sign out button
    @IBAction func signOutPressed(_ sender: Any) {
//this function was copied from the Firebase website
        let firebaseAuth = Auth.auth()
        do {
            try firebaseAuth.signOut()
            print("Signed out!")
//we want to call the unwind segue function we created in the
//LoginViewController to unwind everything until we are back at the login page.
            performSegue(withIdentifier: "unwindToVC1", sender: self)
//Print the error in the console if there is an error
        } catch let signOutError as NSError {
            print ("Error signing out: %@", signOutError)
        }
    }
}

Now run your program. You should be able to allow a user to create an account, and save that information in the Authentication page and Database in the Firebase Console, authenticate the user when signing in, grab user data from Firebase Database to display the current logged in user’s information on the home screen, and safely and securely log out of the application.