Firebase Login con Email y Password Xcode y SwiftUI
Firebase Login con Email y Password Xcode y SwiftUI

🔥 FIREBASE Authentication - LOGIN con Email y CONTRASEÑA en SwiftUI

FirebaseAuth en iOS lo usamos en nuestras apps para autenticar usuarios. Utilizamos el Proveedor de Email y Password para registrar a un user y así pueda tener una sesión abierta en nuestra app. También veremos como hacer Login y Logout con Firebase Authentication en iOS. Usaremos SwiftUI.

SwiftBeta

Tabla de contenido


👇 SÍGUEME PARA APRENDER SWIFTUI, SWIFT, XCODE, etc 👇
🤩 ¡Sígueme en Twitter!
▶️ ¡Suscríbete al canal!
🎁 ¡Apoya el canal!

Tutorial Firebase iOS, autenticación Email y Password con SwiftUI

Hoy lo que vamos a ver es a cómo crear un método de autenticación con Firebase. Lo primero que vamos a ver es a cómo registrar users y que luego estos users puedan hacer login y logout de su sesión dentro de nuestra app.

¿Por qué queremos aplicar autenticación a nuestra app? Solo dejaremos acceder a nuestra app si el usuario se ha registrado préviamente. Una vez ha creado su sesión podrá hacer login dentro de nuestra app y podrá empezar a guardar información en una base de datos (esto lo veremos en futuros posts).

Para crear nuestra app, vamos a crear una estructura de ficheros como la que os muestro en el siguiente diagrama.

Estructura de nuestra app (MVVM y Pattern Repository)
Estructura de nuestra app (MVVM y Pattern Repository)

Tendremos diferentes instancias con diferentes responsabilidades:

  • View, vistas que crearemos con SwiftUI y que llamaran a los ViewModel para lanzar alguna acción (por ejemplo, registrar a un user con email y password).
  • ViewModel, será una class conformando el protocolo ObservableObject ya que tendrá propiedades que al asignar valores refrescaran la vista para que se redibuje con la nueva información. También tendrá métodos que llamaran al Repository
  • Repository, tendremos esta capa que de momento llamará a los métodos que creemos en nuestro Datasource
  • Datasource, se encargará de importar los Frameworks de terceros como por ejemplo el de Firebase, FacebookLogin, etc

Una vez hemos visto la estructura que vamos a seguir, vamos a empezar!

Creamos las vistas de inicio, login y registro en SwiftUI

Lo primero de todo que vamos hacer es crear la vistas de nuestra app y para ser más concreto, crearemos la vista de inicio de nuestra app, la primera que aparecerá al User:

Creamos las vistas en SwiftUI
Creamos las vistas en SwiftUI

Es decir, si el user no tiene ninguna sesión creada, aparecerá una primera vista para que haga login en nuestra app o se registre. La vista de inicio va a ser la siguiente:

import SwiftUI

enum AuthenticationSheetView: String, Identifiable {
    case register
    case login
    
    var id: String {
        return rawValue
    }
}

struct AuthenticationView: View {
    @State private var authenticationSheetView: AuthenticationSheetView?
    
    var body: some View {
        VStack {
            Image("swiftbeta")
                .resizable()
                .frame(width: 200, height: 200)
            VStack {
                Button(action: {
                    authenticationSheetView = .login
                }, label: {
                    Label("Entra con Email", systemImage: "envelope.fill")
                })
                .tint(.black)
            }
            .controlSize(.large)
            .buttonStyle(.bordered)
            .buttonBorderShape(.capsule)
            .padding(.top, 60)
            Spacer()
            HStack {
                Button {
                    authenticationSheetView = .register
                } label: {
                    Text("¿No tienes cuenta?")
                    Text("Regístrate")
                        .underline()
                }
                .tint(.black)
            }
        }
        .sheet(item: $authenticationSheetView) { sheet in
            switch sheet {
            case .register:
                RegisterEmailView()
            case .login:
                LoginEmailView()
            }
        }
    }
}
AuthenticationView será nuestra vista de inicio en SwiftUI

He añadido una imagen de SwiftBeta para darle más estilo a la vista y también he añadido una systemImage de un sobre para que el button tenga más gracia.

Y la vista de login:

import SwiftUI

struct LoginEmailView: View {
    @State var textFieldEmail: String = ""
    @State var textFieldPassword: String = ""
    
