Home » Cursos » Lecciones por prototipos (I): Vistas de tabla (UITableView)
Lecciones Prototipos (I): UITableView

Lecciones por prototipos (I): Vistas de tabla (UITableView)

Según Google, un prototipo es: “Primer ejemplar que se fabrica de una figura, un invento u otra cosa, y que sirve de modelo para fabricar otras iguales, o molde original con el que se fabrica.”. Pero en desarrollo, el prototipo es mucho más que eso: es aquello que construimos en primer lugar con el esqueleto de nuestra app y que luego vamos modificando y ampliando hasta ir convirtiendo en la versión final. Un prototipo de una app puede ser una vista normal de tipo tabla o de colección, que muestra cuadros vacíos o textos Lorem ipsum. O el prototipo de un juego puede ser un cuadro que se mueve por una línea y salta cuando tocamos, que luego se convertirá en un personaje corriendo por un paisaje con scroll. Pero la idea está ahí, plasmada, en lo que llamamos prototipos.

En mi larga carrera de formador, no solo en tecnologías Apple, también en sistemas de bases de datos Oracle, distribuciones Linux empresariales, certificación y firma digital y un sin fin de experiencias que he tenido la suerte de tener, los prototipos siempre han sido una forma óptima de comenzar un proyecto y de, principalmente, entender cómo funciona o va a funcionar. Por este motivo, cuando Apple presentó una herramienta como los Playgrounds en Xcode (junto a la llegada de Swift) no pude más que sentirme feliz por aquello, más aun cuando luego lo portó al iPad con la app Swift Playgrounds.

Siguiendo el estilo de enseñanza de Apple Coding Academy y nuestros contenidos formativos, donde lo importante es entender la base y el concepto, para después explotarlo, hoy damos comienzo a una serie de lecciones, o curso, que vamos a dar en Apple Coding: lecciones por prototipos. Cada semana veremos, de una forma fácil y sencilla de entender, cómo funcionan internamente elementos clave del sistema y que nos proporcionarán la forma de, a través de prototipos, crear esqueletos que luego nos sirvan para realizar una app, un juego, una prueba de concepto de una funcionalidad y casi cualquier cosa que queramos. Pero sobre todo, y por encima de todo, entender más claramente su esencia, su funcionamiento y su base. Aprender desde la raíz.

Es importante que notéis que estas lecciones funcionan por igual, al 100%, tanto en Xcode 8.0 o superior, como en la app Swift Playgrounds para iPad. En ambas dos podréis probar y entender cómo funciona lo que os explicamos. En concreto, esta lección está realizada y probada en Xcode 8.2.1.

Vistas de tabla (UITableView)

Una vista de tabla (o Table View) tiene la facultad de crear una estructura de filas o rows, donde puede haber igualmente secciones que dividan estas rows con su propia cabecera cada una. La construcción de las mismas depende del propio sistema pero nosotros hemos de proporcionarle a esta, a través de su fuente de datos, qué ha de mostrar en cada celda de cada fila.

La tabla pertenece a la librería UIKit, base del desarrollo de apps y podemos crearla al instanciar un objeto UITableView (vista de tabla). Cada una de las celdas que muestra es a su vez un objeto UITableViewCell. Y cada una de estas celdas (como vistas que son) a su vez puede tener otras vistas que podemos agregar como subvistas. La celda como tal no muestra nada, es más bien un contenedor donde luego nosotros podemos colocar imágenes, etiquetas, campos o lo que queramos.

La vista de tabla, en primera instancia, crea un objeto UITableView y luego necesita dos protocolos que le permiten delegar tareas en la subclase que creemos. Tenemos la opción de crear la vista de tabla tal cual, o, la mejor opción, podemos crear un objeto UITableViewController, hija de UIViewController y que además ya está conformada a los protocolos de delegación UITableViewDelegate y UITableViewDataSource. El primero proporciona elementos clave para controlar los eventos que la tabla puede provocar (como haber pulsado en una fila), mientras el segundo establece la fuente de datos que usará esta para ser dibujada.

