Ingeniería iOS

Patrón Coordinator en iOS: separa la navegación del controlador

Al crear apps iOS, llega un momento en que el controlador de vista engorda demasiado.

4 min de lectura
Imagen de portada de Patrón Coordinator en iOS: separa la navegación del controlador

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().

Con un toque en el botón, el Coordinator se encarga del resto
Con un toque en el botón, el Coordinator se encarga del resto

No tienes que preocuparte por el destino ni por cómo se inserta la pantalla.

Al dejar solo esta llamada, el controlador de vista queda mucho más ligero
Al dejar solo esta llamada, el controlador de vista queda mucho más ligero

¿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.

Cuantos más caminos tenga el flujo, más útil resulta
Cuantos más caminos tenga el flujo, más útil resulta

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