iOS desde el boceto con Swift: Explorando el control de la barra de pestañas
() translation by (you can also view the original English article)
En el tutorial anterior, discutimos cómo un controlador de navegación permite al usuario navegar contenido jerárquico o datos complejos administrando una pila de controladores de vista. Los controladores de la barra de pestañas también administran una variedad de controladores de visualización. La diferencia es que los controladores de vista de un controlador de barra de pestañas no necesariamente tienen una relación entre sí. En este tutorial, exploraremos los controladores de barra de pestañas con más detalle al crear una aplicación con pestañas desde cero.
Introducción
UITabBarController
es otra subclase de UIViewController
. Si bien los controladores de navegación administran una pila de controladores de vista relacionados, los controladores de barra de pestañas administran una matriz de controladores de vista que no tienen relación explícita entre sí.
Las aplicaciones de Reloj y Música en iOS son dos ejemplos principales de controladores de barra de pestañas. Al igual que cualquier otra subclase UIViewController
, un controlador de barra de pestañas administra una instancia de UIView
.



La vista de un controlador de barra de pestañas se compone de dos subvistas:
- La barra de pestañas en la parte inferior de la vista
- La vista de uno de los controladores de vista que administra el controlador de la barra de pestañas



Antes de iniciar
Hay algunas advertencias a tener en cuenta cuando se trabaja con controladores de barra de pestañas. Aunque las instancias de UITabBar
solo pueden mostrar cinco pestañas, UITabBarController
puede administrar más controladores de vista. Si un controlador de barra de pestañas administra más de cinco controladores de vista, la última pestaña de la barra de pestañas se titula Más.



Se puede acceder a los controles de vista adicionales a través de esta pestaña e incluso es posible editar la posición de las pestañas en la barra de pestañas.



Aunque los controladores de la barra de pestañas administran una vista, no se supone que tu aplicación interactúe directamente con la vista del controlador de la barra de pestañas. Se requiere que el controlador de barra de pestañas sea el controlador de vista raíz de la ventana de la aplicación. En otras palabras, la vista raíz de la ventana de la aplicación siempre es la vista del controlador de la barra de pestañas. Un controlador de barra de pestañas nunca se debe instalar como un elemento secundario de otro controlador de vista. Esta es una de las diferencias clave con los controladores de navegación.
Biblioteca con pestañas
En este artículo, revisamos la aplicación de la Biblioteca que construimos en el artículo anterior. Hacerlo nos permitirá reutilizar varias clases y acelerar el proceso. Además, te mostrará que los controladores de navegación y los controles de barra de pestañas son bastante diferentes y que se usan en diferentes situaciones y casos de uso.
Debido a que la aplicación que construimos en esta lección se basa en la clase UITabBarController
, le dará un aspecto muy específico a la aplicación, lo que permite poca flexibilidad en términos de interfaz de usuario y experiencia. Los controladores de la barra de pestañas son increíblemente útiles, pero debes aceptar que ponen límites a la interfaz de usuario de la aplicación hasta cierto punto.
Abre Xcode, crea un nuevo proyecto (Archivo > Nuevo > Proyecto...) y selecciona la plantilla Aplicación de vista única.



Nombra el proyecto como Biblioteca de pestañas, asigna un nombre e identificador de organización, establece Idioma a Swift y configura Dispositivos en iPhone. Dile a Xcode dónde quieres guardar el proyecto y haz clic en Crear.



Aunque Xcode incluye una plantilla de aplicación con pestañas, prefiero comenzar con una plantilla de aplicación básica para que comprendas cómo encajan las distintas piezas del rompecabezas. Notarás que los controladores de la barra de pestañas no son tan complicados.
Iniciando
Cuando finaliza la aplicación de la Biblioteca de pestañas, el controlador de la barra de pestañas de la aplicación administrará seis controladores de vista. En lugar de crear cada clase de controlador de vista desde cero, vamos a hacer un poco de trampa reutilizando las clases de controlador de vista que creamos en el artículo anterior. Además, crearemos varias instancias de la misma clase de controlador de vista para ahorrarnos algo de tiempo. El objetivo de este artículo no es crear un grupo de clases de controlador de vista. En este punto, debes estar bastante familiarizado con cómo funciona eso.
Descarga el código fuente del artículo anterior y abre el proyecto Xcode que se incluye en los archivos fuente en una nueva ventana del Finder. Busca las clases AuthorsViewController
, BooksViewController
y BookCoverViewController
y arrástralas a tu nuevo proyecto. Asegúrate de copiar los archivos al nuevo proyecto marcando la casilla Copiar elementos si es necesario y no olvides agregar los archivos al destino de la Biblioteca con pestañas.