    var body: some View {
        VStack {
            DismissView()
                .padding(.top, 8)
            Group {
                Text("👋 Bienvenido de nuevo a")
                Text("SwiftBeta")
                    .bold()
                    .underline()
            }
            .padding(.horizontal, 8)
            .multilineTextAlignment(.center)
            .font(.largeTitle)
            .tint(.primary)
            Group {
                Text("Loguéate de nuevo para poder acceder a todos tus links.")
                    .tint(.secondary)
                    .multilineTextAlignment(.center)
                    .padding(.top, 2)
                    .padding(.bottom, 32)
                TextField("Añade tu correo electrónico", text: $textFieldEmail)
                TextField("Añade tu contraseña", text: $textFieldPassword)
                Button("Login") {
                    print("Login")
                }
                .padding(.top, 18)
                .buttonStyle(.bordered)
                .tint(.blue)
            }
            .textFieldStyle(.roundedBorder)
            .padding(.horizontal, 64)
            Spacer()
        }
    }
}
LoiginEmailView en SwiftUI

Y la vista de registro que será muy parecida a la de Login, solo cambiaremos algún mensaje:

import SwiftUI

struct RegisterEmailView: View {
    @State var textFieldEmail: String = ""
    @State var textFieldPassword: String = ""
    
    var body: some View {
        VStack {
            DismissView()
                .padding(.top, 8)
            Group {
                Text("👋 Bienvenido a")
                Text("SwiftBeta")
                    .bold()
                    .underline()
            }
            .padding(.horizontal, 8)
            .multilineTextAlignment(.center)
            .font(.largeTitle)
            .tint(.primary)
            Group {
                Text("Regístrate para guardar todos tus enlaces en una sola app.")
                    .tint(.secondary)
                    .multilineTextAlignment(.center)
                    .padding(.top, 2)
                    .padding(.bottom, 32)
                TextField("Añade tu correo electrónico", text: $textFieldEmail)
                TextField("Añade tu contraseña", text: $textFieldPassword)
                Button("Aceptar") {
                    print("Aceptar")
                }
                .padding(.top, 18)
                .buttonStyle(.bordered)
                .tint(.blue)
            }
            .textFieldStyle(.roundedBorder)
            .padding(.horizontal, 64)
            Spacer()
        }
    }
}
RegisterEmailView en SwiftUI

Hemos creado también una vista helper, para poder dismisear vistas que hemos presentado con el modificador .sheet

import SwiftUI

struct DismissView: View {
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        HStack {
            Spacer()
            Button("Cerrar") {
                dismiss()
            }
            .tint(.black)
            .padding(.trailing, 12)
        }
        .textFieldStyle(.roundedBorder)
        .buttonStyle(.bordered)
    }
}
Vista DismissView para reutilizar en otras vistas en SwiftUI

Creamos el Datasource

Lo siguiente que vamos hacer es crear un fichero llamado AuthenticationFirebaseDatasource e importar la dependencia FirebaseAuth

Creamos el Datasource
Creamos el Datasource

Justo en el post anterior importamos solo las de Firebase Analytics, es por eso que al importar en esta nueva clase import FirebaseAuth nos fallará (no such module FirebaseAuth), así que vamos a arreglarlo.

Para importar estas dependencia tenemos que ir a nuestro proyecto, vamos al target de ReadLater y en la sección General hay una sección que tiene de título Frameworks, Libraries, and Embedded Content

Añadimos FirebaseAuth en la sección de Frameworks, Libraries and Embedded Content
Añadimos FirebaseAuth en la sección de Frameworks, Libraries and Embedded Content

Allí vamos a añadir solo la dependencia que necesitamos, FirebaseAuth

Buscamos FirebaseAuth en la pantalla que aparece en Xcode
Buscamos FirebaseAuth en la pantalla que aparece en Xcode

Una vez añadida, en nuestro datasource desaparece el error que teníamos. Vamos a seguir añadiendo código en nuestro AuthenticationFirebaseDatasource, aquí lo que queremos es crear una capa de separación con Firebase, es decir las capas superiores no sabrán nada de Firebase y por lo tanto no tendrán que importarlo por que no lo necesitaran.

