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: Conceptos básicos de Table View

by
Difficulty:BeginnerLength:LongLanguages:
This post is part of a series called iOS From Scratch With Swift.
iOS From Scratch With Swift: Auto Layout Basics
iOS From Scratch With Swift: Navigation Controllers and View Controller Hierarchies

Spanish (Español) translation by CYC (you can also view the original English article)

Las vistas de tabla se encuentran entre los componentes más utilizados del framework UIKit y son parte integral de la experiencia del usuario en la plataforma iOS. Las vistas de tabla hacen una cosa y lo hacen muy bien, presentan una lista ordenada de elementos. La clase UITableView es un buen lugar para continuar nuestro viaje a través del framework UIKit, porque combina varios conceptos clave de Cocoa Touch y UIKit, que incluyen vistas, protocolos y reutilización.

Data Source y Delegate

La clase UITableView, uno de los componentes clave del framework UIKit, está altamente optimizada para mostrar una lista ordenada de elementos. Las vistas de tabla se pueden personalizar y adaptar a una amplia gama de casos de uso, pero la idea básica sigue siendo la misma, presentando una lista ordenada de elementos.

A Plain Table View

La clase UITableView solo está encargada de presentar los datos como una lista de filas. Los datos que se muestran se gestionan mediante el objeto de data source de la vista de tabla, accesible a través de la propiedad dataSource de la vista de tabla. La fuente de datos puede ser cualquier objeto que se ajuste al protocolo UITableViewDataSource, un protocolo Objective-C. Como veremos más adelante en este artículo, el origen de datos de la vista tabla suele ser el controlador de vista que administra la vista de la que es una subvista la vista de tabla.

De forma similar, la vista de tabla solo es responsable de detectar toques en la vista de tabla. No es responsable de responder a los toques. La vista de tabla también tiene una propiedad llamada delegate. Siempre que la vista de tabla detecte un evento táctil, notifica a su delegado de ese evento táctil. Es responsabilidad del delegado de la vista de tabla responder al evento táctil.

Al tener un objeto de fuente de datos que gestiona sus datos y un objeto delegado que maneja la interacción del usuario, la vista de tabla puede enfocarse en la presentación de datos. El resultado es un componente de UIKit altamente reutilizable y en perfecto alineamiento con el patrón MVC (Modelo-Vista-Controlador), del que hablamos anteriormente en esta serie. La clase UITableView hereda de UIView, lo que significa que solo es responsable de mostrar los datos de la aplicación.

Un objeto fuente de datos es similar, pero no idéntico a, un objeto delegado. Un objeto delegate es el control delegado de la interfaz de usuario por el objeto delegate. Sin embargo, un objeto de fuente de datos es un control de datos delegado.

La vista de tabla pregunta al objeto de fuente de datos por los datos que debería mostrar. Esto implica que el objeto fuente de datos también es responsable de gestionar los datos que alimenta la vista de tabla.

Componentes de Table View

La clase UITableView hereda de UIScrollView, una subclase de UIView que proporciona soporte para mostrar contenido que es más grande que el tamaño de la ventana de la aplicación.

Una instancia UITableView se compone de filas con cada fila que contiene una celda, una instancia de UITableViewCell o una subclase de la misma. A diferencia de la contraparte de UITableView en OS X, NSTableView, las instancias de UITableView tienen una columna de ancho. Los conjuntos de datos anidados y las jerarquías se pueden mostrar utilizando una combinación de vistas de tabla y un controlador de navegación (UINavigationController). Hablamos sobre los controladores de navegación en el siguiente artículo de esta serie.

Ya mencioné que las vistas de tabla solo se encargan de mostrar los datos entregados por el objeto de fuente de datos y detectar los eventos táctiles, que se enrutan al objeto delegado. Una vista de tabla no es más que una vista que gestiona varias subvistas, las celdas de la vista de tabla.

Un nuevo proyecto

En lugar de sobrecargarte de teoría, es mejor y más divertido crear un nuevo proyecto de Xcode y mostrarte cómo configurar una vista de tabla, completarla con datos y hacer que responda a eventos táctiles.

Abre Xcode, crea un nuevo proyecto (Archivo > Nuevo > Proyecto...) y selecciona la plantilla Aplicación de vista única.

