Coordinator: klíčové pojmy, vzor koordinátora pro navigaci v iOS

Autor: IT Sectr Publikováno: 2026-02-18 Doba čtení: 9 min

Coordinator (koordinátor) — architektonický navigační vzor, který přenáší logiku přechodů mezi obrazovkami z ViewControlleru do samostatných tříd. Vzor navrhl Soroush Khanlou v roce 2015 a získal široké rozšíření v iOS komunitě. Coordinator spravuje flow aplikace: vytváří a zobrazuje ViewController, předává data mezi obrazovkami a zpracovává dokončení flow. Vzor řeší problém Massive View Controller tím, že navigaci vyjímá z kontroléru. Více — v původním článku o Coordinatorovi.

Hlavní body

  • Coordinator — navigační vzor, který přenáší logiku přechodů z ViewControlleru
  • Oddělení odpovědností — ViewController spravuje UI, Coordinator — navigaci
  • Router — pomocná komponenta Coordinatoru pro abstrakci UINavigationControlleru
  • Flow — posloupnost obrazovek spravovaná jedním Coordinatorem (např. onboarding)
  • Delegování — dceřiné koordinátory hlásí rodiči prostřednictvím delegate/protocol

Co je Coordinator: podstata navigačního vzoru

Coordinator — vzor, který přebírá odpovědnost za navigaci v iOS aplikaci. Ve standardním UIKit si ViewController spravuje přechody sám: present, push, show segue — všechny navigační metody jsou volány z UIViewController. Coordinator tuto logiku přenáší: ViewController hlásí událost (např. „uživatel stiskl tlačítko přihlášení“), Coordinator rozhoduje, která obrazovka se zobrazí dále. ViewController zůstává pouze s UI logikou a deleguje navigaci koordinátorovi.

Struktura vzoru — CoordinatorProtocol s metodami start() a finish(). start() — začátek flow: vytvoření prvního ViewControlleru a zobrazení. finish() — dokončení flow s oznámením rodičovskému koordinátorovi. Router — obal kolem UINavigationControlleru (nebo UISplitViewControlleru), který poskytuje metody show, push, pop, dismiss. Koordinátor nepracuje přímo s UINavigationControllerem — pouze přes Router. To umožňuje testovat navigaci a přepínat UI framework.

KomponentaRolePříklad
CoordinatorSpráva navigačního flowAuthCoordinator, ProfileCoordinator
RouterAbstrakce nad UINavigationControllerempush, present, pop, dismiss
ViewControllerUI + delegování událostí CoordinatoruLoginViewController.delegate

Problémy, které Coordinator řeší — Massive View Controller (navigace — častá příčina rozrůstání kontroléru). Ve standardním UIKit obsahuje ViewController prepareForSegue, delegáty navigace, zpracování unwind segues. Coordinator to eliminuje. Segue ve storyboardu — statické propojení mezi obrazovkami, Coordinator poskytuje dynamickou navigaci s podmínkami. Testování navigace je možné: Coordinator lze testovat bez UI kontrolou posloupnosti volání Routeru.

Coordinator ve Swiftu: implementace s Router a Flow

Základní Coordinator ve Swiftu — protokol s asociovaným typem pro Router a metodami start/finish. Router — protokol abstrahující UINavigationController. Konkrétní implementace Routeru obaluje UINavigationController a deleguje mu metody. Coordinator přijímá Router v init a používá jej pro navigaci. Dceřiné koordinátory jsou uloženy v poli childCoordinators pro správu životního cyklu.

swift
// Router — abstrakce navigace
protocol RouterProtocol: AnyObject {
    func push(_ viewController: UIViewController, animated: Bool)
    func pop(animated: Bool)
    func present(_ viewController: UIViewController, animated: Bool)
    func dismiss(animated: Bool)
}

final class NavigationRouter: RouterProtocol {
    private let navigationController: UINavigationController

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }

    func push(_ vc: UIViewController, animated: Bool) {
        navigationController.pushViewController(vc, animated: animated)
    }

    func pop(animated: Bool) {
        navigationController.popViewController(animated: animated)
    }

    func present(_ vc: UIViewController, animated: Bool) {
        navigationController.present(vc, animated: animated)
    }

    func dismiss(animated: Bool) {
        navigationController.dismiss(animated: animated)
    }
}

// Coordinator — správa flow
protocol CoordinatorProtocol: AnyObject {
    var childCoordinators: [CoordinatorProtocol] { get set }
    var router: RouterProtocol { get }
    func start()
    func finish()
}

class AuthCoordinator: CoordinatorProtocol {
    var childCoordinators: [CoordinatorProtocol] = []
    let router: RouterProtocol

    init(router: RouterProtocol) {
        self.router = router
    }