Crearemos un modelo llamado User que es lo que enviaremos a las capas superiores, este modelo User no tiene nada que ver con Firebase, creamos este modelo en nuestro dominio para evitar tirar hacía las capas superiores un modelo de Firebase. Es decir, el respositorio recibirá este User, que se lo enviará al ViewModel y en el ViewModel se le asignará a una propiedad @Published que hará que pase algo en la vista.
Pero vamos por partes, lo primero es crear la lógica de crear un nuevo User en nuestro AuthenticationFirebaseDatasource.

import Foundation
import FirebaseAuth

struct User {
    let email: String
}

final class AuthenticationFirebaseDatasource {
    func createNewUser(email: String, password: String, completionBlock: @escaping (Result<User, Error>) -> Void) {
        Auth.auth().createUser(withEmail: email, password: password) { authDataResult, error in
            if let error = error {
                print("Error creating a new user \(error.localizedDescription)")
                completionBlock(.failure(error))
                return
            }
            let email = authDataResult?.user.email ?? "No email"
            print("New user created with info \(email)")
            completionBlock(.success(.init(email: email)))
        }
    }
}
Datasource de nuestra app, aquí cremos a un nuevo user en Firebase con un Email y Password

Creamos el Repository

A continuación vamos a crear nuestro repositorio.

Creamos el Repository
Creamos el Repository

Lo vamos a llamar AuthenticationRepository y lo que hará será llamar a los métodos de nuestro AuthenticationFirebaseDatasource:

import Foundation

final class AuthenticationRepository {
    private let authenticationFirebaseDatasource: AuthenticationFirebaseDatasource
    
    init(authenticationFirebaseDatasource: AuthenticationFirebaseDatasource = AuthenticationFirebaseDatasource()) {
        self.authenticationFirebaseDatasource = authenticationFirebaseDatasource
    }
    
    func createNewUser(email: String, password: String, completionBlock: @escaping (Result<User, Error>) -> Void) {
        authenticationFirebaseDatasource.createNewUser(email: email,
                                                       password: password,
                                                       completionBlock: completionBlock)
    }
}
Llamamos al método del Datasource dentro de nuestro Repository

Creamos el ViewModel

Y finalmente vamos a crear nuestro ViewModel, lo vamos a llamar AuthenticationViewModel

Finalmente, creamos el ViewModel
Finalmente, creamos el ViewModel

nuestro ViewModel llamará al método que tenemos en el repositorio que hemos creado en el paso anterior. Al registrar un nuevo usuario de forma correcta, se almacenará en la propiedad User que hará que la vista se refresque, y en caso de error al registrar un nuevo usuario (el email no está bien creado, la password no tiene un mínimo de caracteres, etc) lo mostraremos por pantalla.

import Foundation

final class AuthenticationViewModel: ObservableObject {
    @Published var user: User?
    @Published var messageError: String?
    private let authenticationRepository: AuthenticationRepository
    
    init(authenticationRepository: AuthenticationRepository = AuthenticationRepository()) {
        self.authenticationRepository = authenticationRepository
    }
    
    func createNewUser(email: String, password: String) {
        authenticationRepository.createNewUser(email: email, password: password) { [weak self] result in
            guard let self = self else { return }
            switch result {
            case .success(let newUser):
                self.user = newUser
            case .failure(let error):
                self.messageError = error.localizedDescription
            }
        }
    }
}
Creamos nuestro ViewModel para llamar al método del repositorio y guardar el resultado de la creación de un nuevo User

Ahora lo que tenemos conectado es el ViewModel con el Repositorio y el Repositorio con el Datasource, lo que tenemos que hacer es conectar nuestro ViewModel a la vista.

Para hacerlo, nos vamos a ReadLaterApp, allí vamos a crear una propiedad @StateObject de tipo AuthenticationViewModel, y dentro de WindowGroup vamos hacer los siguiente:

@main
struct ReadLaterApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    @StateObject var authenticationViewModel = AuthenticationViewModel()
    
    var body: some Scene {
        WindowGroup {
            if let user = authenticationViewModel.user {
                Text("User logged! \(user.email)")
            } else {
                AuthenticationView(authenticationViewModel: authenticationViewModel)
            }
        }
    }
}
Diferenciamos si hay una sesión activa para mostrar una vista u otra en nuestra app

Al hacerlo de esta manera, cuando un usuario se registre correctamente, la vista de registro desparecerá y aparecerá una nueva donde en el centro aparece User logged! swiftbeta.blog@gmail.com