Cuando nosotros, por programación, creamos una vista controlador de una tabla, lo único que tenemos que hacer es sobrecargar los métodos necesarios para controlar las delegaciones de la tabla y así proporcionar la información que nos interesa. Pero en contra que podamos pensar que la tabla se dibuja entera, no es así: se dibuja fila a fila. Y cuando hacemos scroll por la pantalla, el sistema va solicitando, vía delegación, el dato de la fila que va a mostrar antes que este se muestre, descartando y borrando previamente los que salen de la pantalla para optimizar la memoria.

Por lo tanto, hemos de entender que una vista de tabla tiene un función plenamente dinámica en el que por cada interacción del usuario puede invocar funciones que nosotros hemos de sobrecargar en su controlador, con el objetivo de darle una funcionalidad o contenido específico.

Vamos a crear el primer esqueleto donde tengamos el controlador que nos muestre una tabla en el editor asistente de un playground. Para ello importamos las librerías UIKit y PlaygroundSupport. La primera da funcionalidad a todos los componentes de una app en iOS, mientras la segunda proporciona la funcionalidad para crear un playground interactivo que nos permita mostrar y probar la tabla que creemos. Luego creamos una clase vistaTabla de tipo UITableViewController, vacía por ahora, y creamos un controlador de navegación UINavigationController que usamos para poner la tabla en la parte viva del Playground y que podamos navegar por ella.

Ahora solo tenemos que abrir el editor asistente en Xcode. Para ello vamos al menú View y luego en Assistant Editor pulsamos en Show Assistant Editor. En ese momento tendremos una vista a la derecha con una tabla vacía, lo que indica que nuestro primer código funciona y ya tenemos una tabla que podemos tocar y mover. Una tabla que responde a los gestos tanto en Xcode con el ratón como en Swift Playgrounds con gestos táctiles. No ha sido muy complicado.

Tabla Vacía

Configurando la tabla

El siguiente paso es determinar qué datos vamos a mostrar. Para ello nada más sencillo que crear un array, aunque obviamente los datos pueden provenir de cualquier fuente que nos interese. Así que vamos a crear una colección de tipo String y le damos valores. Lo ponemos como propiedad de la clase vistaTabla que ya hemos creado.

Ahora pasamos a configurar la tabla, y para ello debemos empezar a sobrecargar (sobrescribir) funciones que forman parte de la delegación. En este caso, vamos a sobrecargar la función numberOfSections, aunque no sea necesario hacerlo en un principio.

En esta función devolvemos un entero que le dice a nuestra tabla el total de secciones que tendrá. Como hemos dicho, no es estrictamente necesario usarlo si solo va a ver una sección, pero lo dejamos para no olvidarnos que la hemos configurado así y por si acaso hacemos algo luego.

Lo siguiente que debemos hacer es decirle a la tabla cuántas filas va a tener nuestra tabla. En contra de lo que podamos pensar, las tablas no pueden tener elementos dinámicos: tienen elementos fijos definidos antes de mostrarse. Por eso cuando queremos ver si ha llegado un nuevo correo o si tenemos algún nuevo tweet, refrescamos la tabla y esta se vuelve a dibujar. En nuestro caso, el total de filas está claro: será el número de elementos que tenemos en el array datos que hemos creado.

Estamos sobrecargando la función tableView, en aquella especificación donde tenemos un parámetro externo llamado numberOfRowsInSection. En esta función hemos de devolver el total de elementos que tendrá la sección cuyo valor vienen en el parámetro interno section. Eso sí, como solo tenemos una sección, devolvemos el total de datos sin hacer más preguntas.

Estas funciones que estamos sobrecargando (o sobrescribiendo) ya nos las da el sistema. Basta empezar a escribir para que aparezca el listado de las mismas y elegir una (sin olvidar añadir la palabra override en caso que el sistema no la ponga automáticamente).

En cuanto ponemos la función que hemos puesto arriba, obtenemos un error que nos dice cuál es el siguiente paso; qué es lo que nos falta: proporcionar los datos que irán en cada fila. Porque como vemos el error dice: failed to obtain a cell from its dataSource. Es decir, entre todos los números y datos que nos da para el error, lo importante es ese mensaje. Así que vamos a ello. Vamos a darle los datos.

