iOS desde cero con Swift: controladores de navegación y jerarquías de controladores de vista
() translation by (you can also view the original English article)
En
iOS, los controladores de navegación son una de las principales
herramientas para presentar múltiples pantallas de contenido. Este
artículo le enseña a usar los controladores de navegación mediante la
creación de una aplicación para explorar los libros de una
biblioteca.
Introduccion
En
el tutorial anterior, aprendió que la clase de vista de tabla de UIKit
es excelente para presentar datos tabulares o columnares. Sin
embargo, cuando el contenido debe distribuirse en varias pantallas, un
controlador de navegación suele ser la herramienta de elección. La clase
UINavigationController
implementa este tipo de funcionalidad.
Al
igual que cualquier otra subclase UIViewController
, un controlador de
navegación administra una vista, una instancia de la clase UIView
. La
vista del controlador de navegación administra varias subvistas,
incluida una barra de navegación en la parte superior, una vista que
contiene contenido personalizado y una barra de herramientas opcional en
la parte inferior. Lo
que hace que un controlador de navegación sea especial es que crea y
administra una jerarquía de controladores de vista, a menudo denominada
navigation stack.
En este artículo, creamos una nueva aplicación iOS
para familiarizarnos con la clase UINavigationController
. Aprenderá
que la combinación de un controlador de navegación y una pila de
controladores de vista (tabla) es una solución elegante y poderosa para
presentar conjuntos de datos anidados.
Además
de UINavigationController
, también encontrará UITableViewController
en
esta guía de aprendizaje, otra subclase de UIViewController
. UITableViewController
administra una instancia de UITableView
en lugar
de una instancia de UIView
. Los
controladores de vista de tabla adoptan automáticamente los protocolos
UITableViewDataSource
y UITableViewDelegate
y eso nos va a ahorrar algo
de tiempo.
Otro proyecto
La aplicación que estamos por crear se llama Library. Con esta aplicación, los usuarios pueden navegar por una lista de autores y ver los libros que han escrito. La lista de autores se presenta en una vista de tabla.
Si el usuario toca el nombre de un autor, una lista de libros escritos por ese autor se anima a la vista. De forma similar, cuando el usuario selecciona un título de la lista de libros, otra vista se anima a la vista, mostrando una imagen de la portada del libro. Vamos a crear un nuevo proyecto de Xcode para que podamos comenzar.
Creando el Proyecto
Abra Xcode, cree un nuevo proyecto seleccionando New > Project... en el menú File y seleccione la plantilla Single View Application en la lista de Plantillas iOS > Application .



Nombre el proyecto Library y asigne un nombre e identificador de la organización. Establezca Language a Swift y Devices a iPhone. Dile a Xcode dónde quieres guardar el proyecto y haz clic en Create.



La
plantilla Single View Application contiene una clase de delegado
de aplicaciones, AppDelegate
, un guión gráfico, Main.storyboard y una
subclase UIViewController
, ViewController
. Abra AppDelegate.swift y eche
un vistazo a la implementación de la application(_:didFinishLaunchingWithOptions:)
. Su implementación es corta y debería
ser familiar por ahora.
1 |
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { |
2 |
return true |
3 |
}
|
Agregar recursos
Los archivos fuente de este tutorial incluyen los datos que usaremos. Puede encontrarlos en la carpeta llamada Resources. Incluye una lista de propiedades, Books.plist, que contiene información sobre los autores, los libros que han escrito, cierta información sobre cada libro y una imagen para cada libro incluido en la lista de propiedades.
Arrastre la carpeta Resources a su proyecto para agregarlos al proyecto. Xcode le mostrará algunas opciones cuando agrega la carpeta al proyecto. Asegúrese de marcar la casilla Copy items if needed y no olvide agregar los archivos al target de Library.






