Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. iOS
Code

iOS desde cero con Swift: creación de una aplicación de lista de compras 2

by
Difficulty:BeginnerLength:LongLanguages:
This post is part of a series called iOS From Scratch With Swift.
iOS From Scratch With Swift: Building a Shopping List Application 1
iOS From Scratch With Swift: Where To Go Next

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

En la lección anterior, sentamos las bases para la aplicación de la lista de compras. En la primera parte de esta lección, refinamos aún más la aplicación al permitir al usuario editar y eliminar elementos de la lista. Más tarde, agregamos la capacidad de seleccionar elementos de la lista para crear la lista de compras.

1. Eliminar elementos

Eliminar elementos de la lista es una adición importante en términos de experiencia del usuario y usabilidad general. Agregar esta habilidad implica:

  • eliminar el elemento de la propiedad items del controlador de vista
  • actualizar la tabla
  • guardando los cambios en el disco

Veamos cómo funciona esto en la práctica. Primero debemos agregar un botón de edición a la barra de navegación. En el método viewDidLoad() del controlador de vista, cree una instancia de UIBarButtonItem y asígnela a la propiedad rightBarButtonItem de la propiedad navigationItem del controlador de vista.

Como hicimos en la lección anterior, creamos un elemento de botón de barra invocando init(barButtonSystemItem:target:action:), pasando .Edit, un valor de miembro de UIBarButtonSystemItem, self como el objetivo y "editItems:" como selector .

La implementación de editItems(_:) es solo una línea de código, como puede ver a continuación. Cada vez que el usuario toca el botón de edición, la vista de tabla se activa o desactiva el modo de edición. Hacemos esto usando un pequeño truco. Le preguntamos a la tabla si está o no en modo de edición, que devuelve un valor de Bool, e inversamente el valor devuelto (true se convierte en false y viceversa). El método que llamamos en la vista de tabla es setEditing(_:animated:), un setter especializado que acepta un parámetro de animación.

Si ejecuta la aplicación de la lista de compras en el simulador y toca el botón de edición, debería ver que la vista de tabla está activada y desactivada en el modo de edición.

Toggling the Table View Into and Out of Edit Mode
Toggling the Table View Into and Out of Edit Mode

Dos métodos del protocolo UITableViewDataSource son importantes para permitir la edición en una vista de tabla:

  • tableView(_:canEditRowAtIndexPath:)
  • tableView(_:commitEditingStyle:forRowAtIndexPath:)

Si el usuario toca el botón de edición, la vista de tabla pregunta a su origen de datos qué filas son editables enviando un fuente de datos a tableView(_:canEditRowAtIndexPath:). Si se devuelve true para una ruta de índice en particular, la vista de tabla indica a la celda de vista de tabla correspondiente que necesita para alternar dentro o fuera del modo de edición, dependiendo del modo de edición de la vista de tabla. Esto se traduce en la celda de vista de tabla que muestra u oculta sus controles de edición. Implemente el método tableView(_:canEditRowAtIndexPath:) como se muestra a continuación para ver cómo funciona esto en la práctica.

La implementación anterior de tableView(_:canEditRowAtIndexPath:) permite al usuario editar cada fila en la vista de tabla con la excepción de la segunda fila. Ejecute la aplicación en el simulador y pruébela.

Adding the Ability to Edit the Table View

Para la aplicación de lista de compras, el usuario debe poder editar cada fila en la vista de tabla. Esto significa que tableView(_:canEditRowAtIndexPath:) siempre debe devolver true.

Puede haber notado que no ocurre nada cuando intenta eliminar una fila en la vista de tabla. El rompecabezas no ha terminado todavía. Cada vez que el usuario toca el botón Eliminar de una fila, la vista de tabla envía a su fuente de datos un mensaje de tableView(_:commitEditingStyle:forRowAtIndexPath:). El segundo argumento del método correspondiente indica qué tipo de acción ha realizado el usuario, insertando o borrando una fila. Para la aplicación de la lista de compras, solo implementaremos el soporte para eliminar filas de la vista de tabla.