Rellenando la tabla con los datos

El último fallo que hemos tenido se debe a que al decirle que tenemos 5 filas, ha intentado llamar a una función que le diga qué celda ha de poner en cada una de las filas de la tabla. Pero como no hemos sobrecargado esta función (todavía) la función por defecto devuelve un error y por eso no nos deja continuar. Así que ahora tenemos que usar esa sobrecarga: aquella que el sistema llama cada vez que dibujamos una fila.

La función que buscamos es la misma tableview, pero esta vez con el parámetro externo cellForRowAt que recibe un dato de tipo IndexPath llamado indexPath (ojo a las mayúsculas y minúsculas). Esta función devuelve un tipo UITableViewCell, es decir, devuelve el objeto de tipo celda que se va a colocar en cada una de las filas de nuestra tabla.

Pero ojo, como ya hemos dicho antes, las celdas no son nada como tal: son un hueco donde poner cosas, así que hemos de crear lo que realmente vamos a poner. En este caso: una etiqueta UILabel con el contenido de la cadena que tenemos en nuestra propiedad datos que usaremos para mostrar datos en la tabla.

Hemos puesto una celda vacía, así que ahora hemos dejado de tener el error anterior pero seguimos viendo la tabla vacía ya que no hemos puesto nada. Vamos a hacer que haya algo.

Analicemos qué hemos hecho: primero hemos creado una celda que tenga un ancho igual al mismo ancho de la tabla accediendo a su ancho con tableView.frame.size.width. La función delegada tableView en todas sus versiones, recibe como primer parámetro la tabla con que trabajamos, así que podemos acceder al cuadrado que ocupa (su frame) y dentro de él a su tamaño en ancho y alto (la propiedad size) para luego leer el ancho (la propiedad width). De alto, le hemos dado 100 puntos, que será más que suficiente para que entre nuestro contenido.

Luego hemos creado un objeto UILabel y le hemos dado el mismo tamaño que la celda pasándole su propiedad frame. Y una vez creada la etiqueta hemos asignado su propiedad text (que define el texto como cadena que será mostrado) leyendo de nuestro array datos.

Y aquí, ahora hay que fijarse en el parámetro indexPath que hemos obtenido. Este es un tipo de dato que representa el camino o ruta a un nodo específico en un árbol o una colección de arrays anidados. En este caso, tenemos dos posibles opciones de acceso: si usamos su propiedad item accederemos al número de elemento, desde 1 hasta el final, sin tener en cuenta si tiene más de una sección o no. Simplemente al número de fila tal cual, insistimos, sin tener en cuenta secciones. Si usamos la propiedad row accederemos a una fila concreta pero siempre de la sección que indique la propiedad section. En este caso, este dato es de solo lectura, por lo que es el sistema el que nos informa de qué dato es el que necesita a través de este dato de tipo IndexPath. Y como solo tenemos una sección, accedemos por item y nos devuelve la fila donde estamos, empezando por 0, justo lo que necesitamos para el array.

Por último, añadimos la etiqueta a la celda invocando el método addSubview. Una vez lo pongamos vemos nuestro resultado.

TableView Data

Ya tenemos nuestra tabla con datos y han sido unas pocas líneas nada más.

Secciones y filas

Vamos a crear una sección más para probar cómo sería, pero para ello debemos tocar varias cosas. Lo primero es devolver 2 en la delegación donde indicábamos cuál era el total de secciones. Al hacerlo, vemos que los datos de nuestra tabla salen duplicados. Pero tranquilos, enseguida lo arreglamos.

Vamos a crear un nuevo array con datos para la nueva sección.

Y ahora vamos a empezar a retocar el código. Lo primero es hacer que la función que devuelve el total de filas cambie y evalúe no un mismo dato, si no queremos saber qué sección es la que nos ocupa. Si nos fijamos numberOfRowsInSection es el nombre de parámetro externo de section. Esto quiere decir que nos está informando qué sección de nuestra tabla es la que está preguntando en ese momento (cuando se invoque) el número de filas. Si dicho parámetro es 0, vamos a devolver el total de elementos en datos, pero si es 2, devolvemos el total de elementos de datos2.

