Un clon de Instagram usando SwiftUI y GraphQL
Hoy empezamos una serie de entradas en el blog que te enseñarán a utilizar un montón de herramientas geniales para construir tu propia Red Social: una aplicación que se parezca a Instagram.
No ahorraremos en tecnología y usaremos lo último y lo mejor: Parse, GraphQL, algo de NodeJS y especialmente el (aún por lanzar) último framework de Apple SwiftUI.
Esto tomará algunos posts para ser completamente funcional, pero cuando lleguemos allí, te darás cuenta de lo simple que puede ser poner tus ideas en marcha con muy poco esfuerzo aquí en Back4app.
Para un mejor aprendizaje, descarga el proyecto iOS Instagram Clone con el código fuente.
Entonces, parece que es hora de…
Contents
¿Quieres un comienzo rápido?
¡Clona esta App desde nuestro Hub y empieza a usarla sin complicaciones!
Introducción
Bueno, probablemente ya conoces los enormes beneficios que Parse aporta a tu proceso de desarrollo, especialmente si está alojado en Back4app, ya que nuestro conjunto de herramientas aumenta la productividad en grandes cantidades.
Y nuestros últimos posts sobre GraphQL te mostraron lo fácil que puede ser mantener el desarrollo de APIs a lo largo del tiempo si lo utilizas. Si te perdiste esos posts, te sugiero encarecidamente que te tomes tu tiempo y los leas, ya que mejorarán en gran medida tu comprensión a medida que avanzamos.
Puedes leer más sobre GraphQL y también tenemos un post completo sobre cómo usar en el artículo Web API.
También puedes ver alguna integración con NodeJS en Funciones de Código Cloud leyendo GraphQL y NodeJS.
Eso nos deja con SwiftUI.
SwiftUI es, según Apple, “una forma innovadora, excepcionalmente simple de construir interfaces de usuario en todas las plataformas de Apple con el poder de Swift”.
A mi me gusta pensar que SwiftUI es más que eso.
Si tú, como yo, estás desarrollando software desde hace algún tiempo, probablemente sepas que la comunidad de desarrolladores siempre ha estado dividida entre la conveniencia de utilizar Storyboards, la interfaz de construcción de interfaz de usuario de arrastrar y soltar de Apple, o el poder de la interfaz de usuario programática.
SwiftUI viene a traer lo mejor de ambos mundos: es lo suficientemente fácil para que los principiantes aprendan, mientras que mantiene la capacidad de mantenimiento de una interfaz de usuario codificada.
Combina eso con la representación en tiempo real, por lo que los desarrolladores pueden ver fácilmente el resultado visual de lo que están codificando, y hacerlo de dos maneras, así que si cambias el código, se reflejará en la interfaz de usuario, y cambiar gráficamente la interfaz de usuario y se reflejará en el código, y lo que obtienes es una de las maneras más poderosas y fáciles de entregar interfaces de usuario bellamente diseñadas, pero mantenibles en el tiempo.
Entonces, ¿qué vas a necesitar?
En el momento de escribir este post, Apple todavía está probando la nueva versión beta de macOS Catalina, XCode 11, iOS 13 y algunos otros productos que deberían llegar a los estantes pronto.
Aunque puedes utilizar XCode 11 con macOS Mojave, necesitarás Catalina para poder renderizar las vistas previas de SwiftUI en tiempo real.
¿Sin Catalina? ¡No hay previsualizaciones para ti!
Esta es una decisión que tienes que tomar ya que encontré que el cambio de Bash a ZSH como shell oficial de macOS en Catalina rompió muchas de mis herramientas y scripts que uso para mi flujo de trabajo diario, decidí quedarme en Mojave por un tiempo. Depende de ti decidir si vas a actualizar o no.
Además, necesitará una cuenta Back4app con una App creada. Puedes aprender a crear tu primera App en la documentación de Back4app.
Por último, pero no menos importante, vamos a utilizar Apollo Client para integrar GraphQL a nuestro proyecto. Si no tienes experiencia con él, he escrito un post muy detallado sobre cómo configurarlo y usarlo Web Authentication.
Así que, antes de empezar, necesitarás
- XCode 11 instalado (con macOS Catalina si quieres ver Previews)
- Tu nueva App creada en Back4app
- Apollo Client instalado y configurado
Mi App se llamará Back4Gram, ya que se parecerá a Instagram.
Nuestra primera Clase
Siempre os hablo de cómo Parse crea la clase User, para que puedas dar de alta nuevos usuarios y autenticar a los ya existentes, y la clase Role, para que puedas agrupar usuarios con privilegios similares. Esto es genial porque, al menos para la autenticación, no tenemos que crear ninguna clase.
La clase User tiene tres propiedades creadas automáticamente, dos de ellas obligatorias y una opcional:
- nombre de usuario (obligatoria)
- contraseña (obligatoria)
- email (opcional)
Con esto es suficiente para que nuestra App registre y gestione usuarios.
Como probablemente sepas, la política de Apple sobre el almacenamiento de los correos electrónicos de los usuarios es muy estricta, por lo que te recomiendo que no pidas ni guardes esa información o podrías no ser aprobado.
Como esto es sólo un tutorial, voy a pedir al usuario que introduzca esa información, sólo para que puedas ver cómo funciona.
Clases de usuario y rol creadas automáticamente
Como si esto no fuera lo suficientemente útil, tu recién creada App ya tiene GraphQL Queries y Mutations creadas automáticamente para nosotros. Esto es cierto para nuestras clases creadas automáticamente y también para cualquier otra clase que crees con el tiempo. ¡También te cubrimos con la documentación!
¿Te he dicho que también tenemos Autocompletar? Sí, lo tenemos.
Así que, mi primera mutación será como la anterior, y utilizaré nuestra mutación específica signUp() para crear un nuevo usuario:
La consulta dependerá de la versión de Parse que estés utilizando:
Parse 3.7.2:
mutación signUpUser($nombredeusuario: ¡Cadena!, $contraseña: ¡Cadena!, $email: Cadena) { usuarios { registrarse( campos: { nombre_usuario: $nombre_usuario, contraseña: $contraseña, email: $email } ) { objectId } } }
Parse 3.8:
mutación signUpUser($username: ¡Cadena!, $password: ¡Cadena!, $email: Cadena) { firmar( campos: { nombre_usuario: $nombre_usuario, contraseña: $contraseña, email: $correo } ) { objectId } }
Parse 3.9:
mutación signUpUser($username: ¡Cadena!, $password: ¡Cadena!, $email: Cadena) { registro( campos: { nombre_usuario: $nombre_usuario, contraseña: $contraseña, email: $correo } ) { id } }
Observa que estoy haciendo que mis variables nombredeusuario y contraseña sean obligatorias al tener un “!” al final de sus tipos, mientras que correo electrónico es opcional, al no tener el “!” después de su tipo.
Al final, estoy devolviendo el objectId del usuario recién creado.
Y ya que estamos aquí, codifiquemos también nuestra mutación GraphQL para el login de un usuario ya existente.
En este caso, tanto el nombre de usuario como la contraseña son obligatorios, y recuperaré el sessionToken del usuario:
Parse 3.7.2
mutation logInUser($nombredeusuario: String!, $contraseña: String!){ 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 último, pero no menos importante, una mutación para que el usuario cierre la sesión, que no requiere ningún parámetro y sólo devuelve un booleano:
Parse 3.7.2
mutación logOutUser{ usuarios{ logOut } }
Parse 3.8
mutación logOutUser{ logOut }
Parse 3.9
mutación logOutUser{ logOut }
Añade todo eso a un nuevo archivo llamado UserMutations.graphql, también, guarda tu archivo schema.graphql que aprendiste a descargar en nuestro artículo anterior.
Mantén estos archivos seguros ya que los añadiremos a nuestro…
Proyecto SwiftUI
Vamos a crear nuestro proyecto SwiftUI.
Inicia XCode 11 y pulsa “Crear un nuevo proyecto XCode” desde la ventana de bienvenida a XCode.
Si no te aparece esa ventana, siempre puedes ir a Archivo > Nuevo > Proyecto.
Elige iOS App en la parte superior y luego Single View App. Dale un buen nombre y recuerda elegir Swift como Lenguaje y SwiftUI como Interfaz de Usuario:
No olvides seleccionar SwiftUI como Interfaz de Usuario
Ahora, instala el Cliente Apollo como aprendiste en nuestro artículo sobre la web api, y después de eso, añade los archivos UserMutations.graphql y schema.graphql a tu proyecto.
Compila y añade el archivo API.swift recién generado al proyecto.
En este punto lo que espero que tengas es:
- XCode 11 instalado y funcionando
- Cliente Apollo instalado e integrado en tu proyecto XCode
- UserMutations.graphql y schema.graphql archivos añadidos a su proyecto XCode
- Archivo generado API.swift añadido a tu proyecto XCode
Tu proyecto debería tener un aspecto similar a este:
Todo integrado en XCode 11
Ahora estamos listos para empezar con SwiftUI.
Regístrate ahora en Back4App y empieza a construir tu Instagram Clone App.
Decidiendo nuestros controles
Antes de que podamos entrar o salir de un usuario, debemos tener ese usuario creado en primer lugar. Así que vamos a codificar nuestra pantalla de registro en primer lugar.
Usualmente, podemos tener de 4 a 5 controles en esa pantalla:
- Algún tipo de Texto para decirle al usuario que pantalla es esta (texto)
- Nombre de usuario (control de entrada de texto)
- Contraseña (control de entrada de texto seguro)
- Confirmar Contraseña (opcional, depende de la plataforma, control de entrada de texto seguro)
- Correo electrónico (control de entrada de texto con formato)
- Botón de registro (botón)
Como esta será una aplicación móvil, decidí no poner el control de entrada de texto Confirmar contraseña, ya que la mayoría de las plataformas móviles permiten al usuario ver el último carácter escrito y en la mayoría de los casos eso es suficiente para que el usuario determine si la contraseña se está escribiendo correctamente.
Esos controles se mostrarán alineados verticalmente, uno debajo del otro, en el orden anterior, cada uno con su propio color.
Construyendo la interfaz de usuario
Como he mencionado antes, no estoy usando macOS Catalina por lo que no tendré las vistas previas habilitadas, pero compilaré y os mostraré los resultados junto con mi código tal y como lo tenía.
Crea una nueva Vista SwiftUI yendo a Archivo > Nuevo > Archivo y eligiendo Vista SwiftUI en la sección Interfaz de Usuario.
Nombra ese archivo SignUpView.swift y aparecerá integrado en tu Proyecto.
Notarás que la vista recién creada ya tiene un control de Texto con la cadena “¡Hola Mundo!” en él.
La belleza de Swift es que puedes crear múltiples vistas simples, conteniendo sólo los controles necesarios para esa vista, y luego fusionar múltiples vistas en una más compleja. Es Orientación a Objetos en su máxima expresión.
Hemos decidido tener nuestros controles alineados verticalmente y SwiftUI tiene un control específico para ello llamado VStack (Vertical Stack): todo lo que está dentro de un VStack se apilará verticalmente de forma automática, así que, sólo para probar, vamos a añadir nuestro control VStack a nuestro ContentView.swift y dentro de él, añadir dos veces el SignUpView:
Añadimos dos veces SignUpView en el VStack: se muestra dos veces alineado verticalmente
Ahora, ¡qué guay! Si necesitamos más controles, ¡podemos seguir añadiéndolos!
Quitemos el VStack y la duplicidad y tengamos solo un SignUpView() en esa View y luego volvamos a nuestro SignUpView y comencemos a cambiarlo, como ya vimos los resultados de nuestra prueba. Tu ContentView debería parecerse a esto ahora:
struct ContentView: View { var body: some View { SignUpView() } }
Ya sabemos que necesitaremos esa VStack con texto en la parte superior de la pantalla, así que vamos a añadir la VStack a la SignUpView y cambiar la cadena “Hello World!” por algo útil, como “Sign Up”. Nuestro SignUpView debe tener este aspecto:
struct SignUpView: View { var body: some View { VStack{ Texto("Regístrate") } } }
Y como ya sabemos que controles debemos añadir debajo, podemos añadir:
- a TextField para Username
- un SecureField para Password
- un TextField para Email
- un Botón para registrarse
Tanto el TextField como el SecureField son bastante similares al antiguo UITextField, pero tienen que depender de la vinculación con el estado, así que vamos a declararlos como:
Control(“Marcador de posición”, text: stateValueToBindTo)
y el primer paso es declarar el estado a enlazar:
@State var nombre de usuario: String = "" @State var password: String = "" @Estado var email: Cadena = ""
Con esto fuera del camino, podemos empezar a añadir nuestros controles:
Text("Regístrate") TextField("Nombre de usuario", text: $nombredeusuario) SecureField("Contraseña", text: $contraseña) TextField("Email (opcional)", text: $email)
Por último, pero no menos importante, podemos añadir nuestro Button, que tiene esta estructura:
Button(action: {
//Código a ejecutar cuando se active
}){
//Contenido
}
Como probablemente hayas notado, funciona igual que un UIButton, pero su estructura es mucho más flexible ya que podemos definir su contenido programáticamente, añadiendo prácticamente cualquier cosa ahí dentro: un Texto, una Imagen. Lo que quieras.
La vieja estructura target/action desaparece, reemplazada por una nueva estructura action:{} para manejar el comportamiento cuando se toca.
Nuestro botón tendrá un texto que dirá “¡Regístrate!”, así que vamos a añadirlo:
Button(action: { }){ Texto("¡Regístrate!") }
Y tu código final debería tener este aspecto
struct SignUpView: View { @State var nombre de usuario: String = "" @Estado var contraseña: Cadena = "" @Estado var email: Cadena = "" var body: alguna Vista { VStack{ Texto("Registrarse") TextField("Nombre de usuario", text: $nombredeusuario) TextField("Contraseña", text: $contraseña) TextField("Correo electrónico (opcional)", text: $correo electrónico) Button(acción: { }){ Text("¡Regístrate!") } } } }
¿Y qué tal nuestra Vista Previa?
Parece prometedor, pero podemos mejorar mucho estableciendo algunas propiedades visuales.
¡Y si hay algo super bonito de SwiftUI es que puedes añadir propiedades visuales al código y ver los cambios en tiempo real también!
¡Vamos a probar!
En primer lugar, vamos a declarar los colores que vamos a utilizar
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)
a continuación, vamos a añadir algunas características agradables:
Algo de Padding para que tengamos algo de espacio entre esos elementos.
Establecer las propiedades background y foregroundColor para que tengamos controles que sean consistentes con nuestra marca.
Algunos cornerRadius para que podamos tener esquinas redondeadas.
Y establecer font y fontWeight para hacer las cosas más bonitas.
¡Voilà!
¿Es bonito o qué?
Sólo para que no pierdas de vista nuestro código en este punto:
struct SignUpView: View { @State var nombre de usuario: String = "" @Estado var contraseña: Cadena = "" @Estado var email: Cadena = "" let gris claroColor = 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) var body: alguna Vista { VStack{ Texto("Regístrate") .font(.títulogrande) .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) TextField("Correo electrónico (opcional)", text: $correo electrónico) .padding() .background(colorGris claro) .cornerRadius(5.0) .padding(.bottom, 20) Botón(action: { }){ Texto("¡Regístrate!") .font(.titular) .foregroundColor(.blanco) .padding() .frame(anchura: 220, altura: 60) .background(colorazulclaro) .cornerRadius(15.0) } }.padding() } }
Así que ya tenemos nuestra bonita y brillante pantalla casi lista.
¿Qué tal…
Algo de funcionalidad
Ahora que tenemos nuestra UI lista y lista, es hora de añadir nuestras llamadas Apollo a nuestros métodos GraphQL.
Como vamos a utilizar esas llamadas en casi todas las vistas, debemos crear un cliente Apollo en un lugar donde todas las vistas puedan acceder a él. Ese lugar es el AppDelegate.swift.
Declara tu cliente Apollo ahí debajo del archivo
importar UIKit
y el inicio del bloque de código
@UIApplicationMain clase AppDelegate: UIResponder, UIApplicationDelegate { ...
tal y como te mostramos en nuestro anterior artículo, y a partir de ahora todas las Views podrán realizar consultas y mutaciones GraphQL.
Primero tienes que importar el framework Apollo y luego instanciar tu cliente de la siguiente manera:
importar Apollo // Inicialización del cliente Apollo. // Más información aquí: https://www.back4app.com/docs/ios/swift-graphql let apollo: ApolloClient = { let configuration = URLSessionConfiguration.default configuration.httpAdditionalHeaders = [ "X-Parse-Application-Id": "YourAppIdHere", "X-Parse-Client-Key": "YourClientKeyHere" ] ¡let url = URL(string: "https://parseapi.back4app.com/graphql")! return ApolloClient( networkTransport: HTTPNetworkTransport( url: url, configuración: configuración ) ) }()
Simplemente sustituye las cadenas YourAppIdHere y YourClientKeyHere por sus valores actuales y ya podemos continuar.
Como probablemente adivinaste, debemos realizar nuestra Mutación para SignUp cuando hagamos clic en el botón Sign Up, así que, en el bloque de acción, vamos a llamarlo:
// Realizar la mutación SignUpUser, pasando los parámetros que acabamos de obtener de nuestros TextFields apollo.perform(mutation: SignUpUserMutation(username: self.username, password: self.password, email: self.email)){ result in // Cambiemos el resultado para poder separar uno correcto de un error switch resultado { // En caso de éxito case .success(let graphQLResult): // Intentamos parsear nuestro resultado if let objId = graphQLResult.data?.users?.signUp.objectId { print ("Usuario creado con ObjectId: " + objId) } // pero en caso de cualquier error GraphQL presentamos ese mensaje else if let errors = graphQLResult.errors { // Errores GraphQL print(errores) } // En caso de fallo, presentamos ese mensaje case .failure(let error): // Errores de red o de formato de respuesta print(error) } }
Eso funcionará, pero esos métodos print() sólo imprimirán en la consola. Necesitamos presentar una Alerta a nuestro usuario, así que cambiémosla por una Alerta, con la siguiente estructura:
Alert(title: Text(“Título”), mensaje: Text(“Mensaje”), dismissButton: .default(Text(“TextOfButton”))
Como necesitaremos cambiar el título y el mensaje en tiempo de ejecución, debemos establecer un Struct para manejar esos valores, ya que las variables self no se pueden cambiar en tiempo de ejecución:
struct Mensaje { var alertTitle: String = "" var alertText: String = "" } var miMensaje = Mensaje()
Y también crear un Estado para que la Vista pueda saber cuando mostrar la alerta:
@State private var mostrandoAlerta = false
Y nuestro código completo para nuestra acción Button debería ser así:
Button(action: { // Realiza la mutación SignUpUser, pasando los parámetros que acabamos de obtener de nuestros TextFields apollo.perform(mutation: SignUpUserMutation(username: self.username, password: self.password, email: self.email)){ result in // Cambiemos el resultado para poder separar uno correcto de un error switch resultado { // En caso de éxito case .success(let graphQLResult): // Intentamos parsear nuestro resultado if let objId = graphQLResult.data?.users?.signUp.objectId { myMessage.alertTitle = "¡Sí!" myMessage.alertText = "¡El usuario se ha registrado!" self.showingAlert = true print ("Usuario creado con ObjectId: " + objId) } // 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) } } }){ Texto("¡Apúntate!") .font(.titular) .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")) }
Parece prometedor, ¿eh?
Así que iiiiiiiii es tiiiiiempo
Compilar y ejecutar. Intenta dar de alta un nuevo usuario y deberías obtener…
¡Veamos si el usuario está creado en nuestro Parse Dashboard!
Conclusión
Has hecho una vista SwiftUI completamente funcional que realiza mutaciones GraphQL usando Apollo Client. ¡Qué impresionante es eso!
¡Vamos a seguir trabajando en nuestra App clon de Instagram! ¡Los próximos pasos son Logging in and out y el post ya está en el horno!
¡Estad atentos!
Referencia
- Parte 1 de esta serie es Instagram Clone.
- Parte 2 es Instagram Login.
- Parte 3 es Perfil de Instagram.
- Parte 4 es Instagram HomeView.
- Ver el backend de la aplicación en iOS Instagram Clone Backend.
- Descarga un proyecto iOS Instagram Clone con el código fuente y empieza a usar Back4App.
Regístrese ahora en Back4App y comience a construir su Instagram Clone App.