Además de estas tres clases, también necesitamos copiar la carpeta de recursos, que contiene Books.plist y los archivos de imagen, a nuestro nuevo proyecto. Arrastra la carpeta llamada Resources a nuestro proyecto y usa la misma configuración que usamos para copiar los archivos de clase. Ahora estamos listos para crear una instancia del controlador de barra de pestañas de la aplicación y poblarlo con su primer controlador de vista.
Agregar un controlador de barra de pestañas
Si abres Main.storyboard, notarás que el storyboard contiene una instancia de la clase ViewController
. Selecciona el controlador de vista y presiona eliminar o retroceder. Abre la Biblioteca de objetos a la derecha y arrastra un controlador de la barra de pestañas al espacio de trabajo.
Xcode agrega automáticamente dos controladores hijos de vista al controlador de la barra de pestañas. Como quiero mostrarte cómo agregar manualmente los controladores hijos de vista a un controlador de barra de pestañas, vamos a eliminar los que Xcode ha creado para nosotros. Selecciona los controladores de vista secundarios y presiona eliminar o retroceder para eliminarlos.
Selecciona el controlador de la barra de pestañas, abre el Inspector de atributos y marca la casilla de verificación Controlador de vista inicial. Si no configuramos el controlador de barra de pestañas como el controlador de vista inicial, la aplicación se bloqueará al iniciarse.



Si ejecutas la aplicación en el simulador, deberías ver una barra de pestañas en la parte inferior y un fondo negro. Esto puede parecer poco importante, pero muestra cómo funciona el controlador de la barra de pestañas. El controlador de la barra de pestañas administra una matriz de controladores de vista, similar a la forma en que un controlador de navegación administra una pila de controladores de visualización.
Necesitamos agregar algunos controladores de vista al storyboard y agregarlos a la propiedad viewControllers
del controlador de la barra de pestañas. Veamos cómo funciona esto.
Agregar controladores de vista
Arrastra una instancia de UITableViewController
desde la Biblioteca de objetos al espacio de trabajo y configura su clase en AuthorsViewController
en el Inspector de identidad. Selecciona la vista de tabla del controlador de vista y establece Prototipo de celdas en 0 en el Inspector de atributos.



Para agregar el controlador de vista de autores a la matriz de controles de vista del controlador de la barra de pestañas, arrastra desde el controlador de la barra de pestañas hasta el controlador de vista de autores, manteniendo presionada la tecla Control. Selecciona Relación de transición > Ver controladores en el menú que aparece.






Un controlador de barra de pestañas con una sola pestaña no es tan útil, así que agreguemos otro controlador de vista a la mezcla. Arrastra otra instancia de UITableViewController
desde la Biblioteca de objetos, establece su clase en BooksViewController
y establece Prototipos de celdas en 0. Crea la transición de relaciones como lo hicimos para el controlador de vista de autores.



Agrega una instancia de UIViewController
al espacio de trabajo y establece su clase en BookCoverViewController
en el Inspector de identidad. Agrega una instancia de UIImageView
al controlador de vista, conéctala con la salida bookCoverView
del controlador de vista y aplica las restricciones de diseño necesarias. Crea la transición de la relación con el controlador de la barra de pestañas como lo hicimos para los controladores de vista de tabla.
Un guión gráfico o storyboard a veces puede llegar a ser un poco desordenado. Si tienes problemas para crear divisiones entre los controladores de vista, debes saber que también puedes crear conexiones, como por ejemplo, en el navegador de la izquierda. Esto es a menudo mucho más fácil y menos torpe.