A New Project

Asigna un nombre al proyecto como Table Views, asigna un nombre e identificador de organización y configura Dispositivos como iPhone. Dile a Xcode dónde quieres guardar el proyecto y presiona en Crear.

Project Configuration

El nuevo proyecto debería parecerte familiar, porque elegimos la misma plantilla de proyecto anteriormente en esta serie. Xcode ya creó una clase de delegado de aplicación para nosotros, AppDelegate, y también nos dio una clase de controlador de vista para comenzar, ViewController.

Agregando una vista de tabla

Compila y ejecuta el proyecto para ver con qué estamos empezando. La pantalla blanca que ves cuando ejecutas la aplicación en el simulador es la vista del controlador de vista que Xcode creó para nosotros en el storyboard.

La forma más fácil de agregar una vista de tabla a la vista del controlador de vista es en el Main.storyboard. del proyecto. Abre Main.storyboard y ubica la Biblioteca de objetos en la derecha. Explora la Biblioteca de objetos y arrastra una instancia de UITableView a la vista del controlador de vista.

Adding a Table View

Si las dimensiones de la vista de tabla no se ajustan automáticamente con los límites de la vista del controlador de vista, entonces ajusta manualmente sus dimensiones arrastrando los cuadrados blancos en los bordes de la vista de tabla. Recuerda que los cuadrados blancos solo son visibles cuando se selecciona la vista de tabla.

Adjusting the Dimensions of the Table View

Agrega las restricciones de diseño necesarias a la vista de tabla para asegurarte de que la vista de tabla abarca el ancho y el alto de su vista padre. Esto debería ser fácil si has leído el artículo anterior sobre Diseño automático.

Adding Layout Constraints

Esto es todo lo que tenemos que hacer para agregar una vista de tabla a la vista de nuestro controlador de vista. Compila y ejecuta el proyecto para ver el resultado en el simulador. Todavía verás una vista blanca ya que la vista de tabla todavía no tiene datos para mostrar.

Una vista de tabla o table view tiene dos estilos predeterminados, simples y agrupados. Para cambiar el estilo actual de la vista de tabla (Plain), selecciona la vista de tabla en el storyboard, abre el Inspector de atributos y cambia el atributo de estilo a Agrupado. Para este proyecto, trabajaremos con una vista de tabla simple, así que asegúrate de volver a poner en claro el estilo de la vista de tabla.

A Grouped Table View

Conectando Data Source y Delegate

Ya sabes que se supone que una vista de tabla debe tener una fuente de datos y un delegado. Por el momento, la vista de tabla no tiene una fuente de datos o un delegado. Necesitamos conectar el dataSource y delegate puntos de venta de la vista de tabla a un objeto que cumpla con los protocolos UITableViewDataSource y UITableViewDelegate.

En la mayoría de los casos, ese objeto es el controlador de vista que administra la vista de la cual la vista de tabla es una subvista. Selecciona la vista de tabla en el storyboard, abre el Inspector de conexiones a la derecha y arrastra desde el orificio dataSource (el círculo vacío a la derecha) hasta el Controlador de vista. Haz lo mismo para la salida de delegate. Nuestro controlador de vista ahora está conectado para actuar como fuente de datos y delegado de la vista de tabla.

Connecting Data Source and Delegate

Si ejecutas la aplicación como está, se bloqueará casi al instante. La razón de esto se aclarará en unos momentos. Antes de examinar de cerca el protocolo UITableViewDataSource, debemos actualizar la clase ViewController.

Los objetos de fuente de datos y delegados de la vista de tabla deben ajustarse al protocolo UITableViewDataSource y UITableViewDelegate, respectivamente. Como vimos anteriormente en esta serie, los protocolos se enumeran después de la superclase de la clase. Múltiples protocolos son separados por comas.

Creando el origen de datos

Antes de comenzar a implementar los métodos del protocolo de origen de datos, necesitamos algunos datos para mostrarlos en la vista de tabla. Almacenaremos los datos en una matriz, así que primero agreguemos una nueva propiedad a la clase ViewController. Abre ViewController.swift y agrega una propiedad, fruits, del tipo [String].