Al hacer esto, tenemos que pasar la instancia de nuestro AuthenticationViewModel en la vista de AuthenticationView y en la vista de RegisterEmailView, así que AuthenticationView quedaría:

enum AuthenticationSheetView: String, Identifiable {
    case register
    case login
    
    var id: String {
        return rawValue
    }
}

struct AuthenticationView: View {
    @ObservedObject var authenticationViewModel: AuthenticationViewModel
    @State private var authenticationSheetView: AuthenticationSheetView?
    
    var body: some View {
        VStack {
            Circle()
                .frame(width: 200, height: 200)
            VStack {
                Button("Entra con Email") {
                    authenticationSheetView = .login
                }
                .tint(.black)
            }
            .controlSize(.large)
            .buttonStyle(.bordered)
            .buttonBorderShape(.capsule)
            .padding(.top, 60)
            Spacer()
            HStack {
                Button {
                    authenticationSheetView = .register
                } label: {
                    Text("¿No tienes cuenta?")
                    Text("Regístrate")
                        .underline()
                }
                .tint(.black)
            }
        }
        .sheet(item: $authenticationSheetView) { sheet in
            switch sheet {
            case .register:
                RegisterEmailView(authenticationViewModel: authenticationViewModel)
            case .login:
                LoginEmailView()
            }
        }
    }
}

struct AuthenticationView_Previews: PreviewProvider {
    static var previews: some View {
        AuthenticationView(authenticationViewModel: AuthenticationViewModel())
    }
}
Conectamos nuestra vista con nuestro ViewModel

y RegisterEmailView quedaría de la siguiente manera:

import SwiftUI

struct RegisterEmailView: View {
    @ObservedObject var authenticationViewModel: AuthenticationViewModel
    @State var textFieldEmail: String = ""
    @State var textFieldPassword: String = ""
    
    init(authenticationViewModel: AuthenticationViewModel) {
        self.authenticationViewModel = authenticationViewModel
    }
    
    var body: some View {
        VStack {
            DismissView()
                .padding(.top, 8)
            Group {
                Text("👋 Bienvenido a")
                Text("SwiftBeta")
                    .bold()
                    .underline()
            }
            .padding(.horizontal, 8)
            .multilineTextAlignment(.center)
            .font(.largeTitle)
            .tint(.primary)
            Group {
                Text("Regístrate para guardar todos tus enlaces en una sola app.")
                    .tint(.secondary)
                    .multilineTextAlignment(.center)
                    .padding(.top, 2)
                    .padding(.bottom, 32)
                TextField("Añade tu correo electrónico", text: $textFieldEmail)
                TextField("Añade tu contraseña", text: $textFieldPassword)
                Button("Aceptar") {
                    authenticationViewModel.createNewUser(email: textFieldEmail, password: textFieldPassword)
                }
                .padding(.top, 18)
                .buttonStyle(.bordered)
                .tint(.blue)
                if let messageError = authenticationViewModel.messageError {
                    Text(messageError)
                        .bold()
                        .font(.body)
                        .foregroundColor(.red)
                        .padding(.top, 20)
                }
            }
            .textFieldStyle(.roundedBorder)
            .padding(.horizontal, 64)
            Spacer()
        }
    }
}
Conectamos nuestra vista RegisterEmailView con nuestro ViewModel

En este último fíjate que hemos añadido:

  • Una propiedad @ObservedObject con la instancia de AuthenticationViewModel
  • Hemos llamado al método createNewUser cuando el button de Login es pulsado
  • Si al registrar un nuevo User tenemos algún error, mostramos un mensaje en la vista.

Vamos a ver qué pasos debemos seguir a continuación.

Habilitar Correo electrónico/contraseña en Firebase como sistema de autenticación

Una vez hemos añadidos nuestro Datasource -> Repository -> ViewModel y hemos conectado el ViewModel al RegisterEmailView, tenemos que ir a la consola de Firebase, y una vez allí seleccionamos del menú lateral Authentication

En la consola de Firebase vamos a la sección de Authentication de Firebase
En la consola de Firebase vamos a la sección de Authentication de Firebase

Al seleccionar Authentication le damos al button que aparece de Comenzar. Ahora nos aparece un vista para agreemos el método de autenticación de nuestra app. En nuestro caso, vamos a escoger de la sección de Proveedores nativos el de Correo Electrónico/contraseña.