Listas de propiedades
Antes de continuar, quiero tomarme un momento para hablar sobre las listas de propiedades y lo que son. Una lista de propiedades no es más que una representación de un gráfico de objetos. Como vimos anteriormente en esta serie, un gráfico de objetos es un grupo de objetos que forma una red a través de las conexiones o referencias que comparten entre sí.
Es fácil leer y escribir listas de propiedades desde y hacia el disco, lo que las hace ideales para almacenar pequeñas cantidades de datos. Al trabajar con listas de propiedades, también es importante recordar que solo ciertos tipos de datos se pueden almacenar en listas de propiedades, como cadenas, números, fechas, matrices, diccionarios y datos binarios.
Xcode hace que buscar listas de propiedades sea muy fácil. Seleccione Books.plist de la carpeta Resources que agregó al proyecto y explore sus contenidos usando el editor de listas de propiedades incorporado de Xcode. Esta será una herramienta útil más adelante en este artículo cuando comencemos a trabajar con los contenidos de Books.plist.



Subclases de UITableViewController
Antes de que podamos comenzar a utilizar los datos almacenados en Books.plist, primero debemos establecer algunas bases. Esto incluye la creación de un controlador de vista que administre una vista de tabla y que muestre los autores enumerados en la lista de propiedades.
En
el artículo anterior, creamos una subclase UIViewController
y agregamos
una vista de tabla a la vista del controlador de vista para presentar
datos al usuario. En este tutorial, tomamos un atajo subclasificando
UITableViewController
.
Comience eliminando ViewController.swift de su proyecto. Cree una nueva clase seleccionando New > File... en el menú File. Seleccione la plantilla Cocoa Touch Class en la lista de Plantillas iOS > Source.



Denomine
la nueva clase AuthorsViewController
y conviértala en una subclase de
UITableViewController
. No
es necesario marcar la casilla de verificación Also create XIB file for user interface, porque usaremos el guión gráfico para
crear la interfaz de usuario de la aplicación.



Abra Main.storyboard para reemplazar el controlador de vista en el storyboard con un controlador de vista de tabla. Seleccione
el controlador de vista en el storyboard, presione la tecla Eliminar
y arrastre una instancia de UITableViewController
desde la Object Library a la derecha. Seleccione
el nuevo controlador de vista, abra el Identity Inspector a la
derecha y establezca su clase en AuthorsViewController
.



En el artículo anterior, hicimos uso de células prototipo para poblar la vista de tabla. En este tutorial, te mostraré un enfoque alternativo. Seleccione el objeto de vista de tabla en el área de trabajo o desde la lista de objetos a la izquierda, abra el Attributes Inspector a la derecha y configure las Prototype Cells en 0.