En el método viewDidLoad() del controlador de vista, llenamos la propiedad fruits con una lista de nombres de frutas, que luego mostraremos en la vista de tabla. El método viewDidLoad() se invoca automáticamente después de que la vista del controlador de vista y sus subvistas se cargan en la memoria, de ahí el nombre del método. Por lo tanto, es un buen momento para llenar la matriz fruits.

La clase UIViewController, la superclase de la clase ViewController, también define un método viewDidLoad(). La clase ViewController anula el método viewDidLoad() definido por la clase UIViewController. Esto es indicado por la palabra clave override.

Anular un método de una superclase nunca está exento de riesgos. ¿Qué pasa si la clase UIViewController hace algunas cosas importantes en el método viewDidLoad()? ¿Cómo nos aseguramos de no romper nada cuando anulamos el método viewDidLoad()?

En situaciones como esta, es importante invocar primero el método viewDidLoad() de la superclase antes de hacer cualquier otra cosa en el método viewDidLoad(). La palabra clave super se refiere a la superclase y le enviamos un mensaje de viewDidLoad(), invocando el método viewDidLoad() de la superclase. Este es un concepto importante que debes comprender, así que asegúrate de comprenderlo antes de continuar.

Protocolo fuente de datos

Debido a que asignamos el controlador de vista como el objeto de origen de datos de la vista de tabla, la vista de tabla le pregunta al controlador de vista qué debería mostrar. La primera información que la vista de tabla necesita de su fuente de datos es la cantidad de secciones que debe mostrar.

La vista de tabla hace esto invocando el método numberOfSectionsInTableView(_:) en su origen de datos. Este es un método opcional del protocolo UITableViewDataSource. Si el origen de datos de la vista de tabla no implementa este método, la vista de tabla asume que necesita mostrar solo una sección. Implementamos este método de todos modos ya que lo vamos a necesitar más adelante en este artículo.

Te estaráa preguntando "¿Qué es una sección de vista de tabla?" Una sección de vista de tabla es un grupo de filas. La aplicación Contactos en iOS, por ejemplo, agrupa contactos basados en la primera letra del nombre o apellido. Cada grupo de contactos forma una sección, que está precedida por un encabezado de sección en la parte superior de la sección y/o un pie de página de sección en la parte inferior de la sección.

El método numberOfSectionsInTableView(_ :) acepta un argumento, tableView, que es la vista de tabla que envió el mensaje al objeto de origen de datos. Esto es importante, ya que permite que el objeto fuente de datos sea la fuente de datos de múltiples vistas de tabla si es necesario. Como puedes ver, la implementación de numberOfSectionsInTableView(_:) es bastante fácil.

Ahora que la vista de tabla sabe cuántas secciones necesita mostrar, le pregunta a su fuente de datos cuántas filas contiene cada sección. Para cada sección en la vista de tabla, la vista de tabla envía al origen de datos un mensaje de tableView (_:numberOfRowsInSection:). Este método acepta dos argumentos, la vista de tabla que envía el mensaje y el índice de sección cuya vista de tabla quiere saber el número de filas.

La implementación de este método es bastante simple, como puedes ver a continuación. Comenzamos declarando una constante, numberOfRows, y le asignamos la cantidad de elementos en la matriz fruits llamando count en la matriz. Devolvemos numberOfRows al final del método.

La implementación de este método es tan fácil que podríamos hacerlo un poco más conciso. Echa un vistazo a la implementación a continuación para asegurarte de que comprendes lo que ha cambiado.

Si tratamos de compilar el proyecto en su estado actual, el compilador lanzará un error. El error nos dice que la clase ViewController no se ajusta al protocolo UITableViewDataSource, porque aún no hemos implementado los métodos requeridos del protocolo. La vista de tabla espera que el origen de datos, la instancia de ViewController, devuelva una celda de vista de tabla para cada fila en la vista de tabla.

Necesitamos implementar tableView(_:cellForRowAtIndexPath:), otro método del protocolo UITableViewDataSource. El nombre del método es bastante descriptivo. Al enviar este mensaje a su origen de datos, la vista de tabla solicita su origen de datos para la celda de vista de tabla de la fila especificada por indexPath, el segundo argumento del método.