Eliminar filas de una vista de tabla implica:

  • eliminar el elemento correspondiente de la propiedad  items del controlador de vista
  • actualizando la vista de tabla eliminando la fila correspondiente

Examinemos la implementación de tableView(_:commitEditingStyle:forRowAtIndexPath:). El método comienza comprobando si el estilo de edición es igual a .Delete (un valor de miembro de UITableViewCellEditingStyle), porque solo queremos permitir que el usuario elimine filas de la vista de tabla.

Si el estilo de edición es igual a .Delete, el elemento correspondiente se elimina de la propiedad items, la fila correspondiente se elimina de la vista de tabla y la lista actualizada de elementos se guarda en el disco.

Ejecute la aplicación en el simulador y elimine algunos elementos. No olvide cerrar y volver a iniciar la aplicación para verificar que los elementos se eliminen permanentemente de la lista.

2. Edición de artículos

Podríamos reutilizar la clase AddItemViewController para editar elementos. Sin embargo, debido a que el uso de un solo controlador de vista para agregar y editar elementos a menudo puede complicar demasiado la implementación, generalmente termino creando una clase separada para editar los elementos. Esto puede dar lugar inicialmente a que nos repitamos, pero nos dará más flexibilidad en el futuro.

Paso 1: Crear el controlador de vista de edición

Cree una nueva subclase UIViewController y asígnele el nombre EditItemViewController. La interfaz de las clases AddItemViewController y EditItemViewController es muy similar.

Las diferencias son la adición de una propiedad, item, para almacenar una referencia al elemento que se está editando y la definición del método del protocolo EditItemViewControllerDelegate. Tenga en cuenta que el item es de tipo Item!, un accesorio desenvuelto forzado opcional. Debido a que el controlador de vista de elemento de edición no sirve sin un elemento para editar, esperamos que la propiedad del item siempre tenga un valor.

Abra Main.storyboard, arrastre una instancia de UIViewController desde la Biblioteca de objetos, establezca su clase en EditItemViewController y cree una secuencia de muestra manual desde el controlador de vista de lista hasta el controlador de vista de edición de elementos. Establezca el identificador de segue en EditItemViewController.

Creating a Push Segue to the Edit Item View Controller

Arrastre los campos de texto de la Biblioteca de objetos a la vista del controlador de vista y colóquelos como se muestra en la figura a continuación. Seleccione el campo de texto superior, abra el Inspector de atributos e ingrese Nombre en el campo Marcador de posición. Seleccione el campo de texto de la parte inferior y, en el Inspector de atributos, establezca su texto de marcador de posición en Precio y establezca Teclado en el Panel numérico. Seleccione el objeto controlador de vista, abra el Inspector de conexiones y conecte las salidas nameTextField y priceTextField con el campo de texto correspondiente en la interfaz de usuario.

Configuring the Text Fields

En el método viewDidLoad() del controlador de vista, cree el botón guardar como lo hicimos en la clase AddItemViewController.

La implementación de la acción save(_:) es muy similar a la que implementamos en la clase AddItemViewController. Sin embargo, hay algunas diferencias sutiles que quiero señalar.

En lugar de pasar los valores de nombre y precio al delegado, actualizamos directamente el elemento y pasamos el elemento actualizado al delegado del controlador de vista. Como el controlador de vista es un controlador de vista infantil de un controlador de navegación, descartamos el controlador de vista sacándolo de la pila de navegación.

Paso 2: Mostrar el controlador de vista Editar elemento

En unos minutos, implementaremos la funcionalidad que permite al usuario seleccionar elementos del controlador de vista de lista para agregarlos a la lista de compras. El usuario podrá hacer esto tocando una fila en la vista de lista. La pregunta es cómo el usuario podrá editar un elemento si toca reservar una fila para agregar un artículo a la lista de compras.

