Это руководство раскрывает тайны навигационной системы SwiftUI — от настройки TabView до глубинных ссылок и расширенных NavigationPaths.
Введение: почему навигация до сих пор всех сбивает с толку (включая меня)
Если бы SwiftUI был парком развлечений, навигация была бы домом с привидениями — сначала захватывающе, но быстро понимаешь, что не знаешь, как из него выбраться.
Apple подарила нам NavigationView, а затем деликатно похлопала по плечу, выпустив новенький блестящий NavigationStack. Добавьте NavigationPath, onOpenURL и всеми любимый загадочный ящик — диплинкинг — и теперь даже опытные iOS-разработчики выглядят как тот мем с парнем, вокруг которого парят математические уравнения.
Но не волнуйтесь. Создаёте ли вы простое приложение с несколькими экранами или проектируете динамический поток с URL-адресами и кастомными типами данных, SwiftUI действительно может сделать простую навигацию. Как только вы разберётесь с инструментами (и будете знать, когда с ними не стоит бороться), всё станет на свои места.
Вот что вы узнаете из этой статьи:
- Как использовать
TabViewдля быстрой навигации на нескольких экранах - Как
NavigationStackзаменяет устаревшийNavigationView - Что, чёрт возьми, делает
NavigationPathи зачем он нужен - Как работать с глубокими ссылками и заставить приложение реагировать на URL-адреса
- Распространённые ошибки и как не нарушить логику навигации
Давайте начнём с самого простого инструмента навигации SwiftUI: TabView
TabView: самый простой инструмент навигации SwiftUI
Если NavigationStack — это новая модная игрушка, то TabView — это старый добрый велосипед, к которому вы всегда возвращаетесь — простой, надёжный и на удивление увлекательный.
Что такое TabView? Это способ SwiftUI, позволяющий пользователям переключаться между различными разделами вашего приложения с помощью нижней панели вкладок — представьте себе домашнюю страницу, поиск, рилсы, профиль… ну, вы поняли.
Вот как легко начать:
struct ContentView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
Label("Home", systemImage: "house.fill")
}
SettingsView()
.tabItem {
Label("Settings", systemImage: "gear")
}
}
}
}
Два представления, две вкладки и работающее приложение. Никаких роутеров, никакой магии.
Советы профессионалов:
- Используйте
Label(title:image:)для красивого сочетания значка и текста. - SF Symbols создают ощущение нативных элементов.
- SwiftUI сохраняет состояние каждой вкладки. Так что, если у вашей вкладки есть счётчик
@State, и вы переключаетесь на другую вкладку — он запомнится! Удобно или раздражает — в зависимости от вашего дизайна. - Хотите скрыть панель вкладок? Оберните
TabViewв кастомное условие, но не пытайтесь принудительно скрывать его для каждого экрана — SwiftUI может дать сбой.
Распространённая путаница: TabView + NavigationStack
Да, вы можете объединить оба элемента — просто поместите NavigationStack внутрь каждой вкладки, если хотите, чтобы экраны в них располагались. Вот так:
TabView {
NavigationStack {
HomeView()
}
.tabItem { Label("Home", systemImage: "house") }
NavigationStack {
SettingsView()
}
.tabItem { Label("Settings", systemImage: "gear") }
}
И вот так у каждой вкладки есть свой собственный контекст навигации.
NavigationStack: попрощайтесь с NavigationView
Помните NavigationView? Что ж, Apple тихонько отказалась от него, начиная с iOS 16, и приветствовала NavigationStack как новый инструмент для навигации между экранами. И нет, это не просто переименование — это совершенно новый подход.
Что такое NavigationStack?
Представьте его как современную, более мощную версию NavigationView, созданную для лучшей работы с развивающейся природой SwiftUI, основанной на данных. Он позволяет вам добавлять и выводить представления, используя типы значений, с большим контролем над историей навигации.
Вот простой пример:
struct ContentView: View {
var body: some View {
NavigationStack {
List {
NavigationLink("Go to Detail", value: "SwiftUI ❤️ NavigationStack")
}
.navigationDestination(for: String.self) { value in
Text("You selected: \(value)")
}
.navigationTitle("Home")
}
}
}
Ключевые концепции:
NavigationStackзаменяетNavigationView, начиная с iOS 16.- Используйте
NavigationLink(value:)сnavigationDestination(for:)для создания типобезопасной динамической навигации. - Вы определяете, какой тип данных передается и как он должен рендерится при пуше.
Когда использовать:
- Когда нужен глубокий контроль над обработкой навигации.
- Когда передаете данные (например, модель или строку) на следующий экран.
- Когда нужен предсказуемый и декларативный способ пуша представлений — без странных ошибок, которыми так славился
NavigationView.
Небольшой сдвиг, большое влияние
Важно отметить: с NavigationStack вы больше не привязываете навигацию к иерархиям представлений. Вместо этого это больше похоже на создание стека значений данных — и SwiftUI сам определяет, как их отобразить.
Как NavigationLink и .navigationDestination работают вместе
В NavigationStack навигация управляется значениями, а не жёстко заданными представлениями назначения. Это сильно отличается от того, как раньше работал NavigationView. Давайте разберёмся:
Шаг 1: Используйте NavigationLink(value:) для передачи значения
Вместо того, чтобы писать что-то вроде NavigationLink(destination: DetailView()), теперь вы передаете значение определённого типа:
NavigationLink("Go to Profile", value: "karanpal")
Эта строка говорит:
При нажатии поместить представление в стек навигации и использовать строковое значение «karanpal», чтобы определить, какое представление отображать.
- Go to Profile → Это метка, которая отображается в пользовательском интерфейсе. Это то, что видит пользователь и нажимает.
- karanpal → Это значение, которое помещается в
NavigationStackпри тапе.
Шаг 2: Определите, что делать с этим значением, с помощью .navigationDestination(for:)
Здесь вы объявляете, как следует обрабатывать конкретный тип при его помещении в стек:
.navigationDestination(for: String.self) { username in
ProfileView(username: username)
}
Это означает:
Если передана строка, использовать её для построения данного представления.
Простыми словами
NavigationLink(value:)добавляет значение вNavigationStack..navigationDestination(for:)сообщает SwiftUI, как преобразовать это значение в представление.- Затем SwiftUI автоматически сопоставляет тип переданного значения с замыканием
.navigationDestinationи отображает соответствующее представление.
NavigationPath: для случаев, когда вам нужен полный контроль
К настоящему моменту NavigationLink(value:) и .navigationDestination(for:) кажутся отличным дуэтом. Но что, если навигация вашего приложения станет сложнее?
Возможно, вы:
- Перемещаетесь между разными типами пунктов назначения
- Создаёте собственный стек переходов (да, такое возможно!)
- Восстанавливаете состояние навигации из сохранённых данных или глубокой ссылки
Вот тут-то и вступает в дело NavigationPath.
Что такое NavigationPath?
NavigationPath — это специальный объект SwiftUI, который действует как type-erased стек значений. Он позволяет программно добавлять, извлекать и манипулировать стеком — без участия пользователя.
Представьте это так: «Я буду отслеживать всё, куда был выполнен переход, и мне всё равно, какой это тип — просто скажите мне, как это отобразить».
Простой пример использования
Допустим, мы хотим пушить разные типы данных — данные о пользователе или статью.
struct User: Hashable { let name: String } struct Article: Hashable { let title: String }
Вот как настроить динамический путь навигации:
@State private var path = NavigationPath()
NavigationStack(path: $path) {
List {
Button("Open User") {
path.append(User(name: "Karan"))
}
Button("Open Article") {
path.append(Article(title: "SwiftUI Tips"))
}
}
.navigationDestination(for: User.self) { user in
Text("User: \(user.name)")
}
.navigationDestination(for: Article.self) { article in
Text("Article: \(article.title)")
}
}
Зачем это нужно?
С NavigationPath вы можете:
- Перемещаться программно — пушить данные из логики, а не только по нажатиям
- Обрабатывать несколько типов в одном стеке
- Делать Pop или сбрасывать стек (
path.removeLast(),path.removeAll()) - Сохранять/восстанавливать историю навигации из состояния (отлично подходит для диплинкинга или сохранения сессий)
Реальные сценарии
- Глубокие ссылки: можно вручную построить путь, соответствующий URL-адресу, например,
myapp://user/Karan/article/SwiftUI - Восстановление состояния: когда пользователь снова открывает приложение, можно перестроить путь, чтобы вернуть его на место остановки
- Потоки онбординга: программно запушить несколько представлений подряд при выполнении условия
NavigationPath — это как предоставить SwiftUI динамический GPS, а не просто следовать статическому маршруту. Вы управляете стеком. Вы пушите значения. А SwiftUI выполняет рендеринг.
Что дальше? Давайте изучим диплинкинг и наконец-то заставим URL-адреса myapp:// работать так, как им положено.
Глубокие ссылки в SwiftUI: не так страшно, как кажется
Вы когда-нибудь нажимали на уведомление или ссылку на веб-сайте и попадали в глубины приложения, минуя заставку, словно VIP-гость? Это и есть диплинкинг. И да — SwiftUI поддерживает его нативно, без магии UIKit.
Что такое глубокие ссылки
Глубокие ссылки — это то, как ваше приложение открывает определённый экран при нажатии на URL-адрес, например:
myapp://profile/karanpal myapp://article/swiftui-navigation
Они очень полезны для:
- Открытия приложения из Safari или Почты
- Нажатия на кнопки push-уведомлений
- Внутренних переходов между разделами
Обработка глубоких ссылок в SwiftUI: AppDelegate не нужен
В UIKit всё это обрабатывалось через AppDelegate. В SwiftUI это обрабатывается с помощью .onOpenURL {} — и вот ключевая часть:
.onOpenURL следует разместить на верхнем уровне вашего приложения, в идеале внутри структуры App, чтобы гарантировать перехват ссылок независимо от того, где находится пользователь.
Пример: обработка ссылки на профиль
Допустим, вы хотите сделать ссылку на профиль:
myapp://profile/karanpal
Вот как это сделать в структуре App вашего SwiftUI-приложения:
@main
struct MyApp: App {
@State private var path = NavigationPath()
var body: some Scene {
WindowGroup {
NavigationStack(path: $path) {
HomeView()
.onOpenURL { url in
handleDeepLink(url)
}
.navigationDestination(for: User.self) { user in
ProfileView(username: user.name)
}
}
}
}
private func handleDeepLink(_ url: URL) {
guard let host = url.host else { return }
switch host {
case "profile":
if let username = url.pathComponents.dropFirst().first {
path.append(User(name: username))
}
default:
break
}
}
}
Такая настройка гарантирует:
- Ваше приложение прослушивает диплинки сразу после запуска.
- Вам не нужно зависеть от активности какого-то случайного экрана для перехвата URL.
- Вы можете пушить data-driven назначения с помощью
NavigationPath.
Регистрация вашей URL-схемы
Чтобы сообщить iOS, что ваше приложение обрабатывает такие типы ссылок:
- Перейдите в настройки
.xcodeprojвашего приложения - Target → Info → URL Types
- Добавьте новую запись с вашей пользовательской схемой (
myapp)
Вуаля — теперь ваше приложение поддерживает глубокие ссылки.
Советы и подсказки: что сэкономит вам часы отладки
Навигационная система SwiftUI элегантна, современна и декларативна… пока внезапно не перестаёт быть таковой. Вот несколько реальных подвохов (и как их обходить, как настоящий ниндзя Swift).
1. NavigationLink не срабатывает? Проверьте тип значения
Если нажатие на NavigationLink(value:) ничего не происходит, обычно это из-за следующих причин:
- Вы не определили
.navigationDestination(for:)для нужного типа - Или вы передали не хешируемое/optional значение
Исправление: всегда проверяйте, совпадают ли тип значения и тип .navigationDestination(for:) — не допускается Optional, не допускаются несоответствия.
2. Вложение NavigationStack в другой? SwiftUI может запутаться
Пытаетесь вложить NavigationStack в другой (например, во вкладку)? Технически допустимо. Логически опасно.
Вы можете увидеть:
- Странное поведение навигации
.navigationDestinationне срабатывает- Кнопка «Назад» исчезает
Исправление: избегайте вложенных стеков. Вместо этого создавайте новый NavigationStack на уровне TabView — каждая вкладка должна иметь свой собственный стек.
3. Изменение path вручную? Легко разрушить стек
Хотя path.append(...) — мощный метод, слепое добавление значений может:
- Нарушить иерархию представления, если
.navigationDestinationне определен - Заставить SwiftUI молча игнорировать навигацию
Исправление: проверьте правильность перед добавлением. Кроме того, если вы восстанавливаете состояние из JSON или глубокой ссылки, перестройте путь в правильном порядке.
4. Состояние навигации не сбрасывается при выходе из системы
Если вы используете NavigationPath, и пользователь выходит из системы, он может всё ещё оставаться в стеке при повторном входе.
Исправление: при выходе из системы вызовите:
path.removeAll()
Это сбрасывает стек, возвращая пользователя в начало навигационного потока.
5. TabView + NavigationStack: остерегайтесь путаницы состояний
У каждой вкладки может быть свой NavigationStack, но если вы используете общее состояние для вкладок, SwiftUI может вызвать непредвиденное поведение (например, возврат к глубокому экрану после переключения вкладок).
Исправление: используйте отдельные модели представления для каждой вкладки или сбрасывайте состояние навигации при переключении вкладок пользователем, если это необходимо.
Краткое содержание: вы только что распутали навигационную сеть SwiftUI
Если вы дошли до этого места — поздравляем! Теперь вы понимаете навигацию SwiftUI больше, чем большинство разработчиков, которые в ярости бросают на полпути создания панели вкладок.
Вот краткий обзор того, что мы рассмотрели:
TabView: самый простой способ переключаться между разделами приложения с минимальным кодом и максимальной функциональностью.NavigationStack: современная заменаNavigationView, использующая навигацию, основанную на значениях.NavigationLink+.navigationDestination: декларативный дуэт — представления запускаются значениями, а не императивными пушами представлений.NavigationPath: когда вы хотите стать профессионалом. Программная навигация, динамические стеки и глубокий контроль.- Глубокие ссылки: используйте
.onOpenURLв точке входа в приложение, анализируйте URL-адреса и отправляйте значения в стек. - Подводные камни: от странного поведения при возврате данных до сброса состояния при входе в систему — теперь вы знаете, чего следует избегать и как это исправить.
Модель навигации SwiftUI поначалу может показаться немного запутанной, но как только вы поймете правила игры, она окажется одновременно элегантной и мощной.