Antes de continuar, me gustaría tomar un minuto para hablar sobre la clase NSIndexPath. Como explica la documentación, "La clase NSIndexPath representa la ruta a un nodo específico en un árbol de colecciones de matrices anidadas". Una instancia de esta clase puede contener uno o más índices. En el caso de una vista de tabla, contiene un índice para la sección en la que se encuentra un elemento y la fila de ese elemento en la sección.

Una vista de tabla nunca tiene más de dos niveles de profundidad, el primer nivel es la sección y el segundo nivel es la fila de la sección. Aunque NSIndexPath es una clase Foundation, el framework UIKit agrega un puñado de métodos adicionales a la clase que facilitan el trabajo con vistas de tabla. Examinemos la implementación del método tableView(_:cellForRowAtIndexPath:).

Reutilizando celdas de vista de tabla

Al principio de esta serie, te dije que las vistas son un componente importante de una aplicación de iOS. Pero también debes saber que las vistas son costosas en términos de la memoria y la potencia de procesamiento que consumen. Cuando se trabaja con vistas de tabla, es importante reutilizar las celdas de la vista de tabla tanto como sea posible. Al reutilizar las celdas de la vista de tabla, la vista de tabla no tiene que inicializar una nueva celda de vista de tabla desde cero cada vez que una nueva fila necesita una celda de vista de tabla.

Las celdas de vista de tabla que se mueven fuera de la pantalla no se descartan. Las celdas de la vista de tabla se pueden marcar para su reutilización especificando un identificador de reutilización durante la inicialización. Cuando una celda de vista de tabla marcada para su reutilización se mueve fuera de la pantalla, la vista de tabla la coloca en una cola de reutilización para su uso posterior.

Cuando el origen de datos solicita su vista de tabla para una nueva celda de vista de tabla y especifica un identificador de reutilización, la vista de tabla inspecciona primero la cola de reutilización para verificar si una celda de vista de tabla con el identificador de reutilización especificado está disponible. Si no hay una celda de vista de tabla disponible, la vista de tabla crea una nueva instancia y la pasa a su origen de datos. Eso es lo que sucede en la primera línea de código.

La fuente de datos de la vista de tabla pregunta a la vista de tabla por una celda de vista de tabla enviándole un mensaje de dequeueReusableCellWithIdentifier(_:forIndexPath:). Este método acepta el identificador de reutilización que mencioné anteriormente, así como la ruta de índice de la celda de la vista de tabla.

El compilador te dirá que cellIdentifier es un "identificador no resuelto". Esto simplemente significa que estamos usando una variable o constante, que aún no hemos declarado. Encima de la declaración de la propiedad fruits, agrega la siguiente declaración para cellIdentifier.

¿Cómo sabe la vista de tabla cómo crear una nueva celda de vista de tabla? En otras palabras, ¿cómo sabe la vista de tabla qué clase usar para crear una nueva celda de vista de tabla? La respuesta es simple. En el storyboard, creamos un prototipo de celda y le damos un identificador de reutilización. Hagámoslo ahora.

Creando un prototipo de celda

Abre Main.storyboard, selecciona la vista de tabla que agregaste anteriormente, y abre el Inspector de Atributos a la derecha. El campo Prototype Cells está actualmente en 0. Crea un prototipo de celda configurándolo en 1. Ahora deberías ver un prototipo de celda en la vista de tabla.

Creating a Prototype Cell

Selecciona la celda del prototipo y echa un vistazo al Inspector de Atributos a la derecha. El Style está configurado actualmente en Custom. Cambia esto a Basic. Una celda de vista de tabla básica es una celda de vista de tabla simple que contiene una etiqueta. Eso está bien para la aplicación que estamos construyendo. Antes de volver a la clase ViewController, establece Identifier para CellIdentifier. El valor debe ser idéntico al valor asignado a la constante cellIdentifier que declaramos hace un momento.

Configuring the Prototype Cell

Configurar la celda de vista de tabla

El siguiente paso consiste en rellenar la celda de la vista de tabla con los datos almacenados en la matriz fruits. Esto significa que necesitamos saber qué elemento usar de la matriz fruits. Esto a su vez significa que de alguna manera necesitamos saber la fila o el índice de la celda de la vista de tabla.