Cada storyboard necesita un controlador de vista inicial. Este es el controlador de vista que se crea una instancia cuando se carga el storyboard. Podemos marcar el controlador de visualización de autores como el controlador de vista inicial seleccionando el objeto Authors View Controller a la izquierda, abriendo el Attributes Inspector a la derecha y marcando la casilla de verificación Is Initial View Controller.
Poblar la vista de tabla
Abre AuthorsViewController.swift e
inspecciona el contenido del archivo. Debido
a que AuthorsViewController
es una subclase de UITableViewController
,
AuthorsViewController
ya se ajusta a los protocolos
UITableViewDataSource
y UITableViewDelegate
.
Antes de que podamos mostrar datos en la vista de tabla, necesitamos datos para mostrar. Como mencioné anteriormente, los datos contenidos en Books.plist sirven como fuente de datos de la vista de tabla. Para usar esta información, primero debemos cargarla en un objeto, una matriz para ser precisos.
Declaramos una propiedad variable authors
y establecemos su
valor inicial en una matriz vacía de tipo [AnyObject]
. Recuerde que el
tipo AnyObject
puede representar cualquier clase o estructura.
El
método viewDidLoad()
del controlador de vista es un buen lugar para
cargar los datos de Books.plist en la propiedad authors
del
controlador de vista. Hacemos esto invocando el inicializador init(contentsOfFile:)
de la clase NSArray
. Echamos el objeto resultante a
una instancia de tipo [AnyObject]
.
1 |
authors = NSArray(contentsOfFile: path) as! [AnyObject] |
El método acepta una ruta de archivo, lo que significa que tenemos que averiguar cuál es la ruta del archivo de Books.plist. El archivo, Books.plist, se encuentra en el paquete de la aplicación, que es una palabra elegante para el directorio que contiene el ejecutable de la aplicación y los recursos de la aplicación, como imágenes y sonidos.
Para
obtener la ruta del archivo de Books.plist, primero necesitamos una
referencia al paquete principal de la aplicación llamando a mainBundle()
en la clase NSBundle
. El siguiente paso es solicitar el paquete de la
aplicación para la ruta de uno de sus recursos, Books.plist. Invocamos
pathForResource(_:ofType:)
en el paquete principal de la aplicación,
pasando el nombre y el tipo (extensión) del archivo que nos interesa. Almacenamos la ruta del archivo en una constante, filePath
.
1 |
let filePath = NSBundle.mainBundle().pathForResource("Books", ofType: "plist") |
Como
es posible que solicitemos un recurso que no está presente en el
paquete de la aplicación, pathForResource(_:ofType:)
devuelve un
recurso opcional. Como regla general, si un método puede devolver nil
, se debe usar un opcional. La constante filePath
es de tipo String?
. Para
desenvolver de forma segura el elemento opcional, utilizamos el enlace
opcional, que hemos discutido anteriormente en esta serie.
Si unimos las
dos piezas, terminamos con la siguiente implementación de viewDidLoad()
. También agregué una declaración de impresión para imprimir los
contenidos de la propiedad authors
a la consola. Esto nos permite
echar un vistazo a sus contenidos.
1 |
override func viewDidLoad() { |
2 |
super.viewDidLoad() |
3 |
|
4 |
let filePath = NSBundle.mainBundle().pathForResource("Books", ofType: "plist") |
5 |
|
6 |
if let path = filePath { |
7 |
authors = NSArray(contentsOfFile: path) as! [AnyObject] |
8 |
print(authors) |
9 |
}
|
10 |
}
|
Si ha leído el artículo anterior de esta serie, llenar la vista de tabla debería ser sencillo. Como la vista de tabla contiene solo una sección, la implementación de
numberOfSectionsInTableView(_:)
es sencilla. AuthorsViewController
hereda de UITableViewController
, que ya se ajusta e implementa el
protocolo UITableViewDataSource
. Es por eso que debemos usar la palabra
clave override
. Estamos anulando un método implementado por la clase
principal.
1 |
override func numberOfSectionsInTableView(tableView: UITableView) -> Int { |
2 |
return 1 |
3 |
}
|
El
número de filas en la única sección de la vista de tabla es igual al
número de autores en la matriz authors
, de modo que todo lo que
tenemos que hacer es contar los elementos de la matriz.
1 |
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { |
2 |
return authors.count |
3 |
}
|
La implementación de tableView(_:cellForRowAtIndexPath:)
es similar a la que vimos en el artículo
anterior. La principal diferencia es cómo buscamos los datos que
mostramos en la celda de la vista de tabla.
La matriz de autores contiene
una lista ordenada de diccionarios, con cada diccionario que contiene
dos pares clave-valor. El
objeto para la clave llamada Author es del tipo String
,mientras que el
objeto para la clave Books es una matriz de diccionarios con cada
diccionario que representa un libro escrito por el autor. Abra
Books.plist en Xcode para inspeccionar la estructura de la fuente de
datos si esto no está del todo claro.
Antes de implementar tableView(_:cellForRowAtIndexPath:)
, tenemos que encargarnos de dos cosas. Primero,
declaramos una constante para el identificador de reutilización de
celda que vamos a usar.
1 |
import UIKit |
2 |
|
3 |
class AuthorsViewController: UITableViewController { |
4 |
|
5 |
let CellIdentifier = "Cell Identifier" |
6 |
|
7 |
var authors = [AnyObject]() |
8 |
|
9 |
...
|
10 |
|
11 |
}
|
En
segundo lugar, llamamos registerClass(_:forCellReuseIdentifier:)
en
la vista de tabla, pasando en UITableViewCell.classForCoder()
y el
identificador de reutilización de celda. Invocamos este método en
viewDidLoad()
para asegurarnos de que se llame solo una
vez.
1 |
override func viewDidLoad() { |
2 |
super.viewDidLoad() |
3 |
|
4 |
let filePath = NSBundle.mainBundle().pathForResource("Books", ofType: "plist") |
5 |
|
6 |
if let path = filePath { |
7 |
authors = NSArray(contentsOfFile: path) as! [AnyObject] |
8 |
print(authors) |
9 |
}
|
10 |
|
11 |
tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier) |
12 |
}
|
Con lo anterior en su lugar, la implementación de
tableView(_:cellForRowAtIndexPath:)
se vuelve bastante corta.
1 |
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { |
2 |
// Dequeue Resuable Cell
|
3 |
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) |
4 |
|
5 |
if let author = authors[indexPath.row] as? [String: AnyObject], let name = author["Author"] as? String { |
6 |
// Configure Cell
|
7 |
cell.textLabel?.text = name |
8 |
}
|
9 |
|
10 |
return cell; |
11 |
}
|
Tenga en cuenta que encadenamos enlaces opcionales con una coma. Este es un enfoque conveniente para evitar declaraciones if
anidadas. Pedimos a authors
el elemento en indexPath.row
y lo bajamos como
[String: AnyObject]
. Debido a que necesitamos el nombre del autor,
pedimos al author
el valor de la clave "Author"
, bajando el resultado a
String
.
Agregar un controlador de navegación
Agregar un controlador de navegación es fácil usando un storyboard. Sin embargo, antes de agregar un controlador de navegación, es importante entender cómo funcionan los controladores de navegación en iOS.
Al igual
que cualquier otra subclase UIViewController
, un controlador de
navegación administra una instancia de UIView
. La
vista del controlador de navegación administra varias subvistas,
incluida una barra de navegación en la parte superior, una vista que
contiene contenido personalizado y una barra de herramientas opcional en
la parte inferior. Lo que hace que un controlador de navegación sea
único es que administra una pila de controladores de visualización.