El marco UIKit proporciona un botón de divulgación de detalles para este caso de uso. Un botón de divulgación de detalles se coloca a la derecha de una celda de vista de tabla. Para agregar un botón de divulgación de detalles a una celda de vista de tabla, necesitamos revisar tableView(_:cellForRowAtIndexPath:) en el controlador de vista de lista y enmendarlo como se muestra a continuación.

Cada celda de vista de tabla tiene una propiedad accessoryType. En tableView(_:cellForRowAtIndexPath:), lo configuramos en .DetailDisclosureButton, un valor miembro de UITableViewCellAccessoryType. Puede haber notado que a los ingenieros de Apple no les gustan los nombres cortos.

¿Cómo notifica la vista de tabla a su delegado cuando se toca el botón de divulgación de detalles? Como era de esperar, el protocolo UITableViewDelegate define el método tableView(_:accessoryButtonTappedForRowWithIndexPath:)  para este propósito. Eche un vistazo a su implementación.

Buscamos el elemento correcto de la propiedad items y lo almacenamos en selection, una propiedad del controlador de vista de lista que declararemos en un momento. Luego realizamos la segue con el identificador EditItemViewController.

Antes de actualizar la implementación de prepareForSegue(_:sender:), necesitamos declarar la propiedad para almacenar el elemento seleccionado. También debemos adaptar la clase ListViewController al protocolo EditItemViewControllerDelegate.

La implementación actualizada de prepareForSegue(_:sender:) es bastante simple, como puede ver a continuación. Obtenemos una referencia al controlador de vista de elemento de edición y establecemos sus propiedades delegate y item. Debido a que la propiedad selection es del tipo Item?, lo vinculamos a la constante item para desenvolverlo de manera segura.

La implementación de EditItemViewController está casi completa. En su método viewDidLoad(), llenamos los campos de texto con los datos de la propiedad item. Debido a que la propiedad text de un campo de texto es de tipo String?, utilizamos la interpolación de cadenas para crear una cadena a partir del valor Float de la propiedad price del elemento. La interpolación de cadenas es bastante poderosa en Swift.

Paso 3: adopción del protocolo de delegado

Adoptar el protocolo EditItemViewControllerDelegate significa implementar el método controller(_:didUpdateItem:) como se muestra a continuación. Puede sorprenderle que no actualicemos el origen de datos de la vista de tabla. Todo lo que hacemos en el método de delegado es volver a cargar una fila de la vista de tabla.

La razón por la que no necesitamos actualizar el origen de datos de la vista de tabla, la matriz items, es que el elemento actualizado se pasó por referencia al controlador de vista de elemento de edición. En otras palabras, el objeto que el controlador de vista de elemento de edición ha actualizado es el mismo objeto que está contenido en la matriz items. Este es uno de los buenos beneficios de trabajar con instancias de clase. Se pasan por referencia.

No olvides guardar la lista de elementos para asegurarte de que los cambios se escriben en el disco. Ejecute la aplicación para probar la funcionalidad de edición.

3. Crear el controlador de vista de lista de compras

Antes de explorar la fuente de datos del controlador de vista de la lista de compras, creemos algunos andamios para trabajar. Cree una nueva subclase UITableViewController y asígnele el nombre ShoppingListViewController.

Abra ShoppingListViewController.swift y agregue dos propiedades de tipo [Item]:

  • items, que contendrán la lista completa de artículos    
  • shoppingList, que solo contendrá los artículos de la lista de compras

La idea es cargar la lista de elementos cada vez que se realiza un cambio en la lista, analizar la lista de elementos y extraer solo los elementos que tienen su propiedad inShoppingList establecida en true. Esos elementos luego se agregan a la lista shoppingList.

Otra opción sería almacenar la lista de compras en un archivo separado. La desventaja de este enfoque sería que tenemos que mantener los elementos en el controlador de vista de lista y los elementos en la lista de compras sincronizados. Esto es pedir problemas si me preguntas.

Paso 1: Agregar observadores de propiedades