Activamos el Proveedor Correo Electrónico y Contraseña
Activamos el Proveedor Correo Electrónico y Contraseña

Una vez seleccionado,  lo habilitamos y pulsamos en Guardar

Le damos a Habilitar y luego a Guardar. Así tendremos nuestro primero proveedor de autenticación
Le damos a Habilitar y luego a Guardar. Así tendremos nuestro primero proveedor de autenticación

Ahora en Firebase podemos usar (de momento) el Proveedor de Correo electrónico/contraseña

Nuevo proveedor de autenticación habilitado en Firebase
Nuevo proveedor de autenticación habilitado en Firebase

Si vamos a la pestaña de Users, vemos que no hay ningún usuario que se haya registrado aún en nuestra app. Vamos a compilar la app y vamos a registrarnos!

De momento no tenemos ningún User registrado
De momento no tenemos ningún User registrado

Registro de nuevos users en Firebase

Ahora que tenemos todo listo:

  • Vista de registro conectada con el ViewModel que a su vez está conectado con el Repositorio y que el repositorio está conectado con el datasource, que hará la llamada al SDK de Firebase para añadir un nuevo user con un email y una password. (En caso de error lo controlaremos.)
  • Habilitado el Proveedor de Correo electrónico/contraseña

Compilamos la aplicación, nos aparece la vista de Inicio, donde podemos seleccionar el login o registro por email. Seleccionamos el registro ya que el login aún no lo tenemos implementado, lo haremos justo después de registrar nuestro User.

Simulador mostrando Entra con Email
Simulador mostrando Entra con Email

Clickamos en Regístrate y nos aparece la pantalla de registro

Simulador mostrando la pantalla de regístrate
Simulador mostrando la pantalla de regístrate

Vamos a añadir nuestro correo electrónico y una password, al acabar pulsamos en el button de Login. Si todo funciona bien deberíamos ver que nuestra vista de registro desaparece y muestra el Text que hemos puesto en ReadLaterApp.

Simulador mostrando la pantalla que aparece cuando un User tiene una sesión activa
Simulador mostrando la pantalla que aparece cuando un User tiene una sesión activa

Parece que funciona perfectamente, vamos a ver nuestra consola de Firebase y vamos a confirmar que aparece un nuevo User.

En la sección de Users nos aparece nuestro primer User registrado en nuestra app
En la sección de Users nos aparece nuestro primer User registrado en nuestra app

Y aquí lo tenemos! acabamos de registrar nuestro primer user en nuestra app! 🎉
Vamos a continuar.

Cargar user si hay una sesión activa

Si ahora compilamos de nuevo la app, vemos que nos lleva otra vez a la pantalla de inicio (en la que podíamos escoger el login y registro), pero en principio ya tenemos una sesión abierta, ya que de momento no hemos hecho Logout (crearemos esta lógica en breve). Vamos a arreglar que aunque lancemos otra vez la app, si hay una sesión abierta la cargue y muestre la vista con el Text User logged! swiftbeta.blog@gmail.com.

Nos vamos al datasource, ya que como comentaba antes, es la única clase que puede utilizar Firebase, las demás son agnósticas. Y vamos a crear un método nuevo para que nos retorne el User de la sesión actual, y este User vamos hacer como antes, lo vamos a transformar, lo vamos a mapear a un objeto de nuestro dominio llamado User donde solo tiene una propiedad de tipo String, llamada email. Pues en AuthenticationFirebaseDatasource creamos un nuevo método.

    func getCurrentUser() -> User? {
        guard let email = Auth.auth().currentUser?.email else {
            return nil
        }
        return .init(email: email)
    }
Obtenemos el email del currentUser de la sesión de Firebase actual

Vamos a crear un método en AuthenticationRepository que llame al método que acabamos de crear en nuestro datasource.

    func getCurrentUser() -> User? {
        authenticationFirebaseDatasource.getCurrentUser()
    }
Creamos el método en el repositorio

Y finalmente lo vamos a crear en nuestro AuthenticationViewModel:

    init(authenticationRepository: AuthenticationRepository = AuthenticationRepository()) {
        self.authenticationRepository = authenticationRepository
        getCurrentUser()
    }
    
    func getCurrentUser() {
        self.user = authenticationRepository.getCurrentUser()
    }
Creamos el método en el ViewModel

Fíjate, que aparte de crear el método getCurrentUser para recibir el User de la sesión actual, también llamamos al método al inicializar el ViewModel.