El término stack casi se puede tomar literalmente. Cuando se inicializa un controlador de navegación, el controlador de navegación recibe un controlador de vista raíz. El controlador de vista raíz es el controlador de vista en la parte inferior de navigation stack.
Al presionar otro controlador de vista en navigation stack, la vista del controlador de vista raíz se reemplaza con la vista del nuevo controlador de vista. Al trabajar con controladores de navegación, la vista visible es siempre la vista del controlador de vista superior de navigation stack.
Cuando un controlador de vista se elimina o extrae de la navigation stack, la vista del controlador de vista debajo de esta se vuelve a ver. Al presionar y abrir los controladores de vista hacia y desde la pila de navegación del controlador de navegación, se crea una jerarquía de vistas y, como resultado, se puede presentar un conjunto de datos anidados al usuario. Veamos cómo funciona todo esto de empujar y hacer estallar en la práctica.
Revise el storyboard del proyecto (Main.storyboard) y seleccione el controlador de vista. Para agregar un controlador de navegación a la mezcla, seleccione Embed In > Navigation Controller en el menú Editor. Algunas cosas cambian:
- el controlador de navegación se convierte en el controlador de vista inicial del storyboard
- se agrega una nueva escena llamada Navigation Controller Scene
- se agrega una barra de navegación al controlador de vista de navegación y autores
- el controlador de navegación y el controlador de vista de los autores están conectados por un segue



Los Segues son comunes en storyboard y aprenderemos más sobre ellos más adelante en esta serie. Hay varios tipos de segues y el segue que conecta el controlador de navegación y el controlador de vista de authors es una relationship segue.
Cada controlador de navegación tiene un controlador de vista raíz, el controlador de vista en la parte inferior de navigation stack. No se puede extraer de la pila de navegación, porque un controlador de navegación siempre necesita un controlador de vista para mostrar al usuario. La transición de la relación entre el controlador de navegación y el controlador de vista del autor simboliza que este último es el controlador de vista raíz del controlador de navegación.
La
barra de navegación en la parte superior del controlador de navegación y
el controlador de vista de autores es algo que obtienes de forma
gratuita cuando trabajas con controladores de navegación. Es una
instancia de UINavigationBar
y ayuda a navegar en la navigation stack.
Aunque el controlador de navegación es el controlador de vista inicial storyboard, el controlador de vista de los autores es el primer controlador de vista que veremos al iniciar la aplicación. Como mencioné anteriormente, el controlador de navegación no es más que un contenedor que ayuda a navegar entre una jerarquía de controladores de vista. Su vista está poblada por las vistas de los controladores de vista en su navigation stack.
Para
agregar un título a la barra de navegación, agregue la siguiente línea
al método viewDidLoad()
de la clase AuthorsViewController
.
1 |
// Set Title
|
2 |
title = "Authors" |
Cada controlador de vista tiene una
propiedad title
que se usa en varios lugares. La barra de
navegación es uno de ellos. Ejecute la aplicación para ver el resultado
de este pequeño cambio.
Empujando y estallaando
Añadamos ahora la posibilidad de ver una lista de libros cuando el usuario toca el nombre de un autor. Esto significa que tenemos que capturar la selección (el nombre del autor) instanciar un nuevo controlador de vista basado en esa selección, y empujar el nuevo controlador de vista en navigation stack. ¿Suena complicado? No lo es. Deja que te enseñe.
Otro controlador de vista de tabla
¿Por qué no mostrar la lista de libros en otra vista de tabla? Cree
una nueva subclase de UITableViewController
y asígnele el nombre
BooksViewController
.