Compila y ejecuta la aplicación. En este punto, la barra de pestañas contiene tres pestañas. Al tocar la segunda o tercera pestaña se bloquea la aplicación. ¿Porqué pasa esto? Es hora de algunas depuraciones.
Corrección de errores
Reparar el Authors View Controller
Cuando tocas el nombre de un autor en el controlador de vista de autores, la aplicación falla. Lo primero que debes hacer cuando te enfrentas a un bloqueo es inspeccionar la consola de Xcode. Esto es lo que me dice.
1 |
Tabbed Library[1141:54864] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<Tabbed_Library.AuthorsViewController: 0x7fde3943d050>) has no segue with identifier 'BooksViewController'' |
El mensaje que Xcode muestra en la consola no es difícil de descifrar. Le decimos al controlador de vista que realice una transición con el identificador BooksViewController
, pero ese cambio no existe. El resultado es un bloqueo.
Este error es fácil de corregir al incrustar el controlador de vista de autores en un controlador de navegación, crear una transición a la instancia de BooksViewController
y asignarle un nombre al separador BooksViewController. Te dejo eso como una tarea ya que ya lo hicimos en el artículo anterior. Los archivos fuente de este tutorial contienen la solución.
El problema más importante en este ejemplo es entender que los tres controladores de vista que están vinculados al controlador de la barra de pestañas no se comunican entre sí. La idea detrás de un controlador de barra de pestañas y un controlador de navegación es muy diferente. Un controlador de navegación mantiene una matriz de controladores de vista, la pila de navegación. Los controladores de vista en la pila de navegación tienen una relación implícita entre sí en el sentido de que son parte de la misma pila de navegación.
Un controlador de barra de pestañas también administra una matriz de controladores de vista, pero los controladores de vista no se conocen entre sí. No se pueden comunicar entre ellos a través del controlador de la barra de pestañas. En el artículo anterior, pasamos datos del controlador de vista de autores al controlador de vista de libros cuando el usuario tocaba un autor. Este patrón no es aplicable en un controlador de barra de pestañas. Por supuesto, podríamos implementar una solución para mostrar al usuario el controlador de visualización de libros cuando se toca un autor en el controlador de vista de autores, pero es importante que comprendas que ese no es el objetivo de un controlador de barra de pestañas.
Las aplicaciones de Reloj y Música en iOS son buenos ejemplos de cómo debe usarse un controlador de barra de pestañas. Los controladores de vista que maneja el controlador de barra de pestañas en la aplicación de Música no tienen relación entre sí, aparte del hecho de que muestran canciones.
Antes de continuar, asegúrate de comprender los conceptos de un controlador de navegación y un controlador de barra de pestañas. Son importantes más adelante en esta serie.
Reparar el Books View Controller
Cuando tocas la segunda pestaña del controlador de la barra de pestañas, la aplicación también falla. Esto es lo que veo en la consola de Xcode.
1 |
fatal error: unexpectedly found nil while unwrapping an Optional value
|
Esto no me sorprende ya que no establecemos la propiedad author
del controlador de vista de libros. Actualicemos el controlador de vista de libros para que muestre todos los libros en Books.plist.
Necesitamos hacer tres cambios. Comencemos con el cambio más fácil primero. Abre BooksViewController.swift y cambia el tipo de propiedad author
por [String: AnyObject]?
. La propiedad author ahora es algo opcional en lugar de una opción forzada no envuelta.
1 |
var author: [String: AnyObject]? |
Para el segundo cambio, necesitamos actualizar la implementación de viewDidLoad()
. Mostramos el nombre del autor si el author
no es igual a null
. Si el autor es null
, establecemos el título para "Books"
.
1 |
override func viewDidLoad() { |
2 |
super.viewDidLoad() |
3 |
|
4 |
if let author = author, let name = author["Author"] as? String { |
5 |
title = name |
6 |
} else { |
7 |
title = "Books" |
8 |
}
|
9 |
|
10 |
tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier) |
11 |
}
|
El último cambio es un poco más complicado. Convertimos la propiedad books
computados en una propiedad almacenada perezosa. Una propiedad almacenada perezosa se inicializa la primera vez que se accede a ella. En otras palabras, la propiedad no se inicializa cuando el controlador de vista se inicializa. Así es como se ve la implementación.
1 |
lazy var books: [AnyObject] = { |
2 |
// Initialize Books
|
3 |
var buffer = [AnyObject]() |
4 |
|
5 |
if let author = self.author, let books = author["Books"] as? [AnyObject] { |
6 |
buffer += books |
7 |
|
8 |
} else { |
9 |
let filePath = NSBundle.mainBundle().pathForResource("Books", ofType: "plist") |
10 |
|
11 |
if let path = filePath { |
12 |
let authors = NSArray(contentsOfFile: path) as! [AnyObject] |
13 |
|
14 |
for author in authors { |
15 |
if let books = author["Books"] as? [AnyObject] { |
16 |
buffer += books |
17 |
}
|
18 |
}
|
19 |
}
|
20 |
}
|
21 |
|
22 |
return buffer |
23 |
}()
|
Las propiedades almacenadas diferidas están marcadas por la palabra clave lazy
. En lugar de asignar un valor ordinario a la propiedad, asignamos un cierre. Pero ten en cuenta que el cierre termina con dos paréntesis, ()
. Esto indica que el cierre se ejecuta cuando se accede a books
por primera vez. Esto es muy similar a las funciones anónimas en otros idiomas, como JavaScript.
En el cierre, creamos una matriz vacía de tipo [AnyObject]
. Si el author
no es nil
o nulo, cargamos los libros de la propiedad author
. Si el autor no tiene un valor, cargamos Books.plist y agregamos todos los libros de cada autor al buffer
. Ten en cuenta que devolvemos la matriz de libros al final del cierre. Ejecuta la aplicación para ver si esto soluciona el bloqueo causado por el controlador de vista de libros.
Si abres la segunda pestaña de la aplicación y tocas un libro de la lista, la aplicación se bloquea por el mismo motivo por el que el controlador de vista de los autores provocó un bloqueo anterior. Puedes remediar esto insertando el controlador de vista de libros en un controlador de navegación y creando una transición o un segue con el identificador BookCoverViewController en la instancia BookCoverViewController
.
Reparando el Book Cover View Controller
Cuando tocas la tercera pestaña, la aplicación también falla. La solución es similar a la que aplicamos a la clase BooksViewController
. Comienza por hacer que la propiedad del libro BookCoverViewController
sea opcional.
1 |
var book: [String: String]? |
Declaramos otra propiedad almacenada diferida, bookCoverImage
de tipo UIImage?
. En bookCoverImage
seleccionamos un libro aleatorio de los libros almacenados en Books.plist si la propiedad del libro no tiene ningún valor. Si el libro tiene un valor, mostramos la portada del libro.
1 |
lazy var bookCoverImage: UIImage? = { |
2 |
var image: UIImage? |
3 |
|
4 |
if self.book == nil { |
5 |
// Initialize Buffer
|
6 |
var buffer = [AnyObject]() |
7 |
|
8 |
let filePath = NSBundle.mainBundle().pathForResource("Books", ofType: "plist") |
9 |
|
10 |
if let path = filePath { |
11 |
let authors = NSArray(contentsOfFile: path) as! [AnyObject] |
12 |
|
13 |
for author in authors { |
14 |
if let books = author["Books"] as? [AnyObject] { |
15 |
buffer += books |
16 |
}
|
17 |
}
|
18 |
}
|
19 |
|
20 |
if buffer.count > 0 { |
21 |
let random = Int(arc4random()) % buffer.count |
22 |
|
23 |
if let book = buffer[random] as? [String: String] { |
24 |
self.book = book |
25 |
}
|
26 |
}
|
27 |
}
|
28 |
|
29 |
if let book = self.book, let fileName = book["Cover"] { |
30 |
image = UIImage(named: fileName) |
31 |
}
|
32 |
|
33 |
return image |
34 |
}()
|
En viewDidLoad()
, utilizamos la propiedad bookCoverImage
para actualizar la vista de la imagen del controlador de vista de portada del libro.
1 |
override func viewDidLoad() { |
2 |
super.viewDidLoad() |
3 |
|
4 |
if let bookCoverImage = bookCoverImage { |
5 |
bookCoverView.image = bookCoverImage |
6 |
bookCoverView.contentMode = .ScaleAspectFit |
7 |
}
|
8 |
}
|
Si has implementado los cambios anteriores, ahora deberías poder ejecutar la aplicación sin ver ningún bloqueo. Si tienes buen ojo para los detalles, puedes haber notado que el título de la segunda y tercera pestaña solo aparecen cuando se selecciona la pestaña. ¿Puedes adivinar por qué pasa eso?
Artículos de barra de tabulación
La causa de esta extraña peculiaridad es bastante simple. En general, una vista no se carga en la memoria hasta que sea absolutamente necesario. Esto generalmente significa que una vista se carga en la memoria cuando está a punto de mostrarse al usuario.
Cuando se inicia la aplicación Biblioteca de pestañas, la primera pestaña se selecciona de manera predeterminada. Siempre que la segunda o tercera pestaña no sea seleccionada por el usuario o por programación, no hay necesidad de cargar la vista de ese controlador de vista. Como resultado, viewDidLoad()
no se llama hasta que se selecciona la pestaña. Esto significa que el título no se establece hasta que se seleccione la pestaña.
La solución es simple. Cada instancia de UIViewController
(o una subclase de la misma) tiene una propiedad tabBarItem
del tipo UITabBarItem!
. Esta propiedad es utilizada por el controlador de la barra de pestañas para configurar las pestañas en su barra de pestañas. El título de la pestaña del controlador de vista se obtiene de la propiedad tabBarItem
del controlador de vista.
Para solucionar el problema anterior, necesitamos establecer la propiedad de título de la propiedad tabBarItem
cuando los controladores de vista del controlador de la barra de pestañas se inicializan. Veamos cómo funciona esto para la clase BooksViewController
.
Abre BooksViewController.swift y agrega el siguiente inicializador. Antes de echar un vistazo a la implementación, me gustaría señalar tres detalles interesantes sobre el inicializador. En Swift, los inicializadores son un tipo especial de función.
1 |
required init?(coder aDecoder: NSCoder) { |
2 |
super.init(coder: aDecoder) |
3 |
|
4 |
// Initialize Tab Bar Item
|
5 |
tabBarItem = UITabBarItem(title: "Books", image: UIImage(named: "icon-books"), tag: 1) |
6 |
}
|
- La palabra clave
required
indica que se requiere que este iniciador sea implementado por cada subclase. Sin embargo, no es necesario implementar un inicializador requerido si la subclase satisface los requisitos con un inicializador heredado por su clase principal. La inicialización es un tema bastante complejo en Swift que causa un poco de confusión entre los desarrolladores. Debido a su complejidad, no lo cubriré con mucho detalle en esta serie. - El signo de interrogación después de la palabra clave
init
indica que la inicialización puede fallar. Esto es mejor conocido como un inicializador failable. Si el controlador de vista no puede ser instanciado por una razón u otra, el inicializador devuelvenil
en lugar de una instancia de la clase. - También puedes haber notado que el inicializador no usa la palabra clave
func
. La palabra clavefunc
no se usa para inicializadores.
En el inicializador, invocamos el inicializador de la clase padre. La palabra clave super
se refiere a la superclase. Luego creamos un elemento de barra de pestañas y lo asignamos a la propiedad tabBarItem
. Para crear el elemento de la barra de pestañas, invocamos init(title:image:tag:)
, pasando el título del elemento de la barra de pestañas, una instancia de UIImage
y una etiqueta. La etiqueta se usa para identificar el elemento de la barra de pestañas.
Ten en cuenta que puedes omitir la extensión del nombre del archivo. Aunque utilizamos una imagen con el nombre icon-authors.png, el valor que pasamos al inicializador de UIImage
es "icon-authors". Las imágenes que he utilizado están incluidas en los archivos fuente en GitHub y también puedes encontrarlas en GraphicRiver si deseas usarlas en tus propios proyectos.
Para cada imagen, hay dos archivos, icon-authors.png e icon-authors@2x.png. El sistema operativo selecciona el primer archivo para los dispositivos que no tienen una pantalla Retina. El segundo archivo se usa para dispositivos con una pantalla Retina. Ten en cuenta que no necesitas incluir el especificador @2x en la cadena que pasas al inicializador de UIImage
. El sistema operativo es lo suficientemente inteligente como para resolver esto.
Puedes incluir un tercer archivo con un especificador @3x para iPhone 6/6S Plus. La densidad de píxeles de este archivo debe ser tres veces mayor que la del primero. Echa un vistazo al siguiente ejemplo para comprender mejor cómo funciona esto en la práctica.
- icon-authors.png: 30px x 30px
- icon-authors@2x.png: 60px x 60px
- icon-authors@3x.png: 90px x 90px
Vamos a crear también un elemento de barra de pestañas para las clases AuthorsViewController
y BookCoverViewController
.
1 |
// Authors View Controller
|
2 |
required init?(coder aDecoder: NSCoder) { |
3 |
super.init(coder: aDecoder) |
4 |
|
5 |
// Initialize Tab Bar Item
|
6 |
tabBarItem = UITabBarItem(title: "Authors", image: UIImage(named: "icon-authors"), tag: 0) |
7 |
}
|
1 |
// Book Cover View Controller
|
2 |
required init?(coder aDecoder: NSCoder) { |
3 |
super.init(coder: aDecoder) |
4 |
|
5 |
// Initialize Tab Bar Item
|
6 |
tabBarItem = UITabBarItem(title: "Cover", image: UIImage(named: "icon-cover"), tag: 2) |
7 |
}
|
Los elementos de la barra de pestañas también pueden tener una insignia. Si estás creando un cliente de correo electrónico, por ejemplo, puedes identificar una pestaña con la cantidad de correos electrónicos no leídos. Puedes agregar una insignia a un elemento de la barra de pestañas configurando su propiedad badgeValue
. Actualiza el inicializador del controlador de vista de libros como se muestra a continuación.
1 |
required init?(coder aDecoder: NSCoder) { |
2 |
super.init(coder: aDecoder) |
3 |
|
4 |
// Initialize Tab Bar Item
|
5 |
tabBarItem = UITabBarItem(title: "Books", image: UIImage(named: "icon-books"), tag: 1) |
6 |
|
7 |
// Configure Tab Bar Item
|
8 |
tabBarItem.badgeValue = "8" |
9 |
}
|
Este es el aspecto que debería tener el resultado cuando ejecutas la aplicación en el simulador.