Creamos el Logout en FirebaseAuth

Una vez estamos logueados nos aparece una pantalla con un simple Text enseñando nuestro email. Vamos a crear una vista llamada HomeView y aparte de mostrar el mismo Text vamos a mostrar un Button que haga Logout. Pero antes vamos a crear el mismo proceso que antes, vamos a crear el método de logout en el datasource, luego en el repo, luego en el viewmodel y a continuación crearemos la nueva vista HomeView que al pulsar el button de Logout llamará al método del ViewModel.

En AuthenticationFirebaseDatasource añadimos:

    func logout() throws {
        try Auth.auth().signOut()
    }
Logout de Firebase en iOS

En el repositorio de AuthenticationRepository añadimos:

    func logout() throws {
        try authenticationFirebaseDatasource.logout()
    }
Creamos el método en el Repository que llame al logout

Y en el ViewModel creamos un nuevo método:

    func logout() {
        do {
            try authenticationRepository.logout()
            self.user = nil
        } catch {
            print("Error logout")
        }
    }
Creamos el método en el ViewModel que llame al logout del Repository

Fíjate que aquí hemos usado do y catch para ver si ha habido un error al cerrar la sesión al User. En caso de que todo vaya bien, asignamos a User nil para que vuelva aparecer la pantalla de Inicio de la app. Y en caso de error, mostramos un print por consola.

Cuando hemos acabado de añadir este nuevo método, creamos la vista HomeView:

import SwiftUI

struct HomeView: View {
    @ObservedObject var authenticationViewModel: AuthenticationViewModel
    
    var body: some View {
        NavigationView {
            VStack {
                Text("Bienvenido \(authenticationViewModel.user?.email ?? "No user")")
                    .padding(.top, 32)
                Spacer()
            }
            .navigationBarTitleDisplayMode(.inline)
            .navigationTitle("Home")
            .toolbar {
                Button("Logout") {
                    authenticationViewModel.logout()
                }
            }
        }
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView(authenticationViewModel: AuthenticationViewModel())
    }
}
Creamos la vista de HomeView que mostraremos cuando el User tenga una sesión activa

Y por lo tanto modificamos nuestro fichero (y struct) ReadLaterApp:

@main
struct ReadLaterApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    @StateObject var authenticationViewModel = AuthenticationViewModel()
    
    var body: some Scene {
        WindowGroup {
            if let _ = authenticationViewModel.user {
                HomeView(authenticationViewModel: authenticationViewModel)
            } else {
                AuthenticationView(authenticationViewModel: authenticationViewModel)
            }
        }
    }
}
Reemplazamos el Text por la vista de HomeView en SwiftUI

Creamos la lógica del Login con FirebaseAuth en SwiftUI

Lo último que nos falta por crear es el Login de nuestra app. Es decir, hasta ahora hemos creado el registro y hemos hecho el Logout. Vamos a crear el Login. Esta va a ser la parte más fácil, ya que es muy parecido a los pasos del Registro. Lo primero de todo que vamos hacer es crear un método en AuthenticationFirebaseDatasource:

    func login(email: String, password: String, completionBlock: @escaping (Result<User, Error>) -> Void) {
        Auth.auth().signIn(withEmail: email, password: password) { authDataResult, error in
            if let error = error {
                print("Error creating a new user \(error.localizedDescription)")
                completionBlock(.failure(error))
                return
            }
            let email = authDataResult?.user.email ?? "No email"
            print("New user created with info \(email)")
            completionBlock(.success(.init(email: email)))
        }
    }
Creamos el Login con Firebase con Email y Password

En nuestro repositorio vamos a crear un método que llame al nuevo método que acabamos de crear en nuestro datasource:

    func login(email: String, password: String, completionBlock: @escaping (Result<User, Error>) -> Void) {
        authenticationFirebaseDatasource.login(email: email,
                                               password: password,
                                               completionBlock: completionBlock)
    }
Creamos el método de Login en el Repository

Y finalmente lo creamos en nuestro AuthenticationViewModel:

    func login(email: String, password: String) {
        authenticationRepository.login(email: email, password: password) { [weak self] result in
            guard let self = self else { return }
            switch result {
            case .success(let newUser):
                self.user = newUser
            case .failure(let error):
                self.messageError = error.localizedDescription
            }
        }
    }
Creamos el método de Login en el ViewModel

