Навигация: NavigationStack

Приложения состоят из множества экранов. NavigationStack — современный, управляемый данными способ переходить между ними, пришедший на смену устаревшему NavigationView.
Суть урока: в современной навигации вы привязываете переход не к конкретному экрану, а к ДАННЫМ. NavigationLink толкает значение, а navigationDestination решает, какой экран ему соответствует.

С iOS 16 NavigationView объявлен устаревшим, его заменил NavigationStack. Главная идея — навигация, управляемая данными. NavigationLink «толкает» в стек не вью, а значение; а модификатор navigationDestination(for:) описывает, какой экран показать для значения такого типа:

struct AppView: View {
    let products = ["Кофе", "Чай", "Какао"]
    var body: some View {
        NavigationStack {
            List(products, id: \.self) { name in
                NavigationLink(name, value: name)   // толкаем значение
            }
            .navigationTitle("Меню")
            .navigationDestination(for: String.self) { name in
                Text("Детали: \(name)")             // экран для значения
            }
        }
    }
}

Такое разделение мощно: список не знает, как выглядит экран деталей — он лишь передаёт данные. А navigationDestination в одном месте решает, что показать. Это разводит «что выбрали» и «как это отобразить».

Навигацией можно управлять программно через путь — массив значений. Меняя массив, вы переходите вперёд, назад или сразу к корню:

struct RootView: View {
    @State private var path: [String] = []
    var body: some View {
        NavigationStack(path: $path) {
            Button("Открыть профиль") {
                path.append("profile")     // переход программно
            }
            .navigationDestination(for: String.self) { route in
                Text("Экран: \(route)")
            }
        }
    }
}
NavigationStack(path: [ ... ])
   корень
     |  append("profile")
     v
   [profile]            -- экран профиля
     |  append("settings")
     v
   [profile, settings]  -- экран настроек
     |  path.removeLast()
     v
   [profile]            -- назад
     |  path = []
     v
   корень               -- сразу к началу

Попробуй сам ▶ — запусти код прямо в браузере (Pyodide). Здесь нет Swift, но логика та же, что под капотом мобильного кода:

# Навигация как стек значений: path управляет историей экранов.
path = []

def go(route):
    path.append(route)
    print('Переход ->', path)

def back():
    if path:
        path.pop()
    print('Назад   ->', path)

def to_root():
    path.clear()
    print('К корню ->', path)

go('profile')
go('settings')
back()
to_root()

Как работает под капотом

NavigationStack хранит путь как массив значений-маршрутов. Каждый элемент пути сопоставляется с подходящим navigationDestination по типу значения и порождает экран. Поскольку путь — это обычное состояние (@State), навигация подчиняется тем же правилам реактивности: измените массив — изменится стек экранов. Это делает возможными глубокие ссылки (deep links) и восстановление навигации: достаточно воссоздать массив пути.

Частые ошибки

  • Использовать устаревший NavigationView. В новых проектах берите NavigationStack.
  • Забыть navigationDestination. Без него NavigationLink с value не знает, что показывать.
  • Несоответствие типа значения и destination. Тип в value должен совпадать с типом в navigationDestination(for:).

Best practices

  • Передавайте в NavigationLink данные (value), а не готовые вью.
  • Управляйте сложными переходами через массив path для программной навигации.
  • Используйте типобезопасные маршруты (часто enum) для масштабируемой навигации.

Итоги. NavigationStack — современный, управляемый данными механизм навигации. NavigationLink толкает значения, navigationDestination сопоставляет их с экранами, а массив path даёт полный программный контроль над историей переходов.

Шире контекста

Управляемая данными навигация NavigationStack — это куда больше, чем косметическая замена старого NavigationView. Поскольку весь путь хранится в обычном массиве состояния, навигация становится сериализуемой: вы можете сохранить путь, восстановить его при следующем запуске и вернуть пользователя ровно туда, где он был. На этом строятся глубокие ссылки (deep links), когда нажатие на уведомление или переход по ссылке открывает конкретный экран в глубине приложения — достаточно собрать нужный массив маршрутов. В крупных проектах маршруты часто описывают перечислением, что делает навигацию типобезопасной: компилятор не даст перейти на несуществующий экран. Разделение «список толкает данные» и «navigationDestination решает, как их показать» — это тот же принцип разделения ответственности, что и везде в SwiftUI. Освоив базовый стек, вы легко перейдёте к продвинутым сценариям: вкладкам, модальным окнам и координаторам навигации.

Проверьте себя
1. Что современный NavigationLink передаёт в стек?
AГотовое вью экрана
BЗначение (данные), которому navigationDestination сопоставляет экран
CСтроку URL
DЗамыкание без аргументов
2. Как осуществить программную навигацию в NavigationStack?
AВызвать present()
BИзменять массив path, привязанный через NavigationStack(path:)
CМенять глобальную переменную
DЧерез таймер