Los observadores de propiedad son una excelente manera de responder a los cambios en el valor de una propiedad. Veamos cómo podemos aprovechar los observadores de propiedades para actualizar la propiedad shoppingList cada vez que cambia el valor de items.

La sintaxis es bastante fácil de entender. Los observadores de propiedad están envueltos en un cierre. Hay dos tipos de observadores de propiedades:

  • willSet, que se invoca antes de que se establezca el nuevo valor de la propiedad
  • didSet, que se invoca después de establecer el nuevo valor de la propiedad

En el ejemplo anterior, invocamos buildShoppingList() cada vez que se asigna un nuevo valor a items. Esto es lo que parece la implementación de buildShoppingList().

Filtramos los elementos de la matriz items, incluidos solo los elementos para los que inShoppingList se establece a true. El resultado se asigna a shoppingList.

También creamos un observador de propiedad didSet para la propiedad shoppingList. En este observador de propiedades, actualizamos la vista de tabla para reflejar el contenido de shoppingList, el origen de datos de la vista de tabla.

Paso 2: mostrar la lista de compras

Implementar los métodos del protocolo UITableViewDataSource ya debería ser un juego de niños. Eche un vistazo a la implementación a continuación.

No olvide declarar el identificador de reutilización de celda y registrar la clase UITableViewCell como lo hicimos en la clase ListViewController.

Paso 3: carga de elementos

Como expliqué anteriormente, la principal ventaja de utilizar la lista de elementos para almacenar y crear la lista de compras es que la aplicación almacena cada elemento en un solo lugar. Esto hace que la actualización de elementos en la lista y en la lista de compras no sea un dolor de cabeza. Los métodos loadItems() y pathForItems() son idénticos a los que implementamos en la clase ListViewController.

Cuando te encuentres duplicando el código, deberías escuchar un timbre de alarma sonando. El código de duplicación no es problemático mientras está implementando una nueva característica. Después, sin embargo, debería considerar la refacturación del código para minimizar la cantidad de duplicaciones en la base de códigos de la aplicación. Este es un concepto muy importante en el desarrollo de software y a menudo se lo conoce como DRY, Do not Repeat Yourself. Chris Peters escribió un excelente artículo sobre Envato Tuts+ sobre programación DRY.

Antes de usar la clase ShoppingListViewController, debemos actualizar el método viewDidLoad() de la clase. Además de configurar el título del controlador de vista, también cargamos la lista de elementos, que automáticamente rellena el arreglo shoppingList como vimos anteriormente.

El último paso es inicializar el controlador de vista de la lista de compras actualizando el guión gráfico. Esto implica agregar una instancia de UITableViewController, configurar su clase en ShoppingListViewController, incrustarla en un controlador de navegación y crear una transición de relación entre el controlador de la barra de pestañas y el controlador de navegación. Seleccione la vista de tabla del controlador de vista de lista de compras y establezca el número de celdas de prototipo en 0.

Creating the Shopping List View Controller

Ejecute la aplicación para ver si todo funciona correctamente. Por supuesto, la lista de compras está vacía y no tenemos forma de agregar artículos a la lista de compras. Vamos a remediar eso en el siguiente paso.

4. Agregar elementos a la lista de compras

Como escribí anteriormente, la idea es agregar un artículo a la lista de compras cuando se toca en el controlador de vista de lista. Para mejorar la experiencia del usuario, si un artículo está presente en la lista de compras, mostramos una marca de verificación verde a la izquierda del nombre del artículo. Si se toca un elemento que ya está en la lista de compras, se elimina de la lista de compras y la marca de verificación verde desaparece. Esto significa que tenemos que echar un vistazo al método tableView(_:didSelectRowAtIndexPath:) del protocolo UITableViewDelegate.

Antes de implementar tableView(_:didSelectRowAtIndexPath:), descargue los archivos fuente de esta lección. En la carpeta denominada Resources, busque los archivos llamados checkmark.png y checkmark@2x.png. Agregue ambos archivos al proyecto, porque los necesitamos en unos momentos.