    func start() {
        let loginVC = LoginViewController()
        loginVC.onLogin = { [weak self] in
            self?.showHome()
        }
        router.push(loginVC, animated: true)
    }

    private func showHome() {
        let homeCoordinator = HomeCoordinator(router: router)
        childCoordinators.append(homeCoordinator)
        homeCoordinator.start()
    }

    func finish() {
        childCoordinators.removeAll()
        router.pop(animated: true)
    }
}

Vytvoření Coordinatoru v AppDelegate/SceneDelegate — AppDelegate nebo SceneDelegate vytvoří UINavigationController, obalí jej do NavigationRouteru, vytvoří kořenový Coordinator (AppCoordinator) a zavolá start(). AppCoordinator rozhodne, zda zobrazit onboarding, přihlášení nebo hlavní obrazovku — v závislosti na stavu aplikace. Coordinator — jediný vstupní bod pro navigaci, ViewController neví o ostatních obrazovkách.

Dceřiný Coordinator a hierarchie koordinátorů

Hierarchie koordinátorů — AppCoordinator → AuthCoordinator/MainCoordinator → ProfileCoordinator/SettingsCoordinator. Dceřiný koordinátor je vytvořen rodičem a uložen v poli childCoordinators. Když dceřiný koordinátor dokončí práci, zavolá finish() u rodiče a rodič jej odstraní z childCoordinators. To zabraňuje únikům paměti: Coordinator má silný odkaz na ViewController (přes Router) a bez odstranění z childCoordinators objekt nebude uvolněn.

swift
// Delegát pro komunikaci Coordinator -> Parent
protocol AuthCoordinatorDelegate: AnyObject {
    func authCoordinatorFinished(_ coordinator: AuthCoordinator)
}

class AuthCoordinator: CoordinatorProtocol {
    weak var delegate: AuthCoordinatorDelegate?

    func finish() {
        delegate?.authCoordinatorFinished(self)
    }
}

// AppCoordinator — rodič
class AppCoordinator: AuthCoordinatorDelegate {
    func startAuthFlow() {
        let authCoordinator = AuthCoordinator(router: router)
        authCoordinator.delegate = self
        childCoordinators.append(authCoordinator)
        authCoordinator.start()
    }

    func authCoordinatorFinished(_ coordinator: AuthCoordinator) {
        childCoordinators.removeAll { $0 is AuthCoordinator }
        startMainFlow()
    }
}

Správa childCoordinators — odstranění Coordinatoru z pole je jediný způsob, jak jej uvolnit. Pokud zapomenete odstranit dokončený Coordinator, zůstává v paměti spolu s ViewControllery. Doporučuje se: didMove(toParent:) rodiče, callback při dokončení nebo Combine publisher pro automatické odstranění. Vzor Coordinator nespecifikuje mechanismus oznámení — delegát, closure nebo Combine — volba je na vývojáři.

Způsoby předávání dat mezi koordinátory

Předávání dat prostřednictvím delegáta — dceřiný Coordinator definuje protokol delegáta s metodami, přes které se předávají výsledky: func authCoordinator(_:didLoginWith user: User). Rodič implementuje protokol a přijímá data při dokončení dceřiného flow. To je typově bezpečné a explicitní. Nevýhoda: pro každý dceřiný Coordinator je třeba napsat samostatný protokol. U projektů s 10+ Coordinatory to vede k nárůstu počtu souborů.

Předávání dat přes typ Result — metoda finish přijímá Result, kde Output je generický typ výsledku flow. Coordinator — generika s asociovaným typem výsledku. start() s callbackem: start(completion: @escaping (Output) -> Void). To zkracuje kód: není třeba psát samostatný protokol pro každý koordinátor. RxSwift/Combine: Coordinator publikuje výsledek přes PassthroughSubject/Publisher. Volba závisí na architektonickém přístupu týmu.

MetodaVýhodyNevýhody
DelegateTypově bezpečné, explicitní, samostatné protokolyHodně protokolů, hodně boilerplate
ClosureKompaktní, méně souborůObtížné ladění retain cycle
Combine/RxReaktivní, snadno kombinovatelnéZávislost na knihovně, obtížnější Debug

Sdílená datová vrstva — Coordinatory nepředávají data přímo, ale používají sdílenou službu/repozitář. AuthCoordinator ukládá token do Keychain/UserDefaults, ProfileCoordinator odtud čte. Coordinatory komunikují prostřednictvím sdíleného stavu (kontejner Dependency Injection), nikoli přímými voláními. To snižuje provázanost Coordinatorů, ale vytváří implicitní závislosti na sdíleném stavu.

Srovnání Coordinatoru s Router, VIPER a MVVM-C