El argumento indexPath del método tableView(_:cellForRowAtIndexPath:) contiene esta información. Como mencioné anteriormente, tiene algunos métodos adicionales para facilitar el trabajo con vistas de tabla. Uno de estos métodos es row, que devuelve la fila para la celda de la vista de tabla. Buscamos la fruta correcta preguntando al conjunto fruits por el elemento en indexPath.row, usando la conveniente sintaxis de subíndices de Swift.

Finalmente, establecemos el textLabel de la propiedad etiqueta de texto de la celda de la vista de tabla en el nombre de la fruta que obtuvimos de la matriz fruits. La clase UITableViewCell es una subclase UIView y tiene varias subvistas. Una de estas subvistas es una instancia de UILabel y usamos esta etiqueta para mostrar el nombre de la fruta en la celda de la vista de tabla. Que la propiedad textLabel sea nula depende o no del estilo de UITableViewCell. Es por eso que la propiedad textLabel es seguida por un signo de interrogación. Esto se conoce mejor como encadenamiento opcional.

El método tableView (_:cellForRowAtIndexPath:) espera que devolvamos una instancia de la clase UITableViewCell (o una subclase de la misma) y eso es lo que hacemos al final del método.

Ejecuta la aplicación Ahora deberías tener una vista de tabla totalmente funcional llena con la matriz de nombres de fruta almacenados en la propiedad fruits del controlador de vista.

Secciones

Antes de echar un vistazo al protocolo UITableViewDelegate, quiero modificar la implementación actual del protocolo UITableViewDataSource agregando secciones a la vista de tabla. Si la lista de frutas creciera con el tiempo, sería mejor y más amigable ordenar las frutas alfabéticamente y agruparlas en secciones según la primera letra de cada fruta.

Si queremos agregar secciones a la vista de tabla, la matriz actual de nombres de fruta no será suficiente. En cambio, los datos deben dividirse en secciones con las frutas en cada sección ordenadas alfabéticamente. Lo que necesitamos es un diccionario. Comienza por declarar una propiedad nueva, alphabetizedFruits, del tipo [String: [String]] en la clase ViewController.

En viewDidLoad(), utilizamos la matriz fruits para crear un diccionario de frutas. El diccionario debe contener una matriz de frutas para cada letra del alfabeto. Omitimos una letra del diccionario, si no hay frutas para esa letra en particular.

El diccionario se crea con la ayuda de un método auxiliar, alphabetizeArray(_ :). Acepta la matriz fruits como argumento. El método alphabetizeArray(_ :) puede ser un poco abrumador a primera vista, pero su implementación es bastante sencilla.

Creamos un diccionario mutable, result del tipo [String: [String]], que usamos para almacenar las matrices de frutas alfabetizadas, una matriz para cada letra del alfabeto. Recorremos los elementos de array, el primer argumento del método, y extraemos la primera letra del nombre del elemento, haciéndolo en mayúscula. Si result ya contiene una matriz para la letra, agregamos el elemento a esa matriz. Si no es así, creamos una matriz con el elemento y la agregamos result.

Los artículos ahora están agrupados según su primera letra. Sin embargo, los grupos no están alfabetizados. Eso es lo que sucede en el segundo bucle for. Analizamos result y ordenamos cada matriz alfabéticamente.

No te preocupes si la implementación de alphabetizeArray(_ :) no está del todo clara. En este tutorial, nos centramos en las vistas de tabla, no en la creación de una lista alfabética de frutas.

Cantidad de Secciones

Con la nueva fuente de datos en su lugar, lo primero que debemos hacer es actualizar la implementación de numberOfSectionsInTableView(_ :). En la implementación actualizada, le pedimos al diccionario, alfabetizadoFruits, sus claves. Esto nos da una matriz que contiene todas las claves del diccionario. La cantidad de elementos en keys es igual a la cantidad de secciones en la vista de tabla. Es así de simple.