En la primera línea de tableView(_:didSelectRowAtIndexPath:), enviamos a la vista de tabla un mensaje de deselectRowAtIndexPath(_:animated:) para anular la selección de la fila que tocó el usuario. Cada vez que se toca una fila, solo se debe resaltar por un instante, por lo tanto, esta adición. A continuación, buscamos el artículo que corresponde con la selección del usuario y actualizamos la propiedad inShoppingList del artículo (true pasa a ser false y viceversa).

Según el valor de la propiedad inShoppingList del elemento, mostramos u ocultamos la marca de verificación verde. Mostramos la marca de verificación configurando la propiedad image de la propiedad imageView de la celda de la vista de tabla. Una celda de vista de tabla incluye una vista de imagen a la izquierda (una instancia de la clase UIImageView). Al establecer la propiedad image de la vista de imagen en nil, la vista de la imagen está en blanco y no muestra ninguna imagen.

La implementación de tableView(_:didSelectRowAtIndexPath:) finaliza guardando la lista de elementos en el disco para asegurarse de que los cambios sean permanentes.

¿Cómo sabe la lista de compras cuando el usuario toca un elemento en el controlador de vista de lista? El controlador de vista de lista de compras no actualizará automáticamente su vista de tabla si se realizó un cambio en la lista de elementos en el controlador de vista de lista. Para evitar el acoplamiento cerrado, no queremos que el controlador de vista de lista y el controlador de vista de lista de compras se comuniquen directamente entre sí.

Una solución a este problema es el uso de notificaciones. Cada vez que el controlador de vista de lista hace un cambio en la lista de elementos, publica una notificación con un nombre específico para el centro de notificaciones, un objeto que administra las notificaciones. Los objetos que están interesados ​​en ciertas notificaciones pueden agregarse como observadores de esas notificaciones, lo que significa que pueden responder cuando esas notificaciones se envían al centro de notificaciones.

¿Cómo funciona todo esto? Hay tres pasos involucrados:

  • El controlador de vista de lista de compras comienza diciéndole al centro de notificaciones que está interesado en recibir notificaciones con un nombre de ShoppingListDidChangeNotification
  • el controlador de vista de lista publica una notificación al centro de notificaciones cada vez que actualiza la lista de elementos
  • cuando el controlador de vista de lista de compras recibe la notificación del centro de notificaciones, actualiza su fuente de datos y la vista de tabla en respuesta

Antes de implementar los tres pasos que acabo de describir, es una buena idea echar un vistazo más de cerca a la clase NSNotificationCenter.

En pocas palabras, NSNotificationCenter gestiona la transmisión de notificaciones. Los objetos en una aplicación pueden registrarse en un centro de notificaciones para recibir notificaciones usando addObserver(_:selector:name:object:) donde:   

  • el primer argumento es el objeto que recibirá las notificaciones (el observador)
  • selector es la acción que se invoca al observador cuando recibe la notificación
  • name es el nombre de la notificación
  • object es el objeto que desencadena el envío de la notificación

Si el último argumento se establece en nil, el observador recibe cada notificación con el nombre especificado.

Paso 1: recibir notificaciones

Vuelva a visitar el método viewDidLoad() de la clase ShoppingListViewController y agregue la instancia del controlador de vista como un observador para recibir notificaciones con un nombre de ShoppingListDidChangeNotification.

La acción que se desencadena cuando el controlador de vista recibe una notificación con ese nombre es updateShoppingList(_:). El último parámetro, object, es nil, porque no importa qué objeto envió la notificación.

Paso 2: responder a las notificaciones

El método que se activa cuando el observador recibe una notificación tiene un formato específico, como puede ver a continuación. Acepta un argumento, el objeto de notificación que es de tipo NSNotificación.

