Al crear apps iOS, llega un momento en que el controlador de vista engorda demasiado.
El código de UI, el procesamiento de datos y hasta pushViewController la navegación se mezclan.
Si lo dejas así, cuanto más complejo es el flujo, más difícil resulta rastrear cada transición.
Hoy veremos el patrón Coordinator de iOS, que separa limpiamente la navegación del controlador de vista.
En resumen, el patrón Coordinator añade un objeto independiente que se encarga de la navegación. El controlador de vista dibuja la pantalla y delega al Coordinator la decisión del siguiente destino.
¿Qué es el patrón Coordinator?
Se puede definir en una línea:
Coordinator es un objeto dedicado al flujo de navegación entre pantallas.
Antes, el controlador de vista dibujaba la pantalla, recibía la entrada del usuario y también mostraba la siguiente pantalla.
De todo eso, extraemos la transición a la siguiente pantalla y se la delegamos al Coordinator.
Así, el controlador de vista solo debe informar al Coordinator de que se pulsó un botón.
No necesita saber adónde ni cómo se realizará la transición.
Los controladores de vista dejan de conocerse directamente. La pantalla A no necesita conocer la B, lo que facilita mucho la reutilización.
¿Por qué separar la navegación?
Tener código de navegación dentro del controlador de vista causa bastantes problemas.
He resumido los que experimenté personalmente.
- Dependencias entrelazadas: A crea directamente B, así que si B cambia, también hay que modificar A.
- Sin reutilización: para usar la misma pantalla en otro flujo, hay que modificar otra vez la navegación.
- Flujo difícil de entender: la lógica de navegación está repartida en 20 archivos y no se ve el panorama completo.
- Pruebas difíciles: la navegación queda ligada al controlador de vista, lo que complica probarla de forma aislada.
Centralizar la navegación resuelve buena parte de estos problemas.
El flujo de pantallas de la app se entiende con solo mirar un archivo de Coordinator.
¿Cómo se crea un Coordinator?
La estructura básica es más sencilla de lo que parece.
Primero defines un protocolo común. start() actúa como punto de entrada.
protocol Coordinator: AnyObject {
var navigationController: UINavigationController { get }
func start()
}
Después creas el Coordinator real, que también se encarga de crear y mostrar la primera pantalla.
final class MainCoordinator: Coordinator {
let navigationController: UINavigationController
init(nav: UINavigationController) { self.navigationController = nav }
func start() {
let vc = HomeViewController()
vc.coordinator = self // canal para recibir solicitudes de navegación
navigationController.pushViewController(vc, animated: false)
}
func showDetail() { // la navegación a la siguiente pantalla solo ocurre aquí
let vc = DetailViewController()
navigationController.pushViewController(vc, animated: true)
}
}
Cuando se pulsa un botón, el controlador de vista solo tiene que llamar a coordinator?.showDetail().
No tienes que preocuparte por el destino ni por cómo se inserta la pantalla.
¿En qué se diferencia de hacer push o usar Segue directamente?
Es una duda frecuente, así que la resumo en una tabla.
| Elemento | Push directo / Segue | Patrón Coordinator |
|---|---|---|
| Ubicación de la navegación | Dentro del controlador de vista | Centralizada en Coordinator |
| Dependencias entre pantallas | Se conocen directamente | No necesitan conocerse |
| Reutilización de pantallas | Difícil | Fácil |
| Comprensión del flujo | Repartido entre varios archivos | Gestionado en un solo lugar |
| Trabajo inicial | Menor | Algo mayor |
Como ves, Coordinator no es una solución universal.
En una app pequeña con solo dos o tres pantallas, puede aumentar el código y complicar más de lo que ayuda.
En cambio, en apps con flujos ramificados, como inicio de sesión, onboarding o pestañas, demuestra claramente su valor.
Preguntas frecuentes
P. Si hay varias pantallas, ¿creo varios Coordinators?
Sí. Normalmente se dividen por flujo: inicio de sesión, flujo principal, etc. Es habitual que un Coordinator superior gestione los inferiores como hijos.
P. ¿Cuándo se elimina un Coordinator hijo?
Al terminar el flujo, el padre debe quitar la referencia al hijo del array. De lo contrario, permanece en memoria y provoca una fuga.
P. ¿También se usa con SwiftUI?
SwiftUI tiene enrutamiento basado en NavigationStack y path, así que el enfoque es algo distinto. Aun así, la idea de extraer el flujo a un objeto independiente se puede aplicar igual.
Al principio puede resultar engorroso añadir un archivo más.
Pero cuando la app crece hasta diez o veinte pantallas, apreciarás de verdad esta estructura.
Prueba a extraer primero un flujo pequeño con Coordinator. Notarás cómo el controlador de vista se aligera. 🙂
Artículos recomendados
- [Fundamentos de Swift #1] ¿Qué es realmente un Optional de Swift? En realidad, es un enum (incluye 5 formas prácticas de desempaquetarlo)
- [Fundamentos de Swift #2] Guía completa de los closures de Swift: por qué captura, [weak self] y @escaping van de la mano
- [Filosofía de Swift #1] ¿Por qué Swift es tan estricto? Guía completa de sus tres principios: Safe, Fast y Expressive