Finalmente, AuthenticationViewModel se lo inyectamos a nuestra vista LoginEmailView:

import SwiftUI

struct LoginEmailView: View {
    @ObservedObject var authenticationViewModel: AuthenticationViewModel
    @State var textFieldEmail: String = ""
    @State var textFieldPassword: String = ""
    
    var body: some View {
        VStack {
            DismissView()
                .padding(.top, 8)
            Group {
                Text("👋 Bienvenido de nuevo a")
                Text("SwiftBeta")
                    .bold()
                    .underline()
            }
            .padding(.horizontal, 8)
            .multilineTextAlignment(.center)
            .font(.largeTitle)
            .tint(.primary)
            Group {
                Text("Loguéate de nuevo para poder acceder a todos tus links.")
                    .tint(.secondary)
                    .multilineTextAlignment(.center)
                    .padding(.top, 2)
                    .padding(.bottom, 32)
                TextField("Añade tu correo electrónico", text: $textFieldEmail)
                TextField("Añade tu contraseña", text: $textFieldPassword)
                Button("Login") {
                    authenticationViewModel.login(email: textFieldEmail, password: textFieldPassword)
                }
                .padding(.top, 18)
                .buttonStyle(.bordered)
                .tint(.blue)
                if let messageError = authenticationViewModel.messageError {
                    Text(messageError)
                        .bold()
                        .font(.body)
                        .foregroundColor(.red)
                        .padding(.top, 20)
                }
            }
            .textFieldStyle(.roundedBorder)
            .padding(.horizontal, 64)
            Spacer()
        }
    }
}

struct LoginEmailView_Previews: PreviewProvider {
    static var previews: some View {
        LoginEmailView(authenticationViewModel: AuthenticationViewModel())
    }
}
Actualizamos la vista LoginEmailView en SwiftUI

Fíjate que:

  • Hemos creado la propiedad AuthenticationViewModel
  • Hemos conectado el button Login con el método login de nuestro AuthenticationViewModel
  • Hemos añadido un Text en caso de recibir un error de Firebase al hacer login, así damos feedback al user de qué está pasando.

Finalmente, tenemos que pasarle la instancia de AuthenticationViewModel a nuestra vista LoginEmailView y quedaría de la siguiente manera:

struct AuthenticationView: View {
    @ObservedObject var authenticationViewModel: AuthenticationViewModel
    @State private var authenticationSheetView: AuthenticationSheetView?
    
    var body: some View {
        VStack {
            Circle()
                .frame(width: 200, height: 200)
            VStack {
                Button("Entra con Email") {
                    authenticationSheetView = .login
                }
                .tint(.black)
            }
            .controlSize(.large)
            .buttonStyle(.bordered)
            .buttonBorderShape(.capsule)
            .padding(.top, 60)
            Spacer()
            HStack {
                Button {
                    authenticationSheetView = .register
                } label: {
                    Text("¿No tienes cuenta?")
                    Text("Regístrate")
                        .underline()
                }
                .tint(.black)
            }
        }
        .sheet(item: $authenticationSheetView) { sheet in
            switch sheet {
            case .register:
                RegisterEmailView(authenticationViewModel: authenticationViewModel)
            case .login:
                LoginEmailView(authenticationViewModel: authenticationViewModel)
            }
        }
    }
}
Actualizamos la vista AuthenticationView en SwiftUI para pasarle la instancia de authenticationViewModel a la vista RegisterEmailView

Si ahora compilamos nuestra app, vamos a probar que podemos hacer un Login. Al ejecutar la app en el simulador nos aparece la pantalla de inicio, aquí vamos a escoger Entrar con Email para hacer el Login. Si probamos con los credenciales de nuestro User registrado hace el Login perfectamente 🎉

Si quieres que en la app no se vea la contraseña, en lugar de usar un TextField, puedes usar un SecureField

Al acabar de crear el login con email y password, voy a estructurar un poco nuestros ficheros en Xcode.

Añadimos un poco de orden a nuestros ficheros de Swift

Voy a dejar preparada esta estructura para futuros posts.

Conclusión

Hoy hemos visto a como registrar, loguear o desloguear a un user usando el proveedor Email y Password de Firebase dentro de nuestra app. Con esta autenticación creamos una capa extra de seguridad en nuestra app para solo permitir usuarios con permisos realicen ciertas acciones.