El objeto de notificación mantiene una referencia al objeto que publicó la notificación y también puede contener un diccionario con información adicional. La implementación del método updateShoppingList(_:) es bastante simple. Llamamos loadItems() al controlador de vista, lo que significa que la lista de elementos se carga desde el disco. El resto sucede automáticamente gracias a los observadores de propiedades que implementamos anteriormente.

Paso 3: envío de notificaciones

La tercera pieza del rompecabezas es publicar una notificación cada vez que el controlador de vista de lista cambie la lista de elementos. Podemos hacer esto en el método saveItems() de la clase ListViewController.

Primero solicitamos una referencia al centro de notificación predeterminado llamando a defaultCenter() en la clase NSNotificationCenter. A continuación, llamamos a postNotificationName(_:object:) en el centro de notificaciones predeterminado, pasando el nombre de la notificación, ShoppingListDidChangeNotification y el objeto que publica la notificación.

Antes de compilar el proyecto, asegúrese de modificar tableView(_:cellForRowAtIndexPath:) en ListViewController.swift como se muestra a continuación para mostrar una marca de verificación verde para los elementos que ya están presentes en la lista de compras.

Ejecute la aplicación de la lista de compras para darle un giro. ¿Ha notado que las ediciones de un artículo se reflejan automáticamente en la lista de compras?

Finishing Up the Shopping List Application

¿Listo para publicación?

Estupendo. ¿Dónde está el botón para publicar la aplicación de la lista de compras en la App Store? Todavía no hemos terminado. A pesar de que hemos sentado las bases de la aplicación de la lista de compras, no está lista para su publicación. También hay algunas cosas a considerar.

Escalabilidad

La aplicación de lista de compras es una implementación modesta de una lista de compras. Si la aplicación contiene cientos o miles de elementos, sería pertinente agregar funciones de búsqueda y secciones para ordenar los elementos alfabéticamente (como hicimos anteriormente en esta serie). Es importante darse cuenta de que la lista de elementos se escribe en el disco en su totalidad cada vez que se actualiza un elemento. Esto no es un problema cuando la lista es pequeña, pero lo será si la lista crece con el tiempo a cientos o miles de elementos.

Relaciones

Además, los usuarios pueden querer guardar más de una lista de compras. ¿Cómo lidiarías con eso? Una opción es almacenar cada lista de compras en un archivo separado, pero ¿cómo trataría los cambios realizados en los artículos? ¿Vas a actualizar cada lista de compras que contiene el artículo? Cuando empiece a tratar con relaciones, es mejor elegir un almacén de datos SQLite.

Core Data es un gran compañero si eliges seguir por ese camino. Es un marco potente y presenta muchas características que hacen que gran parte del código de nuestra aplicación de listas de compras quede obsoleto. Es cierto que Core Data trae consigo un poco más de gastos indirectos, por lo que es fundamental considerar primero si Core Data es adecuado para su aplicación, en otras palabras, si vale la pena la sobrecarga.

Características adicionales

También hay un gran potencial para funciones adicionales. La propiedad de precio de los artículos permanece sin usar en la implementación actual de la aplicación de la lista de compras. Además, sería bueno si el usuario pudiera marcar un ítem de la lista de compras tocando el ítem. Como puede ver, la implementación actual de la aplicación de la lista de compras es solo un comienzo modesto.

Conclusión

Aunque la aplicación de la lista de compras no está lista para la App Store, no puede negar que funciona según lo planeado y le ha mostrado varios aspectos nuevos del desarrollo de Cocoa, como notificaciones e implementación de un protocolo de delegado personalizado.

Ahora sabe qué esperar del iOS SDK y cómo es el desarrollo de iOS. Depende de usted decidir si desea continuar su viaje y convertirse en un desarrollador experto de iOS. Si eliges continuar con el desarrollo de iOS, entonces te proporcionaré algunos recursos geniales en la próxima y última entrega de esta serie.

Si tiene preguntas o comentarios, puede dejarlos en los comentarios a continuación o comunicarse conmigo en Twitter.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.