Навигация: 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. Освоив базовый стек, вы легко перейдёте к продвинутым сценариям: вкладкам, модальным окнам и координаторам навигации.