Coordinator vs Router — Router je komponenta Coordinatoru, která abstrahuje UINavigationController. Coordinator odpovídá za flow (kterou obrazovku zobrazit), Router — za mechaniku (jak zobrazit: push/present). Router je „jak“, Coordinator je „co“. Router lze použít bez Coordinatoru (např. Navigator-singleton), ale Coordinator bez Routeru — jen ViewController s jinou abstrakcí. Obvykle se oba vzory používají společně.

Coordinator vs VIPER — VIPER má komponentu Wireframe odpovědnou za navigaci — analog Coordinatoru. Ve VIPERu je Wireframe součástí modulu, Coordinator — samostatná vrstva nad moduly. Modul VIPER (View-Interactor-Presenter-Entity-Router) zahrnuje navigaci jako součást modulu. Coordinator je vůči modulům externí: vytváří a propojuje moduly, ale není součástí jejich složení. Coordinator je flexibilnější pro opětovné použití obrazovek v různých flow.

MVVM-C — rozšíření MVVM o Coordinator. ViewModel nezná Coordinator přímo — ViewController deleguje navigaci přes ViewModel, ViewModel volá koordinátora přes protokol. MVVM-C — standardní přístup pro iOS projekty se SwiftUI: Coordinator spravuje NavigationStack nebo fullScreenCover, ViewModel volá koordinátora publikováním stavu. Apple nedoporučuje Coordinator pro SwiftUI — NavigationStack a NavigationPath jsou vestavěné navigační mechanismy.

swift
// MVVM-C: ViewModel volá Coordinator přes protokol
protocol AuthNavigationProtocol: AnyObject {
    func showMainScreen()
    func showForgotPassword()
}

class AuthViewModel: ObservableObject {
    weak var navigation: AuthNavigationProtocol?

    func loginTapped() {
        // logika...
        navigation?.showMainScreen()
    }
}

Často kladené otázky

Je Coordinator potřeba pro SwiftUI?

Pro SwiftUI vestavěná navigace (NavigationStack, NavigationPath) často nahrazuje Coordinator. Apple doporučuje path-based navigaci. Coordinator má smysl pro komplexní flow s hlubokými podmínkami (onboarding-přihlášení-hlavní obrazovka v závislosti na roli). Pro jednoduché aplikace na SwiftUI je Coordinator nadbytečný — použijte NavigationPath.

Je Coordinator to samé jako Router?

Ne, jsou to různé vzory. Coordinator spravuje navigační flow: rozhoduje, kterou obrazovku zobrazit, vytváří ViewControllery a propojuje je. Router — abstrakce nad UINavigationControllerem: push, present, pop, dismiss. Coordinator používá Router k provedení navigace. V některých implementacích Router zahrnuje logiku Coordinatoru (Router-per-screen), ale to je odchylka od původního vzoru.

Jak se vyhnout retain cycle v Coordinatoru?

Dvě hlavní místa úniků: childCoordinators (rodič drží dceřiný, zapomene jej odstranit) a Router (UINavigationController drží ViewController). Řešení: vždy odstraňovat dceřiný Coordinator z pole při finish(). Použít weak referenci pro delegate. Pro Router — nedržet silný odkaz na UINavigationController, pokud je již v hierarchii window. Testujte deinit Coordinatoru.

Kdy je Coordinator nadbytečný?

Pro aplikace s 3-5 obrazovkami je Coordinator nadbytečný — segue nebo jednoduché navigationController.pushViewController je jednodušší. Pro aplikace SwiftUI s NavigationStack — také nadbytečný. Coordinator je ospravedlnitelný pro aplikace s 15+ obrazovkami, komplexními flow (onboarding s větvemi, autorizace s obnovením hesla) a smíšenými UIKit/SwiftUI projekty.

Jak testovat Coordinator?

Mock Router — kontrola, které metody jsou volány a s jakými parametry. Kontrola childCoordinators: po start() pole není prázdné, po finish() — prázdné. Coordinator se testuje bez UI: Router je protokol, jeho mock nevyžaduje UIKit. Použijte XCTestExpectation pro asynchronní flow. V Androidu analogicky — testování NavigationController a NavHost s mock-navigací.

Shrnutí

  • Coordinator — navigační vzor, který přenáší přechody z ViewControlleru do samostatné třídy
  • Router — abstrakce UINavigationControlleru používaná Coordinatorem
  • Hierarchie — rodičovské a dceřiné Coordinatory s delegováním výsledků
  • MVVM-C — standardní přístup pro UIKit projekty s Coordinatorem
  • SwiftUI — vestavěná navigace přes NavigationStack nahrazuje Coordinator
  • Testování — Coordinator se testuje přes mock Router bez UIKit

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také