Cargar la lista de libros es fácil, como vimos anteriormente, pero ¿cómo sabe el controlador de ver libros qué autor ha tocado el usuario? Hay varias maneras de decirle al nuevo controlador de vista acerca de la selección del usuario, pero el enfoque que recomienda Apple se conoce como pasar por referencia. ¿Como funciona esto?
El
controlador de vista de libros declara una propiedad author
que
podemos configurar para configurar el controlador de vista de libros. El controlador de vista de libros usa la propiedad author
para mostrar los libros del autor seleccionado. Abra BooksViewController.swift y agregue una propiedad variable de tipo
[String: AnyObject]!
y nombrelo author
.
1 |
import UIKit |
2 |
|
3 |
class BooksViewController: UIViewController { |
4 |
|
5 |
var author: [String: AnyObject]! |
6 |
|
7 |
...
|
8 |
|
9 |
}
|
¿Por qué no declaramos author
como [String: AnyObject]?
o [String: AnyObject]
? Debido a que una variable necesita
tener un valor inicial, no podemos declarar al author
como [String:
AnyObject]
. Podríamos
usar [String: AnyObject]?
, pero eso significaría que tendríamos que
desenvolver el opcional cada vez que queremos acceder a su valor.
En
Swift, normalmente usará opciones opcionales no envueltas forzadas si
sabe que la propiedad tiene un valor y, más importante aún, si necesita
tener un valor para que su aplicación funcione como se espera. Si
la propiedad author
no tiene un valor, entonces el controlador de
vista de libros nos sirve de poco, ya que no podría mostrar ningún
dato.
Para facilitar el acceso a los libros del autor, también declaramos
una propiedad calculada. Como su nombre lo indica, una propiedad
calculada no almacena un valor. Define un getter y / o setter para
obtener y establecer el valor de otra propiedad. Eche un vistazo a la
propiedad computada books
a continuación.
1 |
var books: [AnyObject] { |
2 |
get { |
3 |
if let books = author["Books"] as? [AnyObject] { |
4 |
return books |
5 |
} else { |
6 |
return [AnyObject]() |
7 |
}
|
8 |
}
|
9 |
}
|
El valor de los books
depende del valor del author
. Comprobamos si el autor tiene un valor para
las claves "Books"
y disminuimos el valor de una matriz de objetos
AnyObject
. Si el autor no tiene un valor para "Books"
, creamos una
matriz vacía de tipo [AnyObject]
. Debido a que la propiedad books
calculados solo define un getter, podemos simplificar la implementación
de esta manera:
1 |
var books: [AnyObject] { |
2 |
if let books = author["Books"] as? [AnyObject] { |
3 |
return books |
4 |
} else { |
5 |
return [AnyObject]() |
6 |
}
|
7 |
}
|
El resto de la clase
BooksViewController
es fácil. Eche un vistazo a las implementaciones de
los tres métodos de protocolo UITableViewDataSource
que se muestran a
continuación.
1 |
override func numberOfSectionsInTableView(tableView: UITableView) -> Int { |
2 |
return 1 |
3 |
}
|
4 |
|
5 |
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { |
6 |
return books.count |
7 |
}
|
8 |
|
9 |
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { |
10 |
// Dequeue Resuable Cell
|
11 |
let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath) |
12 |
|
13 |
if let book = books[indexPath.row] as? [String: String], let title = book["Title"] { |
14 |
// Configure Cell
|
15 |
cell.textLabel?.text = title |
16 |
}
|
17 |
|
18 |
return cell; |
19 |
}
|
Esto
también significa que tenemos que declarar una propiedad constante para
el identificador de reutilización de celda y registrar una clase para
la reutilización de celda en viewDidLoad()
. Esto no es nada nuevo.
1 |
import UIKit |
2 |
|
3 |
class BooksViewController: UITableViewController { |
4 |
|
5 |
let CellIdentifier = "Cell Identifier" |
6 |
|
7 |
...
|
8 |
|
9 |
}
|
1 |
override func viewDidLoad() { |
2 |
super.viewDidLoad() |
3 |
|
4 |
tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier) |
5 |
}
|
Empujando un controlador de vista
Cuando
el usuario toca el nombre de un autor en el controlador de vista de
autores, la aplicación debe mostrar la lista de libros escritos por ese
autor. Esto
significa que necesitamos instanciar una instancia de la clase
BooksViewController
, decirle a la instancia qué autor fue seleccionado
por el usuario, y empujar el nuevo controlador de vista a la navigation stack.
Storyboards nos ayudará con esto. Abra
Main.storyboard, arrastre otra instancia de UITableViewController
desde
la Object Library, y establezca su clase en BooksViewController
en Identity Inspector.