Las secciones empiezan por 0, así que la primera siempre es la 0, la siguiente la 1, etc. Ahora vemos que los datos en nuestra tabla, cuando se duplican los mismos, solo llegan hasta Fila Tres. Pero seguimos sin devolver los datos que nos interesan, así que ahora vamos a cambiar eso haciendo uso del dato indexPath que tenemos en la función sobrescrita cellForRowAt.

Lo que hemos hecho es simple. Lo primero, hemos quitado la asignación que teníamos de la propiedad text de etiqueta y la hemos cambiado por un switch. Este, en caso que la sección sea la 0 (la primera) recupera el dato del array datos y en caso que sea la sección 1, lo recupera de datos2. El quid de la cuestión aquí está en que hemos usado row en vez de item, con lo que para la sección 0 tendremos elementos que empiezan desde 0, para la 1 que empiezan desde 0, etc. Es la forma de acceder al dato cuando hay más de una sección: por la propiedad row. En caso contrario item nos servirá, como ha pasado en el caso anterior de una sola sección.

Tuneando

Como estamos usando más de una sección, podemos sobrecargar otra función más tableView. En este caso la que usa el parámetro externo titleForHeaderInSection. Su función es devolver una cadena que dará nombre a la sección que creemos.

Los playground permiten interactividad y con ello podemos probar algunas funciones que las tablas realizan, como por ejemplo, reaccionar a cuando hacemos un toque en alguna celda al seleccionarla. Nada más simple que sobrecargar la función correspondiente, en este caso la que usa el parámetro externo didSelectRowAt.

Al igual que al devolver los datos, esta función recoge un valor indexPath en cuyo contenido tenemos la sección y fila donde se ha pulsado, por lo tanto hacemos un switch que nos ayude a discernir cuál de las dos fuentes de datos hemos de usar y luego imprimimos (por darle una función básica).

TableView Terminada

El concepto es lo importante

Los prototipos son una herramienta imprescindible para entender los conceptos detrás de elementos que son complejos de por sí. Por ejemplo, el que acabamos de ver de las vistas de tabla. Entender una tableView desde su concepción misma es esencial para trabajar con ella, y ahora gracias a los playgrounds interactivos podemos ver por nosotros mismo cómo funcionan.

En Apple Coding pensamos que lo importante es entender desde la base. Ese es el secreto. Y esa es la filosofía que aplicamos en toda nuestra formación. Este es solo un pequeño trozo del currículum completo que actualmente impartimos en Apple Coding Academy, más concretamente, en el Curso de Desarrollo de Apps con iOS 10.

Para entender completamente esta lección hemos de tener un nivel medio de Swift 3, donde entendamos de clases, herencia, delegaciones, etc. Si no tienes el suficiente nivel aún o te has perdido, te recomendamos nuestro libro “Aprendiendo Swift 3”.

Si queréis bajar el ejercicio completo en el playground, solo tenéis que acceder a nuestro repositorio en GitHub y descargarlo. Lo hemos comentado para que os sea más fácil de entender. E insistimos, funciona igual en Swift Playgrounds para iPad que en Xcode 8. Probad, experimentad y Good Apple Coding.

Repositorio de lecciones | Enlace en GitHub

Acerca de Julio César Fernández

Analista, consultor y periodista tecnológico, desarrollador, empresario, productor audiovisual, actor de doblaje e ingeniero de vídeo y audio.

Otras recomendaciones

Banner Programación Orientada a Protocolos

Programación Orientada a Protocolos

Revisamos con un caso práctico la programación orientada a protocolos, un nuevo mecanismo de abstracción para Swift que permite una gran versatilidad. Vemos un caso donde creamos una simple estructura orientada a objetos para luego ver cómo resolver los problemas que nos plantea con la nueva orientación a protocolos, constituida de protocolos, structs y extensiones.