En la lección anterior, escribí que cada controlador de vista en una pila de navegación mantiene una referencia al controlador de navegación que administra la pila. Lo mismo es cierto para los controladores de vista que son administrados por un controlador de barra de pestañas. Un controlador de vista administrado por un controlador de barra de pestañas mantiene una referencia al controlador de barra de pestañas en su propiedad tabBarController
, que es de tipo UITabBarController?
.
Al trabajar con controladores de barra de pestañas, ten en cuenta que es el controlador de vista raíz de cada pestaña lo que determina cómo se ve el elemento de la barra de pestañas de la pestaña correspondiente. Por ejemplo, si un controlador de barra de pestañas administra un controlador de navegación con una cantidad de controladores de vista, es el elemento de la barra de pestañas del controlador de vista raíz del controlador de navegación que utiliza la barra de pestañas del controlador de la barra de pestañas. La clase UITabBarItem
tiene algunos otros métodos para personalizar aún más la apariencia de un elemento de la barra de pestañas.
Más controladores de vista
Antes de terminar este artículo, me gustaría mostrarte cómo se ve la barra de pestañas cuando el controlador de la barra de pestañas administra más de cinco controladores de vista. Como mencioné anteriormente, solo se muestran cinco pestañas al mismo tiempo, pero el controlador de la barra de pestañas brinda soporte para administrar más de cinco controladores hijos de vista.
Abre el storyboard principal y agrega dos instancias UITableViewController
y una instancia de UIViewController
. En Inspector de identidad, configura la clase de estos controladores de vista en AuthorsViewController
, BooksViewController
y BookCoverViewController
, respectivamente. Ejecuta la aplicación para ver el resultado. Si tabulas la primera pestaña de la derecha, deberías ver los dos controladores de vista que no caben en la barra de pestañas.



Incluso puedes modificar la posición de los elementos de la barra de pestañas tocando el botón Editar en la esquina superior derecha.



Los controladores de vista adicionales que agregamos no son muy útiles, pero muestran cómo un controlador de barra de pestañas administra más de cinco controladores hijos de vista.
Conclusión
Es importante entender que UITabBarController
y UINavigationController
representan cada uno un paradigma de interfaz de usuario único. En este artículo, aprendiste que los controladores de barra de pestañas no son difíciles de dominar una vez que comprendes los componentes involucrados. En el próximo artículo, veremos la persistencia de los datos en iOS y las opciones que tienes como desarrollador.
Si tienes preguntas o comentarios, puedes dejarlos en los comentarios a continuación o comunicarte conmigo en Twitter.