Seleccione la vista de tabla en el nuevo controlador de vista y establezca el número de Prototype Cells en 0 en el Attributes Inspector. Para insertar el controlador de vista de libros en la pila de navegación del controlador de navegación, debemos crear otro segue. Esta vez, sin embargo, creamos un segue manual, un show segue para ser precisos.
Seleccione el controlador de vista de autores en el guión gráfico, mantenga presionada la tecla Control y arrastre desde el controlador de vista de autores al controlador de vista de libros. Seleccione Manual Segue > Show en el menú que aparece para crear una transición del controlador de vista de autores al controlador de vista de libros.



Hay una cosa más que debemos hacer antes de volver a la implementación del controlador de vista de libros. Seleccione el segue que creamos, abra el Attributes Inspector a la derecha y establezca el Identifier de segue en BooksViewController. Al darle un nombre al segue, podemos referirnos a él más adelante en el código.



Para
poner el segue para usar, necesitamos implementar tableView(_:didSelectRowAtIndexPath:)
en el controlador de vista de autores. Este
método se define en el protocolo UITableViewDelegate
como vimos en el
artículo anterior sobre vistas de tabla. En este método, invocamos
performSegueWithIdentifier(_:sender:)
para realizar el segue que
creamos en el storyboard. El método performSegueWithIdentifier(_:sender:)
toma dos argumentos, el identificador del segue y el emisor
del mensaje. Ahora debería estar claro por qué le dimos al segue un
identificador en el storyboard. También tenga en cuenta que
reiniciamos la selección después de realizar el segue.
1 |
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { |
2 |
// Perform Segue
|
3 |
performSegueWithIdentifier(SegueBooksViewController, sender: self) |
4 |
|
5 |
tableView.deselectRowAtIndexPath(indexPath, animated: true) |
6 |
}
|
La constante SegueBooksViewController
es otra propiedad constante de la clase AuthorsViewController
.
1 |
import UIKit |
2 |
|
3 |
class AuthorsViewController: UITableViewController { |
4 |
|
5 |
let CellIdentifier = "Cell Identifier" |
6 |
let SegueBooksViewController = "BooksViewController" |
7 |
|
8 |
...
|
9 |
|
10 |
}
|
Antes
de realizar una segue, el controlador de vista tiene la oportunidad de
prepararse para el segue en prepareForSegue(_:sender:)
. En este método, el controlador de vista puede configurar el controlador
de vista de destino, el controlador de vista de libros. Implementemos
prepareForSegue(_:sender:)
para ver cómo funciona
esto.
1 |
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { |
2 |
if segue.identifier == SegueBooksViewController { |
3 |
if let indexPath = tableView.indexPathForSelectedRow, let author = authors[indexPath.row] as? [String: AnyObject] { |
4 |
let destinationViewController = segue.destinationViewController as! BooksViewController |
5 |
destinationViewController.author = author |
6 |
}
|
7 |
}
|
8 |
}
|
Este método se invoca cada vez que se realiza una
segue. Primero verificamos si el identificador del segue es igual a
SegueBooksViewController
. A continuación, solicitamos a la vista de
tabla la ruta de índice de la selección actual mediante el enlace
opcional. Si se selecciona una fila, preguntamos a los autores por el
authors
que corresponde con esta selección.
En
la instrucción if
, obtenemos una referencia al controlador de vista de
libros (el controlador de vista de destino del segue) y establecemos su
propiedad author
para el autor seleccionado actualmente en la vista de
tabla.
¿Se estará preguntando cuándo o dónde inicializamos el
controlador de vista de libros? No instanciamos explícitamente
una instancia del controlador de vista de libros. El storyboard sabe
qué clase necesita para crear instancias e inicializar una instancia de
BooksViewController
para nosotros.
Antes de ejecutar su aplicación, abra BooksViewController.swift y configure el título del controlador de vista con el nombre del autor para actualizar el título de la barra de navegación.
1 |
override func viewDidLoad() { |
2 |
super.viewDidLoad() |
3 |
|
4 |
if let name = author["Author"] as? String { |
5 |
title = name |
6 |
}
|
7 |
|
8 |
tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier) |
9 |
}
|
}Ejecuta la aplicación. Toque
el nombre de un autor en la vista de tabla y observe cómo se inserta
una nueva instancia de BooksViewController
en navigation stack y se
muestra al usuario. ¿Has notado que también recibimos un botón de
retroceso gratis cuando usamos un controlador de navegación? El título
del controlador de vista anterior se utiliza como el título del botón
Atrás.
Agregar una portada de libro
Cuando el usuario toca un libro en el
controlador de vista de libros, la aplicación debe mostrar la portada
del libro. No usaremos un controlador de vista de tabla para esto. En
su lugar, usamos una subclase simple UIViewController
y mostramos la
portada del libro en una instancia de la clase UIImageView
. UIImageView
es una subclase de UIView
especializada en mostrar imágenes.
Cree una
nueva subclase de UIViewController
, no UITableViewController
, y asígnele
el nombre BookCoverViewController
.



