Un clon de Instagram usando SwiftUI y GraphQL – Login
En nuestro post anterior sobre cómo crear una aplicación clon de Instagram, aprendiste a configurar todo para tener SwiftUI funcionando en XCode 11, y creaste una vista de Sing Up completamente funcional con GraphQL.
Hoy vamos a aprender a crear una vista de inicio de sesión y hacer que el usuario cierre la sesión.
Necesitaremos el proyecto del post anterior, así que si no lo has seguido, te sugiero que lo hagas.
Abróchense los cinturones y ¡vamos allá!
Para un mejor aprendizaje, descarga el proyecto iOS Instagram Clone con el código fuente.
Contents
¿Quieres un comienzo rápido?
¡Clona esta App desde nuestro Hub y empieza a usarla sin complicaciones!
Creación de la vista de inicio de sesión
Nuestra vista Login será bastante similar a nuestra vista SignUp, incluso más simple en realidad.
En la mutación logInUser sólo necesitamos dos parámetros: nombre de usuario y contraseña:
Las consultas y mutaciones dependerán de la versión de Parse que hayamos elegido:
Parse 3.7.2:
mutación logInUser($nombredeusuario: ¡Cadena!, $contraseña: ¡Cadena!){ usuarios{ logIn(nombredeusuario: $nombredeusuario, contraseña: $contraseña){ sessionToken } } }
Parse 3.8:
mutación logInUser($nombredeusuario: ¡cadena!, $contraseña: ¡cadena!){ logIn(nombredeusuario: $nombredeusuario, contraseña: $contraseña){ sessionToken } }
Parse 3.9:
mutación logInUser($nombredeusuario: ¡cadena!, $contraseña: ¡cadena!){ logIn(nombredeusuario: $nombredeusuario, contraseña: $contraseña){ sessionToken } }
por lo que sólo tendremos que preguntar a nuestros usuarios.
Empecemos añadiendo una nueva vista SwiftUI yendo a Archivo > Nuevo > Archivo y seleccionando nuestra vista SwiftUI.
Llamemos a esta vista LogInView.swift y abrámosla en nuestro proyecto:
Y como ya aprendiste, crea tu VStack con los controles que necesitaremos:
- TextField para nombre de usuario
- SecureField para la contraseña
- Button para realizar la acción
Para mantener el diseño consistente, he movido los colores que usaremos al AppDelegate.swift, así que he tenido que importar SwiftUI allí también:
import SwiftUI let lightGreyColor = Color(rojo: 239.0/255.0, verde: 243.0/255.0, azul: 244.0/255.0, opacidad: 1.0) let azul claroColor = Color(rojo: 36.0/255.0, verde: 158.0/255.0, azul: 235.0/255.0, opacidad: 1.0)
Recuerde eliminar las líneas de color de SignUpView.swift y LogInView.swift.
Además, con el fin de mantener la coherencia de los controles, me limité a copiar y pegar de nuestra vista SignUp y eliminé el TextField de correo electrónico y cambié los TextFields para reflejar las nuevas funcionalidades. Mi código terminó así
struct LogInView: View { @State var nombre de usuario: String = "" @Estado var password: String = "" @Estado private var showingAlert = false var body: alguna Vista { VStack{ Texto("Entrar") .font(.largeTitle) .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.bottom, 20) TextField("Nombre de usuario", text: $nombredeusuario) .padding() .background(colorgris claro) .cornerRadius(5.0) .padding(.bottom, 20) SecureField("Contraseña", text: $contraseña) .padding() .background(colorgris claro) .cornerRadius(5.0) .padding(.abajo, 20) Button(acción: { }){ Texto("¡Iniciar sesión!") .font(.titular) .foregroundColor(.blanco) .padding() .frame(anchura: 220, altura: 60) .background(colorazulclaro) .cornerRadius(15.0) } }.padding() } }
Esto ha sido sencillo. Veamos cómo queda.
Cambia tu ContentView.swift para mostrar esa vista en su lugar:
struct ContentView : View { var body: some View { EntrarVista() } }
¡Muy bonito!
¡Hagámoslo más ordenado añadiendo nuestro logo en la parte superior!
Nuestro logo consistirá en una imagen que integraremos en nuestro proyecto. Yo he utilizado ésta.
Arrastra y suelta esa imagen a tu carpeta Assets.xcassets en el proyecto:
Debería verse así:
A partir de ahora podemos referenciarla en nuestro código con ese nombre: logo-social.
Regístrate ahora en Back4App y empieza a crear tu aplicación clon de Instagram.
Nuestro Logo
Nuestro logo consistirá en esa imagen, pero simplemente poner la imagen ahí se vería amateur. Lo haremos brillar: circular, con un tamaño fijo, algún trazo en los bordes y, por supuesto, sombra por… sombra.
El código para todo eso tiene este aspecto
Imagen("logo-social") .redimensionable() .aspectRatio(contentMode: .fit) .frame(anchura: 150, altura: 150) .clipShape(Círculo()) .overlay(Circle().stroke(Color.blue, lineWidth: 2)) .shadow(radio: 5) .padding(.bottom, 75)
Y va en la parte superior de nuestro VStack:
var body: alguna Vista { VStack{ Imagen("logo-social") .redimensionable() .aspectRatio(contentMode: .fit) .frame(anchura: 150, altura: 150) .clipShape(Círculo()) .overlay(Circle().stroke(Color.blue, lineWidth: 2)) .shadow(radio: 5) .padding(.bottom, 75) Texto("Entrar") .font(.largeTitle) .foregroundColor(azul claro) .fontWeight(.semibold) .padding(.bottom, 20) ...
¿No es precioso?
El sessionToken
Nuestro proceso de login devolverá una cadena sessionToken. Debemos mantener ese sessionToken seguro ya que será utilizado durante nuestras operaciones: mientras ese sessionToken sea válido, tendremos acceso a nuestra aplicación. Cuando sea borrado o invalidado, nuestras llamadas serán denegadas.
Como esto está directamente relacionado con la seguridad, necesitaremos almacenarlo de forma segura. El lugar correcto para hacerlo es el Llavero en iOS.
Lo único que tiene el Llavero es que es difícil y aburrido de usar, así que decidí usar este wrapper para gestionarlo. Hace la vida mucho más fácil y como ya estamos usando Cocoapods, tiene todo el sentido.
Vamos a editar nuestro Podfile y añadir esto a nuestros pods:
pod 'SwiftKeychainWrapper'
luego actualicemos nuestros Pods con el comando
pod install
y finalmente, reabramos nuestro proyecto xcworkspace.
Ya estamos listos para utilizar nuestro motor Keychain para…
Almacenar el sessionToken
Según la documentación de nuestro nuevo pod, la forma de guardar un valor en el llavero es
KeychainWrapper.standard.set("SomeValue", forKey: "SomeKey")
pero vamos a añadirle algo de lógica.
Para empezar, vamos a importar nuestro wrapper:
import SwiftKeychainWrapper
Lo primero es que tendremos que llamar a nuestra mutación logInUser y cuando esta responda, almacenamos el sessionToken si lo hay. Si no, tenemos que notificar al usuario con una alerta.
Ahora, si recuerdas en nuestro artículo anterior, ya tenemos una alerta codificada incluyendo su estructura. Vamos a reutilizarlo quitando el código de abajo de nuestro SingUpView.swift y pasándolo a nuestro fichero AppDelegate.swift para que todas las vistas puedan acceder a él:
struct Mensaje { var alertTitle: String = "" var alertText: String = "" } var miMensaje = Mensaje()
Ahora, volviendo a nuestra lógica, lo primero que tenemos que hacer es determinar si el usuario ha rellenado las cajas de texto de nombre de usuario y contraseña. Si no, no hay información para el proceso de login y debemos notificar al usuario sobre eso.
Nuestro código de acción para el botón de inicio de sesión debe comprobarlo. Añadamos este trozo de código que comprueba el tamaño de la cadena de las variables de estado vinculadas a esos campos de texto:
Button(action: { // Comprueba si hay una contraseña escrita if (self.password.count == 0 || self.username.count == 0){ // Si no, debemos mostrar la Alerta myMessage.alertText = "Debe proporcionar un Nombre de usuario y una Contraseña". myMessage.alertTitle = "Oops..." self.showingAlert = true } else { // Si es así, podemos continuar } }){ Texto("¡Iniciar sesión!") .font(.headline) .foregroundColor(.blanco) .padding() .frame(anchura: 220, altura: 60) .background(colorazulclaro) .cornerRadius(15.0) } .alert(isPresented: $showingAlert) { Alert(title: Text(miMensaje.alertTitle), mensaje: Text(myMessage.alertText), dismissButton: .default(Text("OK")) }
y pruébalo…
¡Muy bien!
Ahora debemos llamar a nuestra mutación GraphQL para recuperar el sessionToken y, si lo obtenemos, almacenarlo en el Llavero.
Ya has aprendido a llamar a las Mutaciones así que vamos a hacerlo, pero esta vez si obtenemos el sessionToken, lo almacenaremos:
// Realiza la mutación LogInUser, pasándole los parámetros que acabamos de obtener de nuestros TextFields apollo.perform(mutation: LogInUserMutation(username: self.username, password: self.password)){ result in // Cambiemos el resultado para poder separar un resultado exitoso de un error switch resultado { // En caso de éxito case .success(let graphQLResult): // Intentamos parsear nuestro resultado if let sessionToken = graphQLResult.data?.users?.logIn.sessionToken { myMessage.alertTitle = "¡Yay!" myMessage.alertText = "¡El usuario ha iniciado sesión!" self.showingAlert = true print ("Usuario sessionToken " + sessionToken) // Escribir el sessionToken en nuestro Llavero let _: Bool = KeychainWrapper.standard.set(sessionToken, forKey: "Back4Gram.sessionToken") } // pero en caso de cualquier error GraphQL presentamos ese mensaje else if let errors = graphQLResult.errors { // Errores GraphQL myMessage.alertTitle = "¡Ups!" myMessage.alertText = "Tenemos un error GraphQL: " + errors.description self.showingAlert = true print(errores) } // En caso de fallo, presentamos ese mensaje case .failure(let error): // Errores de red o de formato de respuesta myMessage.alertTitle = "¡Uy!" myMessage.alertText = "Tenemos un error: " + error.localizedDescription self.showingAlert = true print(error) } }
¡Vamos a probarlo!
¡Genial! ¿Pero cómo sabemos que realmente ha funcionado?
Podemos ir al Parse Dashboard de nuestra App y comprobar si hay un nuevo objeto Session escrito:
¡Como un encanto!
Y ya que estamos aquí…
¿Qué tal si añadimos un botón para cerrar la sesión? Sólo para probar, así sabremos que todo va como la seda:
Button(action: { // Comprueba si hay sessionToken storeStringhould sólo cerrar sesión si se ha iniciado sesión. if (KeychainWrapper.standard.string(forKey: "Back4Gram.sessionToken") != nil) { print("¡Encontrado sessionToken! Podemos cerrar la sesión.") // Realiza la mutación LogOutUser apollo.perform(mutación: LogOutUserMutation()){ resultado en // Cambiemos el resultado para poder separar uno exitoso de un error switch resultado { // En caso de éxito case .success(let graphQLResult): // Intentamos parsear nuestro resultado if let resultado = graphQLResult.data?.users?.logOut { if (resultado) { myMessage.alertTitle = "¡Yay!" myMessage.alertText = "¡El usuario ha cerrado sesión!" self.showingAlert = true // Borrar el sessionToken almacenado let _: Bool = KeychainWrapper.standard.set("", forKey: "Back4Gram.sessionToken") } else { myMessage.alertTitle = "¡Ups!" myMessage.alertText = "La operación de cierre de sesión del usuario ha devuelto False." self.showingAlert = true } } // pero en caso de cualquier error GraphQL presentamos ese mensaje else if let errors = graphQLResult.errors { // Errores GraphQL myMessage.alertTitle = "¡Ups!" myMessage.alertText = "Tenemos un error GraphQL: " + errors.description self.showingAlert = true print(errores) } // En caso de fallo, presentamos ese mensaje case .failure(let error): // Errores de red o de formato de respuesta myMessage.alertTitle = "¡Uy!" myMessage.alertText = "Tenemos un error: " + error.localizedDescription self.showingAlert = true print(error) } } } else { // Errores de red o de formato de respuesta myMessage.alertTitle = "¡Ups!" myMessage.alertText = "Parece que el usuario no ha iniciado sesión." self.showingAlert = true } }){ Texto("Cerrar sesión") .font(.titular) .foregroundColor(.white) .padding() .frame(anchura: 220, altura: 60) .background(colorazulclaro) .cornerRadius(15.0) } .alert(isPresented: $showingAlert) { Alert(title: Text(miMensaje.alertTitle), mensaje: Text(myMessage.alertText), dismissButton: .default(Text("OK")) }
Una vez más, ¡vamos a probarlo!
¡Genial!
Más adelante moveremos el botón de cerrar sesión a otro lugar, pero por ahora funciona para mostrar que nuestro flujo está funcionando.
¿Pero qué pasa con nuestro objeto Session ahora que hemos cerrado la sesión?
Se ha ido automáticamente, como esperábamos.
Conclusión
¡Enhorabuena! Ya tienes implementadas las funcionalidades de login y logout. ¡No solo eso, sino que aprendiste como llamar diferentes mutaciones, validar resultados y almacenar valores en el Llavero! ¡Es increíble!
En el próximo capítulo, comenzaremos a trabajar con múltiples vistas y a construir nuestra vista principal.
¡Permanece atento!
Referencia
- La parte 1 de esta serie es Instagram Clone usando Swift UI y GraphQL.
- Parte 2 es Instagram Login usando Swift UI y GraphQL.
- Parte 3 es Vista de Perfil usando Swift UI y GraphQL.
- Parte 4 es Instagram Clone Home View.
- Descarga un proyecto iOS Instagram Clone con el código fuente y empieza a usar Back4App.
Regístrate ahora en Back4App y comienza a construir tu Instagram Clone App.
¿Qué es SwiftUI?
SwiftUI es una nueva forma de crear interfaces de usuario para aplicaciones en plataformas Apple. Permite a los desarrolladores determinar la interfaz de usuario mediante código Swift.
¿Qué es un sessionToken?
El proceso de inicio de sesión que estamos desarrollando devolverá una cadena llamada sessionToken. Debe ser segura. Si mantenemos la validez del sessionToken, tendremos acceso a la aplicación; de lo contrario, lo perderemos. Esto se relaciona con la seguridad.
¿Qué es Keychain?
Sabemos que sessionToken está relacionado con la seguridad de tu aplicación. Por lo tanto, debe almacenarse en un lugar seguro. Ese lugar seguro se llama llavero. Su uso puede ser un poco complicado y también aburrido.