Coordinator паттерн в SwiftUI

Курс охватывает архитектурные принципы паттерна Coordinator и его практическое применение в SwiftUI-приложениях. Вы научитесь централизовать навигацию, управлять модальными окнами и реализовывать сложные пользовательские flows с использованием NavigationStack и NavigationPath.

1. Введение в Coordinator паттерн

Введение в Coordinator паттерн: зачем он нужен и как работает

Представьте типичный сценарий: вы разрабатываете приложение для интернет-магазина. Пользователь видит список товаров, тапает на карточку, переходит на детальный экран, оттуда — в корзину, затем в оформление заказа, потом в экран оплаты. Каждый экран знает, куда вести дальше. ProductListView создаёт ProductDetailView, ProductDetailView создаёт CartView, и так далее. Поначалу это кажется разумным — SwiftUI же декларативный, всё просто!

Но через три месяца вы хотите открыть CartView напрямую из пуш-уведомления. Или показать ProductDetailView из экрана поиска. Или добавить A/B тест, где часть пользователей видит другой флоу оформления заказа. И вот тут начинается настоящая боль.

Проблема: навигация, размазанная по всему приложению

В стандартном SwiftUI-подходе навигация живёт прямо внутри вью:

На первый взгляд — чисто и лаконично. Но у этого подхода есть системные проблемы:

  • Жёсткая связанность (tight coupling): ProductListView напрямую зависит от ProductDetailView. Изменить один экран без риска сломать другой становится сложно.
  • Дублирование маршрутов: если ProductDetailView открывается из трёх мест, логика создания этого экрана дублируется трижды.
  • Невозможность тестирования навигации: чтобы проверить, что тап на товар ведёт на нужный экран, нужно монтировать весь UI.
  • Deep links — nightmare: чтобы открыть конкретный экран по ссылке, приходится городить костыли в каждом вью.
  • > Вью не должны знать, куда они ведут. Они должны лишь сообщать о намерении пользователя — а кто-то другой решит, что с этим делать.

    Именно этот «кто-то другой» и есть Coordinator.

    Что такое Coordinator

    Coordinator (координатор) — это объект, который централизует всю навигационную логику приложения. Вместо того чтобы каждый экран самостоятельно решал, куда вести пользователя, экраны делегируют это решение координатору.

    Паттерн был введён в iOS-сообщество Сорушем Ханлу (Soroush Khanlou) в 2015 году в статье "Coordinators Redux" — изначально для UIKit, где контроллеры страдали от синдрома «Massive View Controller». В SwiftUI проблема другая по форме, но та же по сути: логика навигации расползается по вью.

    Аналогия из жизни: представьте авиадиспетчера. Самолёты (вью) не решают сами, куда лететь и на какую полосу садиться — они сообщают о своём положении и намерениях, а диспетчер (координатор) принимает все решения о маршрутах. Пилоты сосредоточены на управлении самолётом, диспетчер — на управлении потоками.

    !Схема Coordinator паттерна: вью делегируют навигацию координатору, который управляет NavigationPath

    Три принципа, на которых стоит Coordinator

    Разделение ответственности

    Каждый компонент делает только своё дело:

    | Компонент | Ответственность | |---|---| | View | Отображение UI, реакция на действия пользователя | | Coordinator | Решения о навигации, создание экранов | | ViewModel | Бизнес-логика, работа с данными |

    Вью не знает, что будет после тапа на кнопку. Она просто вызывает метод координатора — coordinator.showProductDetail(product) — и забывает об этом.

    Единственный источник истины для навигации

    Весь граф переходов описан в одном месте. Хотите понять, откуда можно попасть на экран оплаты? Открываете координатор — и видите все пути. Не нужно искать NavigationLink по всему проекту.

    Тестируемость

    Координатор — это обычный Swift-объект. Его можно создать в тесте, вызвать метод навигации и проверить, что состояние изменилось правильно — без запуска симулятора и без монтирования вью.

    Как это выглядит в коде: минимальный пример

    Вот простейшая реализация координатора для нашего магазина:

    Теперь посмотрите, как изменился ProductListView:

    ProductListView больше не импортирует ProductDetailView. Она не знает о его существовании. Если завтра вы решите показывать вместо детального экрана модальный sheet — меняете только координатор.

    Ключевые компоненты паттерна

    Полноценная реализация Coordinator в SwiftUI обычно состоит из нескольких слоёв:

    Route enum — перечисление всех возможных экранов и переходов. Каждый кейс может нести ассоциированные данные (например, productDetail(Product)). Это единственный «словарь» навигации в приложении.

    Coordinator-объектObservableObject или @Observable-класс, который хранит NavigationPath и предоставляет методы навигации. Именно он принимает решения: пушить экран, показывать sheet, переключать таб.

    NavigationStack с path — SwiftUI-контейнер, который рендерит стек экранов на основе NavigationPath. Координатор управляет этим path программно.

    Environment для передачи координатора — координатор передаётся вниз по иерархии вью через .environment(), чтобы любой экран мог вызвать навигационный метод без prop drilling.

    Когда Coordinator действительно нужен

    Паттерн не является серебряной пулей. Для приложения из трёх экранов он добавит сложности без реальной пользы. Вот признаки того, что пора вводить координатор:

  • В приложении больше 5–7 экранов с нетривиальными переходами между ними
  • Один и тот же экран открывается из нескольких мест
  • Нужна поддержка deep links или push-уведомлений с навигацией
  • Есть отдельные флоу (авторизация, онбординг, оформление заказа), которые должны быть изолированы
  • Команда больше одного человека и нужно чёткое разделение ответственности
  • Если ваше приложение — это таб-бар с тремя экранами в каждом табе и без deep links, стандартный NavigationLink вполне справится.

    Coordinator vs стандартный NavigationLink: сравнение

    Разница кажется небольшой, но в масштабе реального приложения это разница между архитектурой, которую можно поддерживать, и той, которую хочется переписать с нуля.

    Паттерн Coordinator — это не про конкретный фреймворк или синтаксис. Это про принцип: навигация — это отдельная ответственность, и она должна жить в отдельном объекте. Всё остальное в этом курсе — детали реализации этого принципа в SwiftUI с использованием NavigationStack, NavigationPath, модальных окон, флоу-координаторов и передачи зависимостей.