Necesitamos declarar dos propiedades almacenadas
en el nuevo controlador de vista. La
primera propiedad almacenada es una referencia a la vista de la imagen
que vamos a utilizar para mostrar la portada del libro. La palabra clave
@IBOutlet
indica que haremos la conexión en el storyboard. La
segunda propiedad almacenada, Book
, es de tipo [String: String]!
Esta
propiedad representa el libro que se muestra en el controlador de vista
de portada del libro.
1 |
import UIKit |
2 |
|
3 |
class BookCoverViewController: UIViewController { |
4 |
|
5 |
@IBOutlet var bookCoverView: UIImageView! |
6 |
|
7 |
var book: [String: String]! |
8 |
|
9 |
...
|
10 |
|
11 |
}
|
Abra Main.storyboard para crear la interfaz de
usuario del controlador de vista de libro. Arrastre
una instancia de UIViewController
desde la Object Library al
área de trabajo y configure su clase en BookCoverViewController
en el
Identity Inspector.



Arrastre
una instancia de UIImageView
desde la Object Library a la vista
del controlador de vista y haga que cubra toda la vista del controlador
de vista. En Connections Inspector, conéctelo con la outlet
bookCoverView
del controlador de vista.



Para asegurarnos de que la vista de la imagen se muestra correctamente en todos los dispositivos, debemos aplicar las restricciones de diseño necesarias, como se muestra a continuación.



Antes de implementar el controlador de vista, cree un push segue entre el controlador de vista de libros y el controlador de vista de portada del libro. Seleccione el segue y establezca su identificador en BookCoverViewController en el Attributes Inspector.



