В этой статье рассматривается навигация в SwiftUI до iOS 16. Если вы заинтересованы в реализации координаторов потока для более новых версий iOS, начиная с iOS 16, я рекомендую вам ознакомиться с моей обновленной статьей здесь Шаблон координатора потока SwiftUI с NavigationStack для координации навигация между представлениями (iOS 16+)

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

  1. Примитивы навигации в SwiftUI

Большую часть навигации в SwiftUI можно выполнить с помощью @Binding, который сохраняет состояние активации навигации, а также специальных модификаторов и представлений SwiftUI, т. е.
fullScreenCover, sheet, alert, confirmDialog или NavigationLink.

NavigationLink( “Purple”, destination: ColorDetail(color: .purple), isActive: $shouldShowPurple)
NavigationLink(tag: .firstLink, selection: activeLink, destination: firstDestination) { EmptyView() }

И для представления модальных окон мы можем использовать что-то вроде этого

view .sheet(isPresented: $isShowingSheet, onDismiss: didDismiss) { 
  Text(“License Agreement”)
 } 
view.sheet(item: sheetItem, content: sheetContent)

2. Вид и модель представления

Обычно мы отделяем логику представления от бизнес-логики (или подготовки данных представления), разделяя код на представление и модель представления.

ViewModel должен подготовить все необходимые данные для отображения в представлении (выходные данные) и обрабатывать все действия, поступающие из представления (входные данные).

Представление должно просто обрабатывать отображение этих данных и размещение их на экране.

Простой вид может выглядеть так

И ViewModal для этого представления готовит текст для отображения и обрабатывает firstAction следующим образом.

3. Создание координатора потока

В SwiftUI все примитивы навигации должны вызываться в контексте представления для правильной работы. Таким образом, мы можем сделать некоторые предположения о координаторах потока:
1. Координатор потока — это представление
2. Координатор потока у нас есть на каждый экран
3. События навигации должны передаваться координатору потока из ViewModel
4. Нам нужно некоторое перечисление, которое будет представлять эти навигационные события

3.1 Создание протокола, представляющего состояние координатора потока
Этот протокол позволяет нам передавать события навигации из модели представления в координатор потока.

Здесь ContentLink — это перечисление, представляющее различные события/действия навигации.

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

Таким образом, наша полная ContentViewModel, обрабатывающая несколько действий пользователя и реализующая ContentFlowStateProtocol, может выглядеть так.

3.2 Создание перечисления ContentLink для событий навигации
Это перечисление определяет различные события навигации, которые могут происходить на нашем экране приложения. Этим событиям могут передаваться некоторые параметры. Более того, перечисление ContentLink должно быть идентифицируемым и хешируемым.

В перечислении мы определяем несколько вычисляемых свойств, т.е. id для заполнения протокола Идентифицируемый, navigationLink для сопоставления параметризованных случаев событий с родственными случаями, >sheetLink, чтобы изолировать и сопоставить случаи, которые должны отображаться с использованием модального представления.

3.3 Реализация представления FlowCoordinator для каждого экрана

Наиболее важной частью нашего шаблона координатора потока будет представление ContentFlowCoordinator. Он будет обрабатывать всю логику навигации по экрану.

Сначала я покажу, как может выглядеть такой координатор, а затем объясню некоторые вещи.

Во-первых, его init (здесь неявный) будет иметь параметры, использующие дженерики.
1. состояние типа реализации ContentFlowStateProtocol.
2. контент, который будет отображаться на экране @ViewBuilder

Во-вторых, состояние должно храниться как @ObservedObject, и оно не должно быть @StateObject, поскольку ContentFlowStateProtocol реализуется ContentViewModel, и эта модель представления будет уже сохранен как @StateObject на экране ContentView.

В-третьих, у нас есть вспомогательные привязки, созданные как вычисляемые свойства для NavigationLink, т. е. activeLink, и для представления листа, т. е. sheetItem.

Вся логика навигации реализована внутри вычисляемого свойства тела ContentFlowCoordinator. Добавлено NavigationView, встроено свойство navigationLinks и прикреплен модификатор sheet(item:…).

И последнее, но не менее важное: у нас есть фабричные функции, которые создают представления назначения/контента. Они извлекают возможные параметры навигационного события, создают с их помощью модели представления и, наконец, просматривают с использованием этой модели представления.

4. Использование FlowCoordinator с представлением

Последний шаг, который остался для завершения экрана ContentView, — собрать все вместе и реализовать это представление. Это то же представление, что и в начале этого руководства, но с добавлением нашего нового ContentFlowCoordinator и расширением универсального типа модели представления, требующего принятия ContentFlowStateProtocol.

Мы также добавили больше действий в это представление.

Если вас интересует полный исходный код, где вы можете клонировать репозиторий и протестировать этот шаблон Flow Coordinator в действии, я рекомендую вам посетить мою ссылку на github: https://github.com/michzio/FlowCoordinator-in-SwiftUI.