También necesitamos actualizar tableView(_: numberOfRowsInSection :). Como hicimos en numberOfSectionsInTableView(_ :), preguntamos por alphabetizedFruits por sus claves y ordenamos el resultado. El orden de la matriz de claves es importante, porque los pares clave-valor de un diccionario están desordenados. Esta es una diferencia clave entre matrices y diccionarios. Esto es algo que a menudo hace tropezar a las personas que son nuevas en la programación.

Luego buscamos la clave de sortedKeys que corresponde a section, el segundo parámetro de tableView(_: numberOfRowsInSection :). Usamos la tecla para buscar la matriz de frutas para la sección actual, utilizando un enlace opcional. Finalmente, devolvemos la cantidad de artículos en la matriz resultante de frutas.

Los cambios que debemos hacer a tableView(_: cellForRowAtIndexPath :) son similares. Solo cambiamos la forma en que buscamos el nombre de la fruta que la celda de la vista de tabla muestra en su etiqueta.

Si tuvieras que ejecutar la aplicación, no verías encabezados de sección como los que ves en la aplicación Contactos. Esto se debe a que necesitamos decirle a la vista de tabla lo que debe mostrarse en cada encabezado de sección.

La opción más obvia es mostrar el nombre de cada sección, es decir, una letra del alfabeto. La forma más sencilla de hacerlo es implementando tableView(_:titleForHeaderInSection:), otro método definido en el protocolo UITableViewDataSource. Echa un vistazo de su implementación a continuación. Es similar a la implementación de tableView(_: numberOfRowsInSection :). Ejecuta la aplicación para ver cómo se ve la vista de tabla con las secciones.

Delegación

Además del protocolo UITableViewDataSource, el framework UIKit también define el protocolo UITableViewDelegate, el protocolo al que el delegado de la vista de tabla debe cumplir.

En el storyboard, ya configuramos el controlador de vista como el delegado de la vista de tabla. Aunque no hemos implementado ninguno de los métodos delegados definidos en el protocolo UITableViewDelegate, la aplicación funciona bien. Esto se debe a que todos los métodos del protocolo UITableViewDelegate son opcionales.

Sin embargo, sería bueno poder responder a los eventos táctiles. Cada vez que un usuario toque una fila, deberíamos poder imprimir el nombre de la fruta correspondiente en la consola de Xcode. Aunque esto no es muy útil, te mostrará cómo funciona el patrón de delegado.

Implementar este comportamiento es fácil. Todo lo que tenemos que hacer es implementar el método tableView(_:didSelectRowAtIndexPath:) del protocolo UITableViewDelegate.

La obtención del nombre de la fruta que corresponde a la fila seleccionada ya debería ser familiar. La única diferencia es que imprimimos el nombre de la fruta en la consola de Xcode.

Puede sorprenderte que usemos el diccionario alphabetizedFruits para buscar la fruta correspondiente. ¿Por qué no le preguntamos a la vista de tabla o a la celda de vista de tabla por el nombre de la fruta? Esa es una muy buena pregunta. Déjame explicarte qué sucede.

Una celda de vista de tabla es una vista y su único propósito es mostrar información al usuario. No sabe qué se muestra aparte de cómo mostrarlo. La vista de tabla en sí no es responsable de conocer su origen de datos, solo sabe cómo mostrar las secciones y filas que contiene y administra.

Este ejemplo es otra buena ilustración de la separación de preocupaciones del patrón Modelo-Vista-Controlador (MVC) que vimos anteriormente en esta serie. Las vistas no saben nada sobre los datos de la aplicación, aparte de cómo mostrarla. Si deseas escribir aplicaciones iOS confiables y robustas, es muy importante conocer y respetar esta separación de responsabilidades.

Conclusión

Las vistas de tabla no son tan complicadas una vez que comprendas cómo se comportan y conozcas los componentes involucrados, como la fuente de datos y los objetos delegados a los que se dirige la vista de tabla.

En este tutorial, solo echamos un vistazo de lo que es capaz de hacer una vista de tabla. En el resto de esta serie, volveremos a visitar la clase UITableView y exploraremos algunas piezas más del rompecabezas. En la próxima entrega de esta serie, echaremos un vistazo a los controladores de navegación.

Si tienes preguntas o comentarios, puedes dejarlos en los comentarios a continuación o comunicarte 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.