En la clase
BooksViewController
, declare una propiedad constante para el
identificador de segue.
1 |
import UIKit |
2 |
|
3 |
class BooksViewController: UITableViewController { |
4 |
|
5 |
let CellIdentifier = "Cell Identifier" |
6 |
let SegueBookCoverViewController = "BookCoverViewController" |
7 |
|
8 |
...
|
9 |
|
10 |
}
|
Usamos esta propiedad en tableView(_:didSelectRowAtIndexPath:)
para realizar el segue que creamos en el guión gráfico. No olvide anular la selección de la fila después de realizar el
cambio.
1 |
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { |
2 |
// Perform Segue
|
3 |
performSegueWithIdentifier(SegueBookCoverViewController, sender: self) |
4 |
|
5 |
tableView.deselectRowAtIndexPath(indexPath, animated: true) |
6 |
}
|
La implementación de prepareForSegue(_:sender:)
es
muy similar a la de la clase BooksViewController
. Verificamos
si el identificador de segue es igual a SegueBookCoverViewController
y
solicitamos a la vista de tabla la ruta de índice de la fila
seleccionada actualmente. Solicitamos books
para
el libro que correspondan con la selección del usuario y establecemos la
propiedad book
del controlador de vista de destino, una instancia
de BookCoverViewController
.
1 |
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { |
2 |
if segue.identifier == SegueBookCoverViewController { |
3 |
if let indexPath = tableView.indexPathForSelectedRow, let book = books[indexPath.row] as? [String: String] { |
4 |
let destinationViewController = segue.destinationViewController as! BookCoverViewController |
5 |
destinationViewController.book = book |
6 |
}
|
7 |
}
|
8 |
}
|
Configuramos la vista de la imagen de la clase BookCoverViewController
en su método viewDidLoad()
. Pedimos
a book
el valor de la clave "Cover"
y se crea una instancia
de un objeto UIImage
invocando el inicializador init(named:)
, pasando
el nombre del archivo. Asignamos el objeto UIImage
a la propiedad image
de bookCoverView
.
1 |
override func viewDidLoad() { |
2 |
super.viewDidLoad() |
3 |
|
4 |
if let fileName = book["Cover"] { |
5 |
bookCoverView.image = UIImage(named: fileName) |
6 |
bookCoverView.contentMode = .ScaleAspectFit |
7 |
}
|
8 |
}
|
En
viewDidLoad()
, también configuramos el modo de contenido de la vista de
imagen en ScaleAspectFit
. La propiedad contentMode
es del tipo
UIViewContentMode
, una enumeración. El
valor que asignamos, ScaleAspectFit
, le dice a la vista de la imagen
que estire la imagen tanto como sea posible respetando su relación de
aspecto.
Ejecuta la aplicación y pruébala. Ahora debería poder navegar por los libros almacenados en Books.plist.
¿Dónde estalla?
Anteriormente en este artículo, expliqué que los controladores de vista pueden ser empujados y levantados desde una navigation stack. Hasta ahora, solo hemos enviado los controladores de vista a una navigation stack. Hacer estallar un controlador de vista desde una navigation stack se lleva a cabo cuando el usuario toca el botón Atrás de la barra de navegación. Esta es otra funcionalidad que obtenemos de forma gratuita.
En
algún momento, sin embargo, se encontrará con una situación en la que
manualmente debe mostrar un controlador de vista desde una navigation stack. Puede hacerlo llamando a popViewControllerAnimated(_:)
en
el controlador de vista de navegación. Esto elimina el controlador de
vista superior de la navigation stack.
De
forma alternativa, puede mostrar todos los controladores de vista de la
pila de navegación, con la excepción del controlador de vista raíz, al
llamar a popToRootViewControllerAnimated(_:)
en el controlador de
navegación.
¿Cómo se accede al controlador de navegación de un
controlador de vista? La clase UIViewController
declara una propiedad
calculada, navigationController
, de tipo UINavigationController?
. Si
el controlador de vista está en una pila de navegación, esta propiedad
hace referencia al controlador de navegación al que pertenece la navigation stack.
Conclusion
Espero que estén de acuerdo en que los controladores de navegación no son tan complicados. Este artículo podría haber sido mucho más corto, pero espero que hayas aprendido algunas cosas más en el camino. En el siguiente artículo, echamos un vistazo a los controladores de navegacion de pestañas. Aunque los controladores de barra de pestañas también administran una colección de controladores de vista, son bastante diferentes de los controladores de navegación.
Si tiene preguntas o comentarios, puede dejarlos en los comentarios a continuación o comunicarse conmigo en Twitter.