GraphQL iOS: Uso de funciones en la nube en una aplicación Swift
En este artículo, te mostré cómo usar GraphQL con NodeJS Cloud Code en Back4app.
Y en esta guía, te mostré cómo usar Apollo GraphQL iOS Client con Swift.
Ahora vamos a juntarlo todo y hacer muchas operaciones complicadas produciendo código realmente fácil. Y además hagamos que XCode nos genere automáticamente cualquier código API incluso cuando cambiamos de Clase.
¿Te parece bien? ¡Pues tómate un café y ponte manos a la obra!
Contents
- 1 Antes de empezar…
- 2 Algunas piezas de software que nos ayudarán…
- 3 Creando nuestras clases
- 4 ¡Trae el Código Nube, nene!
- 5 Probando… Probando…
- 6 Algo de XCoding
- 7 Nuestro archivo GraphQL
- 8 Algo de código Swift
- 9 ¡Vamos a ejecutarlo!
- 10 Conclusión
- 11 ¿Qué software se puede utilizar para utilizar las funciones de la nube en la aplicación Swift?
- 12 ¿Cuál es la magia de la NPM?
Antes de empezar…
Necesitarás una cuenta Back4app con una App creada. Si aún no tienes una, sigue esta guía y lo tendrás todo en poco tiempo.
Algunas piezas de software que nos ayudarán…
También utilizaremos algunas cosas para acelerar las cosas.
Por supuesto, puedes hacerlo todo manualmente si lo prefieres, pero no recomiendo esa ruta especialmente para principiantes.
Añadiendo paquetes manualmente, también necesitarás mantener manualmente los paquetes, lo que puede ser problemático después de algún tiempo.
No tienes que usar los gestores de paquetes que te mostraré aquí, pero te recomiendo encarecidamente que uses al menos algunos gestores de paquetes en lugar de hacer la gestión manual. Como he dicho antes, la gestión manual puede volverse peculiar con el tiempo.
Voy a utilizar Cocoapods para la gestión de paquetes de iOS y NPM para la gestión de escritorio, pero de nuevo, siéntase libre de utilizar lo que flota su barco.
Las instrucciones para instalar Cocoapods se pueden encontrar en este sitio.
Las instrucciones para instalar NPM se pueden encontrar en este sitio.
Creando nuestras clases
Vamos a implementar 3 clases: Propietario, Perro y Raza.
Nuestro modelo implicará que
- Un Propietario puede tener varios Perros
- Un perro sólo puede tener una raza
Nuestras propiedades serán:
- Propietario
- nombre (Cadena)
- edad (Número)
- dirección (cadena)
- hasDogs (Relación con el perro, ya que un propietario puede tener varios perros)
- Perro
- nombre (Cadena)
- cumpleaños (Fecha)
- raza (Puntero a Raza, ya que un Perro sólo tiene una Raza)
- Raza
- Nombre (Cadena
Cree esas clases gráfica o programáticamente y rellénelas con algunos datos. Mis clases terminaron así
Raza
Perro
Propietario
¡Trae el Código Nube, nene!
Es hora de un poco de Código Nube. Pero esta vez también quiero profundizar en ello.
¿Te he dicho alguna vez que Cloud Code es compatible con módulos NPM? ¡Lo es! ¡Y puede ahorrarte MUCHO tiempo!
¿Sabes esa funcionalidad tan chula que querías tener y que alguien ya ha hecho? Bueno, si es un módulo NPM, ¡puedes usarlo!
Ya hay un artículo sobre cómo utilizar módulos utilizando este tutorial, así que para esta implementación, voy a utilizar Moment para que podamos utilizar el cumpleaños de un Perro y calcular su edad en meses.
Así que mi package.json archivo se verá así:
{ "dependencies": { "moment": "*" } }
El * significa que voy a utilizar la última versión.
Mi código Cloud terminó como sigue. Está bien comentado, así que no voy a entrar en muchos detalles aquí, pero se puede ver en la primera línea que estoy usando el módulo Moment NPM:
// Instanciando el módulo Moment NPM const moment = require('moment') Parse.Cloud.define("retrieveOwnersOrderedByAgeDescending", async req => { /* Esta función recupera todos los propietarios ordenados por edad de forma descendente. Si no hay propietarios, se recupera un array vacío. */ const query = new Parse.Query("Propietario"); query.descending("edad") const results = await query.find(); return resultados; }); Parse.Cloud.define("recuperarTodosPastoresAlemanes", async req => { /* Esta función recupera los Perros que tienen la raza "Pastor Alemán". Se recuperan todas las propiedades de los Perros. Si no hay perros, se obtiene un array vacío. */ const breedQuery = new Parse.Query("Raza"); breedQuery.equalTo("nombre", "Pastor alemán") const query = new Parse.Query("Perro"); query.matchesQuery("raza", breedQuery); const results = await query.find(); devolver resultados; }); Parse.Cloud.define("retrieveDogIncludingBreedByName", async req => { /* Esta función recupera los detalles específicos del Perro incluyendo los detalles de su raza (nombre) por nombre de Perro Se recuperan todas las propiedades de los perros y las razas. Si no hay perros, se obtiene null. */ const query = new Parse.Query("Perro"); query.equalTo("nombre", req.params.name); query.include("raza") const results = await query.find(); return resultados; }); Parse.Cloud.define("recuperarNombresDePerrosConEdadesEnMeses", async req => { /* Esta función recupera los nombres de los Perros con la propiedad cumpleaños convertida en Meses utilizando el módulo NPM momento Sólo se recupera el nombre de los Perros y se calcula la edad en meses. Si no hay perros, se obtiene un array vacío. */ let perros = []; const query = new Parse.Query("Perro"); const results = await query.find(); for (let i = 0; i < results.length; i ++){ // calcular la edad en Meses utilizando el Módulo NPM Momento let edadEnMeses = moment.duration(moment().diff(resultados[i].get("cumpleaños"))).humanize(); let nuevoPerro = { "dogName": results[i].get("nombre"), "edaddelperroenmeses": edadenmeses } dogs.push(nuevoPerro); } return perros; });
Mi archivo schema.graphql exponiendo mis cuatro métodos quedó así:
extender tipo Query { getAllOwnersAgeDescending: ¡[OwnerClass]! @resolve(to: "recuperarPropietariosOrdenadosPorEdadDescendente") getAllGermanShepherds: [¡ClasePerro!] @resolve(to: "recuperarTodosPastoresAlemanes") getThisDogWithBreed (name:String): [ClasePerro] @resolve(to: "recuperarPerroConCríaPorNombre") getEdadDelPerroEnMeses: [DogAge!]! @resolve(to: "recuperarNombresDePerrosConEdadesEnMeses") } type DogAge { nombredelperro: String edaddelperroenmeses: ¡String! }
Si no entiendes la sintaxis, échale un vistazo a este artículo en la sección “Un pequeño paso extra”.
Fíjate en el [DogAge!] de getDogsAgesInMonths. Como estaré recuperando un objeto generado por el sistema, no tendrá un esquema para que GraphQL lo interprete, así que tengo que crear el tipo también para que nuestro cliente pueda interpretarlo.
Probando… Probando…
¡Hora de probar!
Vayamos a nuestra Parse GraphQL Console y ejecutemos nuestras consultas, comprobando sus resultados:
Query para getAllOwnersAgeDescending:
query{ getAllOwnersAgeDescending { nombre edad dirección } }
Consulta para getAllGermanShepherds:
query { getTodosPastoresAlemanes { nombre cumpleaños } }
Consulta para getThisDogWithBreed:
query{ getThisDogWithBreed(nombre: "Fido"){ nombre cumpleaños raza{ nombre } } }
Consulta para getDogsAgesInMonths:
query{ getDogsAgesInMonths }
¡Todo funcionando! ¡Muy bien! Ahora es el momento de…
Algo de XCoding
Tenemos todo lo que necesitamos de Back4app funcionando. Es hora de consumirlo en XCode.
Esta guía mostraba cómo hacerlo, pero esta vez nos pusimos un poco más avanzados: tenemos múltiples métodos, uno de los cuales espera variables, así que necesitaremos hacer algunos cambios.
Si aún no has seguido ese artículo, te recomiendo que lo hagas ya que es muy detallado.
Comencemos creando la aplicación e instalando el cliente Apollo como se describe en el artículo.
Después de eso, abra su archivo Main.storyboard, haga clic en el botón Objetos en la esquina superior derecha y arrastre y suelte una vista de tabla en el controlador de vista:
Cambia el tamaño de tu Table View para que coincida con toda la View del View Controller arrastrando las esquinas:
Y cree Constraints para los cuatro lados haciendo clic en el botón Add New Constraints en la parte inferior derecha de la pantalla. Haga clic en los cuatro lados (marcadores rojos) y haga clic en el botón Añadir Restricciones para asignar esas nuevas restricciones:
Ahora, con la Vista Tabla seleccionada (haga clic sobre ella para seleccionarla), vamos a añadir una Celda Prototipo. Haga clic en el Inspector de Atributos en la parte superior derecha, y sustituya el 0 por un 1 en el cuadro Celdas Prototipo:
Ahora, haz clic en la Celda Prototipo recién creada para seleccionarla, y dale un buen identificador: “celda”.
Lo utilizaremos más adelante en el código para identificar esa celda.
Ahora tenemos que enlazar nuestra Fuente de Datos y el Delegado de la Vista de Tabla a nuestro Controlador de Vista.
Mientras mantienes pulsada la tecla Control de tu teclado, haz clic en la Vista Tabla y arrástrala hasta el icono amarillo de la parte superior de tu View Controller, el que dice View Controller cuando pasas el ratón por encima.
Al soltar el enlace de la Vista Tabla allí, aparecerá una pequeña ventana emergente. Elige DataSource y Delegate en la ventana emergente:
Ahora, eso cubrirá lo básico pero aún necesitaremos invocar un refresco en esa Vista de Tabla después de que recuperemos nuestros datos de Back4app. Para ello, debemos crear un Outlet y conectarlo a nuestra Interfaz de Usuario.
La forma más fácil de hacerlo es haciendo clic en Mostrar Editor Asistente en la parte superior derecha de la pantalla, para que pueda ver lado a lado la interfaz de usuario y el código.
Luego haz Control click en tu Vista de Tabla y arrástrala hasta el código:
Cuando lo sueltes, aparecerá una ventana emergente. Rellena el Nombre y haz click en Conectar:
Hecho esto, deberías tener un bonito Outlet conectado (fíjate en el pequeño círculo rellenado en el número de línea) así que ahora podemos invocar código para esa Table View:
Con todo esto hecho, podemos ir a nuestro archivo ViewController.swift porque es hora de…
Nuestro archivo GraphQL
Ennuestra guía de GraphQL te enseñamos a crear tu archivo GraphQL. Para este proyecto, el mío terminó así
query findAllOwners{ getAllOwnersAgeDescending{ Nombre nombre edad dirección } } consulta findAllGermanShepherds{ getAllGermanShepherds { nombre } } query encontrarEstePerro ($nombre: ¡Cadena!){ getThisDogWithBreed(nombre: $nombre){ nombre cumpleaños raza{ nombre } } } consulta agesInMonths{ getDogsAgesInMonths }
Algo de código Swift
Una vez más, ya hemos cubierto cómo configurar Apollo en Swift enesta guía, pero el código de hoy tiene algunos cambios.
Como vamos a mostrar los datos gráficamente en nuestra Table View, debemos incluir dos métodos para que nuestro Data Source y Delegate los ejecuten. Estos métodos son
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
y
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
No voy a hablar mucho de esto ya que está documentado en el código. Sólo ten en cuenta que estos dos métodos son obligatorios para que nuestra Table View funcione.
El código completo debería tener este aspecto
// // ViewController.swift // GraphQLApollo // // Creado por Venom el 19/08/19. // Copyright © 2019 Venom. Todos los derechos reservados. // importar UIKit importar Apollo clase ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet débil var tableView: ¡UITableView! var list = [] as [String] // Esta matriz contendrá los valores que vamos a mostrar // 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": "lAcIOgR0ndd7R4uMqovhNAoudpi6tMsrOI9KCuyr", "X-Parse-Client-Key": "f1wwm6uWqQoxazys3QQrrtY4fKuQuxYvNYJmYQJP" ] ¡let url = URL(string: "https://parseapi.back4app.com/graphql")! return ApolloClient( networkTransport: HTTPNetworkTransport( url: url, configuración: configuración ) ) }() override func viewDidLoad() { super.viewDidLoad() // Realiza cualquier configuración adicional después de cargar la vista. apollo.fetch(query: FindAllOwnersQuery()) { result in guard let data = try? result.get().data else { return } for nombre in datos.getAllOwnersAgeDescending { //Agrega cada nombre a nuestro array de listas self.list.append(nombre.nombre!) } //la línea de abajo forzará una recarga de nuestra Table View después de que recuperemos los datos DispatchQueue.main.async { self.tableView.reloadData() } } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // Esta función es llamada por nuestra Table View a través del Data Source y el Delegate. // Sólo devuelve el número de objetos que la Vista Tabla debe mostrar. // Es obligatoria para que la Vista de Tabla funcione. return list.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // Esta función es llamada por nuestra Table View a través del Data Source y el Delegate. // Crea las celdas que se mostrarán con el identificador que establecimos en el Storyboard. // Es obligatorio para que una Table View funcione. let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell") cell.textLabel?.text = list[indexPath.row] return(cell) } }
¡Vamos a ejecutarlo!
¡Es hora de ejecutar nuestro código! Pulsa Comando + R y si todo es correcto, deberías ver nuestros nombres en tu Vista Tabla:
Mola, ¿eh?
¡Ahora incluso podemos cambiar nuestro código para ejecutar nuestros otros métodos GraphQL y ver cómo funcionará!
Cambiemos nuestra consulta GraphQL para ejecutar nuestro método getAllGermanShepherds:
override func viewDidLoad() { super.viewDidLoad() // Realiza cualquier configuración adicional después de cargar la vista. apollo.fetch(query: FindAllGermanShepherdsQuery()) { result in guard let data = try? result.get().data else { return } print(datos.obtenerTodosPastoresAlemanes) for nombre en datos.obtenerTodosPastoresAlemanes { //Agrega cada nombre a nuestro array de listas self.list.append(nombre.nombre!) } //la línea siguiente forzará una recarga de nuestra vista de tabla después de recuperar los datos DispatchQueue.main.async { self.tableView.reloadData() } } }
y el resultado…
¿Y si queremos pasar una variable a nuestra consulta?
override func viewDidLoad() { super.viewDidLoad() // Realiza cualquier configuración adicional después de cargar la vista. apollo.fetch(query: FindThisDogQuery(name: "Fido")) { result in guard let data = try? result.get().data else { return } print(datos.obtenerEstePerroConCría) for nombre in datos.obtenerEstePerroConRaza { //Agrega cada nombre a nuestro array de listas ¡self.list.append(nombre.nombre! + " - " + (nombre.raza?.nombre!)! } //la línea de abajo forzará una recarga de nuestra Table View después de que recuperemos los datos DispatchQueue.main.async { self.tableView.reloadData() } } }
Y ahora sabemos que Fido es un Pug:
¿Y qué tal ese método que devuelve un nuevo tipo? Probémoslo también:
override func viewDidLoad() { super.viewDidLoad() // Realiza cualquier configuración adicional después de cargar la vista. apollo.fetch(query: AgesInMonthsQuery()) { result in guard let data = try? result.get().data else { return } print(datos.obtenerPerrosEnEdadMeses) for perro in datos.obtenerPerrosEnEdadesMeses { //Agrega cada nombre a nuestro array de listas self.list.append(dog.dogName + " - " + dog.dogAgeInMonths) } //la línea siguiente forzará la recarga de nuestra vista de tabla después de recuperar los datos DispatchQueue.main.async { self.tableView.reloadData() } } }
y el resultado:
Conclusión
El código en la nube mola. GraphQL es super guay. Apollo aporta un nuevo nivel de genialidad a tus aplicaciones nativas de iOS. Con los tres juntos, tienes un gran poder para crear aplicaciones muy complejas con muy poco esfuerzo.
Ahora que eres capaz de crear un flujo de trabajo completo para una aplicación, que es fácil de crear y mantener en el tiempo, la pregunta realmente es: ¿qué vas a crear a continuación?
Espero que te haya gustado. Pronto publicaremos más. Permanezca atento.
¿Qué software se puede utilizar para utilizar las funciones de la nube en la aplicación Swift?
Dependerá de tu elección. Debes usar un software que te resulte fácil de usar. En la práctica anterior, usé los dos siguientes:
Cocoapods para la gestión de paquetes de iOS
NPM para la gestión de paquetes de escritorio
¿Cuál es la magia de la NPM?
El código en la nube es compatible con los módulos NPM. Esto es una gran ventaja. Te ayudará a ahorrar mucho tiempo y te dará una ventaja sobre tu competencia. Por lo tanto, la compatibilidad con NPM y el código en la nube te dará un impulso.