āļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ SwiftUI NavigationStack: āļĢāļđāļāđāļāļāļāļēāļĢāļāļģāļāļēāļ 2026
āđāļāļĢāļĩāļĒāļĄāļāļąāļ§āļŠāļąāļĄāļ āļēāļĐāļāđ iOS āļāđāļ§āļĒāļāļģāļāļēāļĄāļŠāļģāļāļąāļāđāļāļĩāđāļĒāļ§āļāļąāļ NavigationStack, NavigationPath āđāļĨāļ°āļĢāļđāļāđāļāļāļāļēāļĢāļāļģāļāļēāļāļŠāļĄāļąāļĒāđāļŦāļĄāđāđāļ SwiftUI

āļāļēāļĢāļāļģāļāļēāļāđāļāđāļāđāļŠāļēāļŦāļĨāļąāļāļŠāļģāļāļąāļāļāļāļāđāļāļāļāļĨāļīāđāļāļāļąāļ iOS āļāļļāļāļāļąāļ§ āļāļąāđāļāđāļāđ iOS 16 āđāļāđāļāļāđāļāļĄāļē NavigationStack āđāļāđāđāļāđāļēāļĄāļēāđāļāļāļāļĩāđ NavigationView āđāļĨāļ°āļĄāļāļāļāļēāļĢāļāļ§āļāļāļļāļĄāđāļāļīāļāđāļāļĢāđāļāļĢāļĄāđāļāđāļĄāļĢāļđāļāđāļāļāđāļŦāļāļ·āļāļŠāđāļāđāļāļāļēāļĢāļāļģāļāļēāļ āļāļēāļĒāļāđāļēāļāļĄāļąāļāļāļ°āļāļāļŠāļāļāļāļ§āļēāļĄāđāļāļĩāđāļĒāļ§āļāļēāļāđāļāđāļāļ§āļāļīāļāđāļŦāļĨāđāļēāļāļĩāđāđāļāļĢāļ°āļŦāļ§āđāļēāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđāļāļēāļāđāļāļāļāļīāļāđāļāđāļāļāļĢāļ°āļāļģ
āđāļāđāļĨāļ°āļāļģāļāļēāļĄāļāļģāļĨāļāļāļĢāļđāļāđāļāļāļāļāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđāļāļēāļāđāļāļāļāļīāļāļāļĢāļīāļ āļāļĢāđāļāļĄāļāļģāļāļāļāđāļāļĒāļĨāļ°āđāļāļĩāļĒāļāđāļĨāļ°āđāļāđāļāļāļĩāđāđāļāđāļāļēāļāđāļāđ āđāļāļ·āđāļāļŦāļēāļāļ°āļāđāļāļĒ āđ āđāļāļīāđāļĄāļāļ§āļēāļĄāļāļąāļāļāđāļāļāļāļēāļāļĢāļ°āļāļąāļāļāļ·āđāļāļāļēāļāđāļāļāļķāļāļĢāļ°āļāļąāļāļŠāļđāļ
āļāļ·āđāļāļāļēāļāļāļāļ NavigationStack
āļāļģāļāļēāļĄāļāļĩāđ 1: NavigationView āđāļĨāļ° NavigationStack āļāđāļēāļāļāļąāļāļāļĒāđāļēāļāđāļĢ?
NavigationView (āđāļĨāļīāļāđāļāđāļāļąāđāļāđāļāđ iOS 16) āļŠāļĢāđāļēāļāļāļēāļĢāļāļģāļāļēāļāđāļāļāđāļāļĒāļāļĢāļīāļĒāļēāļĒāđāļāļĒāļāļīāļāļāļēāļ NavigationLink āļāļĩāđāļāđāļāļāļāļąāļ āļŠāđāļ§āļ NavigationStack āđāļāļ°āļāļģāđāļāļ§āļāļēāļāđāļāļāđāļāļīāļāļāļĢāļ°āļāļēāļĻāļāļĢāđāļāļĄāļŠāđāļāđāļāļāļēāļĢāļāļģāļāļēāļāļāļĩāđāļāļąāļāđāļāļāđāļĨāļ°āļŠāļēāļĄāļēāļĢāļāļāļĢāļąāļāđāļāļĨāļĩāđāļĒāļāđāļāđāļāđāļ§āļĒāđāļāļĢāđāļāļĢāļĄ
// â Old pattern with NavigationView (deprecated)
struct OldNavigation: View {
var body: some View {
NavigationView {
NavigationLink("Details", destination: DetailView())
}
}
}
// â
New pattern with NavigationStack
struct ModernNavigation: View {
// Navigation stack is explicit and controllable
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List {
// NavigationLink with typed value
NavigationLink("User 1", value: User(id: 1, name: "Alice"))
NavigationLink("User 2", value: User(id: 2, name: "Bob"))
}
// Destination defined by value type
.navigationDestination(for: User.self) { user in
UserDetailView(user: user)
}
}
}
}āļāđāļāđāļāđāđāļāļĢāļĩāļĒāļāļŦāļĨāļąāļāļāļ·āļāļāļēāļĢāđāļĒāļāļāļēāļĢāļāļĢāļ°āļāļēāļĻāļĨāļīāļāļāđāļāļāļāļāļēāļāļāļĨāļēāļĒāļāļēāļ āļāļķāđāļāļāđāļ§āļĒāđāļŦāđāļāļēāļĢāļāļģāļāļēāļāđāļāđāļāđāļāļāļĢāļ§āļĄāļĻāļđāļāļĒāđāđāļĨāļ°āļāļāļŠāļāļāđāļāđ
āļāļģāļāļēāļĄāļāļĩāđ 2: NavigationPath āļāļģāļāļēāļāļāļĒāđāļēāļāđāļĢ?
NavigationPath āļāļ·āļāļāļāļāđāļāļāđāļāļāļĢāđāđāļāļāļĨāļāļāļĢāļ°āđāļ āļ (type-erased) āļāļĩāđāļāļąāļāđāļāđāļāļāđāļēāļāļēāļĢāļāļģāļāļēāļ āļŠāļēāļĄāļēāļĢāļāļāļąāļāļāļēāļĢāļŠāđāļāđāļāđāļāđāđāļāļĒāđāļĄāđāļāļģāđāļāđāļāļāđāļāļāļĢāļđāđāļāļĢāļ°āđāļ āļāļāļĩāđāđāļāđāļāļāļāļāļāļāļŦāļāđāļēāļāļ āļāļāļ°āđāļāļĩāļĒāļ§āļāļąāļāļāđāļāļāļāļ§āļēāļĄāļāļĨāļāļāļ āļąāļĒāļāļāļāļāļĢāļ°āđāļ āļāđāļ§āđāđāļāđāļ§āļĨāļēāļāļāļĄāđāļāļĨāđ
struct ContentView: View {
// NavigationPath can contain different Hashable types
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
VStack(spacing: 20) {
Button("View user profile") {
// Adds a User to the stack
path.append(User(id: 1, name: "Alice"))
}
Button("View settings") {
// Adds a Settings enum to the stack
path.append(SettingsRoute.notifications)
}
Button("Back to root") {
// Clears the entire stack
path.removeLast(path.count)
}
}
.navigationDestination(for: User.self) { user in
UserDetailView(user: user)
}
.navigationDestination(for: SettingsRoute.self) { route in
SettingsView(route: route)
}
}
}
}
// Types must be Hashable
struct User: Hashable {
let id: Int
let name: String
}
enum SettingsRoute: Hashable {
case notifications
case privacy
case account
}NavigationPath āđāļāđāļāļēāļĢāļĨāļāļāļĢāļ°āđāļ āļāļ āļēāļĒāđāļāđāļāđāļāļĢāļ§āļāļŠāļāļāļāļĢāļ°āđāļ āļāđāļāđāļ§āļĨāļēāļāļāļĄāđāļāļĨāđāļāđāļēāļ navigationDestination āļāđāļēāļāļĩāđāđāļĄāđāļĄāļĩāļāļĨāļēāļĒāļāļēāļāļāļĩāđāļŠāļāļāļāļĨāđāļāļāļāļąāļāļāļ°āļāļģāđāļŦāđāđāļāļīāļāļāđāļāļāļīāļāļāļĨāļēāļāđāļāļĩāļĒāļāđāļāđāļ§āļĨāļēāļĢāļąāļ
āļāļģāļāļēāļĄāļāļĩāđ 3: āļāļ°āļāļģāļāļēāļĢāļāļģāļāļēāļāđāļāļīāļāđāļāļĢāđāļāļĢāļĄāļāļĩāđāļŠāļĄāļāļđāļĢāļāđāđāļāđāļāđāļāļĒāđāļēāļāđāļĢ?
āļāļēāļĢāļāļģāļāļēāļāđāļāļīāļāđāļāļĢāđāļāļĢāļĄāļāđāļ§āļĒāđāļŦāđāļāļ§āļāļāļļāļĄāļŠāđāļāđāļāđāļāđāļāļēāļāļāļļāļāđāļāļāđāđāļāđāđāļāđāļāđāļ āđāļāļĒāđāļĄāđāļāđāļāļāļāļēāļĻāļąāļĒāļāļēāļĢāļāļĢāļ°āļāļģāļāļāļāļāļđāđāđāļāđāđāļāļĒāļāļĢāļ āļŠāļīāđāļāļāļĩāđāļŠāļģāļāļąāļāļŠāļģāļŦāļĢāļąāļ deep link, āļāļēāļĢāđāļāļĨāļĩāđāļĒāļāđāļŠāđāļāļāļēāļāļŦāļĨāļąāļāļāļēāļĢāļĒāļ·āļāļĒāļąāļāļāļąāļ§āļāļ āļŦāļĢāļ·āļāđāļāļĨāļ§āđāļŦāļĨāļēāļĒāļāļąāđāļāļāļāļ
// Centralized router to manage navigation
@Observable
class NavigationRouter {
var path = NavigationPath()
// Navigate to a specific screen
func navigateTo(_ destination: AppRoute) {
path.append(destination)
}
// Go back one level
func goBack() {
guard !path.isEmpty else { return }
path.removeLast()
}
// Return to root
func popToRoot() {
path.removeLast(path.count)
}
// Navigate to a complete stack (deep link)
func navigateToPath(_ routes: [AppRoute]) {
popToRoot()
for route in routes {
path.append(route)
}
}
}
// Enum defining all app routes
enum AppRoute: Hashable {
case userList
case userDetail(userId: Int)
case userEdit(userId: Int)
case settings
case settingsDetail(SettingsSection)
}
enum SettingsSection: String, Hashable {
case notifications, privacy, account
}
// Usage in main view
struct MainView: View {
@State private var router = NavigationRouter()
var body: some View {
NavigationStack(path: $router.path) {
HomeView()
.navigationDestination(for: AppRoute.self) { route in
destinationView(for: route)
}
}
.environment(router)
}
@ViewBuilder
private func destinationView(for route: AppRoute) -> some View {
switch route {
case .userList:
UserListView()
case .userDetail(let userId):
UserDetailView(userId: userId)
case .userEdit(let userId):
UserEditView(userId: userId)
case .settings:
SettingsView()
case .settingsDetail(let section):
SettingsDetailView(section: section)
}
}
}āļĢāļđāļāđāļāļāļāļĩāđāļĢāļ§āļĄāļĻāļđāļāļĒāđāļāļĢāļĢāļāļ°āļāļēāļĢāļāļģāļāļēāļāļāļąāđāļāļŦāļĄāļ āļāļģāđāļŦāđāļāļēāļĢāļāļāļŠāļāļāļŦāļāđāļ§āļĒāđāļĨāļ°āļāļēāļĢāļāļģāļĢāļļāļāļĢāļąāļāļĐāļēāļāđāļēāļĒāļāļķāđāļ
āļĢāļđāļāđāļāļāļāļēāļĢāļāļģāļāļēāļāļāļąāđāļāļŠāļđāļ
āļāļģāļāļēāļĄāļāļĩāđ 4: āļāļ°āļāļģ deep link āļĄāļēāđāļāđāļāļąāļ NavigationStack āļāļĒāđāļēāļāđāļĢ?
Deep link āļāđāļ§āļĒāđāļāļīāļāđāļāļāļāļĨāļīāđāļāļāļąāļāđāļāļĒāļąāļāļŦāļāđāļēāļāļāļāļĩāđāļĢāļ°āļāļļāđāļāđāđāļāļĒāļāļĢāļāļāļēāļ URL āļ āļēāļĒāļāļāļ āļāđāļ§āļĒ NavigationStack āļŠāļēāļĄāļēāļĢāļāļŠāļĢāđāļēāļāļŠāđāļāđāļāļāļķāđāļāđāļŦāļĄāđāđāļāđāļāđāļ§āļĒāđāļāļĢāđāļāļĢāļĄ
@Observable
class DeepLinkHandler {
var router: NavigationRouter
init(router: NavigationRouter) {
self.router = router
}
// Parse a URL and navigate to destination
func handle(url: URL) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let host = components.host else {
return
}
// Build navigation stack according to URL
let routes = parseRoutes(host: host, path: components.path, queryItems: components.queryItems)
router.navigateToPath(routes)
}
private func parseRoutes(host: String, path: String, queryItems: [URLQueryItem]?) -> [AppRoute] {
// myapp://users/42/edit â [.userList, .userDetail(42), .userEdit(42)]
switch host {
case "users":
return parseUserPath(path)
case "settings":
return parseSettingsPath(path)
default:
return []
}
}
private func parseUserPath(_ path: String) -> [AppRoute] {
let segments = path.split(separator: "/").map(String.init)
var routes: [AppRoute] = [.userList]
if let userIdString = segments.first, let userId = Int(userIdString) {
routes.append(.userDetail(userId: userId))
// /users/42/edit
if segments.count > 1 && segments[1] == "edit" {
routes.append(.userEdit(userId: userId))
}
}
return routes
}
private func parseSettingsPath(_ path: String) -> [AppRoute] {
var routes: [AppRoute] = [.settings]
let segments = path.split(separator: "/").map(String.init)
if let sectionString = segments.first,
let section = SettingsSection(rawValue: sectionString) {
routes.append(.settingsDetail(section))
}
return routes
}
}
// In the main App
@main
struct MyApp: App {
@State private var router = NavigationRouter()
@State private var deepLinkHandler: DeepLinkHandler?
var body: some Scene {
WindowGroup {
MainView()
.environment(router)
.onOpenURL { url in
// Handle deep links
deepLinkHandler?.handle(url: url)
}
.onAppear {
deepLinkHandler = DeepLinkHandler(router: router)
}
}
}
}āļāļģāļāļēāļĄāļāļĩāđ 5: āļāļ°āđāļāđāļāđāļĨāļ°āļāļđāđāļāļ·āļāļŠāļāļēāļāļ°āļāļēāļĢāļāļģāļāļēāļāļāļĒāđāļēāļāđāļĢ?
āļāļēāļĢāđāļāđāļāļŠāļāļēāļāļ°āļāļēāļĢāļāļģāļāļēāļāļāđāļ§āļĒāđāļŦāđāļāļđāđāļāļ·āļāļāļģāđāļŦāļāđāļāļāļāļāļāļđāđāđāļāđāđāļāđāļŦāļĨāļąāļāļāļēāļāđāļĢāļīāđāļĄāļāđāļāđāļāļāđāļŦāļĄāđ NavigationPath āļĢāļāļāļĢāļąāļ Codable āļŠāļģāļŦāļĢāļąāļāļāļēāļĢāļāļĩāđāļĢāļĩāļĒāļĨāđāļĨāđāļāļāļąāļ
// Extension to make NavigationPath persistable
extension NavigationPath {
// Encode path to Data
func encoded() -> Data? {
guard let representation = self.codable else { return nil }
return try? JSONEncoder().encode(representation)
}
// Decode from Data
static func decoded(from data: Data) -> NavigationPath? {
guard let representation = try? JSONDecoder().decode(
NavigationPath.CodableRepresentation.self,
from: data
) else {
return nil
}
return NavigationPath(representation)
}
}
// Router with persistence
@Observable
class PersistentNavigationRouter {
var path: NavigationPath {
didSet {
saveState()
}
}
private let storageKey = "navigation_path"
init() {
// Restore state at startup
if let data = UserDefaults.standard.data(forKey: storageKey),
let restored = NavigationPath.decoded(from: data) {
self.path = restored
} else {
self.path = NavigationPath()
}
}
private func saveState() {
if let data = path.encoded() {
UserDefaults.standard.set(data, forKey: storageKey)
}
}
func clearPersistedState() {
UserDefaults.standard.removeObject(forKey: storageKey)
}
}āđāļāļ·āđāļāđāļŦāđ NavigationPath.codable āļāļģāļāļēāļāđāļāđ āļāļļāļāļāļĢāļ°āđāļ āļāļāļĩāđāđāļāļīāđāļĄāļĨāļāđāļ path āļāđāļāļāđāļāđāļ Codable āļāļāļāļāļēāļ Hashable āļĄāļīāļāļ°āļāļąāđāļāļāļĢāđāļāļāđāļāļāļĢāđāļāļĩ codable āļāļ°āļāļ·āļāļāđāļē nil
āļāļĢāđāļāļĄāļāļĩāđāļāļ°āļāļīāļāļīāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ iOS āđāļĨāđāļ§āļŦāļĢāļ·āļāļĒāļąāļāļāļĢāļąāļ?
āļāļķāļāļāļāļāđāļ§āļĒāļāļąāļ§āļāļģāļĨāļāļāđāļāļāđāļāđāļāļāļ, flashcards āđāļĨāļ°āđāļāļāļāļāļŠāļāļāđāļāļāļāļīāļāļāļĢāļąāļ
āļāļģāļāļēāļĄāļāļĩāđ 6: āļāļ°āļāļąāļāļāļēāļĢāļāļēāļĢāļāļģāļāļēāļāļĢāđāļ§āļĄāļāļąāļāđāļāļĨāļ§āđāļāļēāļĢāļĒāļ·āļāļĒāļąāļāļāļąāļ§āļāļāļāļĒāđāļēāļāđāļĢ?
āđāļāļĨāļ§āđāļāļēāļĢāļĒāļ·āļāļĒāļąāļāļāļąāļ§āļāļāļĄāļąāļāļāđāļāļāđāļāļĨāļĩāđāļĒāļāđāļŠāđāļāļāļēāļāđāļāļĒāļąāļāļŦāļāđāļēāļāļāļāļĩāđāđāļāđāļĢāļąāļāļāļēāļĢāļāđāļāļāļāļąāļāļŦāļĨāļąāļāļāļēāļāđāļāđāļēāļŠāļđāđāļĢāļ°āļāļ āļŦāļĢāļ·āļāļāļĨāļąāļāđāļāļĒāļąāļāļŦāļāđāļēāđāļāđāļēāļŠāļđāđāļĢāļ°āļāļāļŦāļĨāļąāļāļāļāļāļāļēāļāļĢāļ°āļāļ āļĢāļđāļāđāļāļāļāđāļāđāļāļāļĩāđāļāļąāļāļāļēāļĢāļāļēāļĢāđāļāļĨāļĩāđāļĒāļāļāđāļēāļāđāļŦāļĨāđāļēāļāļĩāđāđāļāđāļāļĒāđāļēāļāđāļĢāļĩāļĒāļāļĢāđāļāļĒ
enum AuthState {
case unauthenticated
case authenticated(User)
}
@Observable
class AuthManager {
var state: AuthState = .unauthenticated
var pendingDeepLink: URL?
func login(email: String, password: String) async throws {
// Simulated authentication
let user = try await AuthService.shared.login(email: email, password: password)
state = .authenticated(user)
}
func logout() {
state = .unauthenticated
}
}
struct RootView: View {
@State private var authManager = AuthManager()
@State private var router = NavigationRouter()
var body: some View {
Group {
switch authManager.state {
case .unauthenticated:
// Separate navigation stack for auth
AuthNavigationStack(authManager: authManager)
case .authenticated:
// Main app stack
MainNavigationStack(router: router, authManager: authManager)
}
}
.onChange(of: authManager.state) { oldState, newState in
handleAuthStateChange(from: oldState, to: newState)
}
}
private func handleAuthStateChange(from oldState: AuthState, to newState: AuthState) {
switch (oldState, newState) {
case (.unauthenticated, .authenticated):
// Login successful: process pending deep link
if let pendingURL = authManager.pendingDeepLink {
DeepLinkHandler(router: router).handle(url: pendingURL)
authManager.pendingDeepLink = nil
}
case (.authenticated, .unauthenticated):
// Logout: reset navigation
router.popToRoot()
default:
break
}
}
}
struct AuthNavigationStack: View {
let authManager: AuthManager
@State private var authPath = NavigationPath()
var body: some View {
NavigationStack(path: $authPath) {
LoginView(authManager: authManager)
.navigationDestination(for: AuthRoute.self) { route in
switch route {
case .register:
RegisterView(authManager: authManager)
case .forgotPassword:
ForgotPasswordView()
}
}
}
}
}
enum AuthRoute: Hashable {
case register
case forgotPassword
}āļāļģāļāļēāļĄāļāļĩāđ 7: āļāļ°āļāļģāļāļēāļĢāļāļģāļāļēāļāđāļāļāđāļĄāļāļāļĨāđāļāđāļāđāļāļąāļ NavigationStack āļāļĒāđāļēāļāđāļĢ?
āđāļĄāļāļāļĨāđāļĨāļ° sheet āļāđāļāļāļĄāļĩāļāļĢāļīāļāļāļāļēāļĢāļāļģāļāļēāļāļāļāļāļāļąāļ§āđāļāļ āļāļēāļĢāļĢāļ§āļĄ NavigationStack āļāļąāļāļāļēāļĢāđāļŠāļāļāļāļĨāđāļāļāđāļĄāļāļāļĨāļāļķāļāļāđāļāļāļāļēāļĢāļāļēāļĢāļāļąāļāļāļēāļĢāļŠāļāļēāļāļ°āļāļĩāđāđāļĒāļāļāļāļāļāļēāļāļāļąāļ
struct ParentView: View {
@State private var mainPath = NavigationPath()
@State private var showSettings = false
@State private var showUserProfile: User?
var body: some View {
NavigationStack(path: $mainPath) {
ContentView()
.toolbar {
Button("Settings") {
showSettings = true
}
}
.navigationDestination(for: MainRoute.self) { route in
MainRouteView(route: route)
}
}
// Sheet with its own NavigationStack
.sheet(isPresented: $showSettings) {
SettingsSheet()
}
// Conditional sheet based on item
.sheet(item: $showUserProfile) { user in
UserProfileSheet(user: user)
}
}
}
// Each sheet has its own NavigationStack
struct SettingsSheet: View {
@Environment(\.dismiss) private var dismiss
@State private var settingsPath = NavigationPath()
var body: some View {
NavigationStack(path: $settingsPath) {
SettingsListView()
.navigationTitle("Settings")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Close") {
dismiss()
}
}
}
.navigationDestination(for: SettingsSection.self) { section in
SettingsDetailView(section: section)
}
}
}
}
// Extension to make User identifiable for sheet(item:)
extension User: Identifiable {}āļāļēāļĢāļāļąāļāļāļēāļĢāļŠāļāļēāļāļ°āđāļĨāļ°āļāļ§āļēāļĄāļŠāļēāļĄāļēāļĢāļāđāļāļāļēāļĢāļāļāļŠāļāļ
āļāļģāļāļēāļĄāļāļĩāđ 8: āļāļ°āđāļāļĩāļĒāļ unit test āļŠāļģāļŦāļĢāļąāļāļāļēāļĢāļāļģāļāļēāļāļāļĒāđāļēāļāđāļĢ?
āļāļ§āļēāļĄāļŠāļēāļĄāļēāļĢāļāđāļāļāļēāļĢāļāļāļŠāļāļāđāļāđāļāļāđāļāđāļāđāđāļāļĢāļĩāļĒāļāļŠāļģāļāļąāļāļāļāļ NavigationStack āļāļēāļĢāđāļĒāļ router āļāļāļāļĄāļēāļāđāļ§āļĒāđāļŦāđ unit test āļŠāļēāļĄāļēāļĢāļāļāļĢāļ§āļāļŠāļāļāļāļĢāļĢāļāļ°āļāļēāļĢāļāļģāļāļēāļāđāļāđāđāļāļĒāđāļĄāđāļāđāļāļāđāļāđ UI
// Protocol to abstract the router
protocol NavigationRouterProtocol {
var pathCount: Int { get }
func navigateTo(_ destination: AppRoute)
func goBack()
func popToRoot()
}
// Concrete implementation
@Observable
class AppNavigationRouter: NavigationRouterProtocol {
var path = NavigationPath()
var pathCount: Int {
path.count
}
func navigateTo(_ destination: AppRoute) {
path.append(destination)
}
func goBack() {
guard !path.isEmpty else { return }
path.removeLast()
}
func popToRoot() {
path.removeLast(path.count)
}
}
// Unit tests
import XCTest
final class NavigationRouterTests: XCTestCase {
var router: AppNavigationRouter!
override func setUp() {
router = AppNavigationRouter()
}
func testNavigateToAddsToPath() {
// Given
XCTAssertEqual(router.pathCount, 0)
// When
router.navigateTo(.userDetail(userId: 42))
// Then
XCTAssertEqual(router.pathCount, 1)
}
func testGoBackRemovesLastItem() {
// Given
router.navigateTo(.userList)
router.navigateTo(.userDetail(userId: 1))
XCTAssertEqual(router.pathCount, 2)
// When
router.goBack()
// Then
XCTAssertEqual(router.pathCount, 1)
}
func testPopToRootClearsPath() {
// Given
router.navigateTo(.userList)
router.navigateTo(.userDetail(userId: 1))
router.navigateTo(.userEdit(userId: 1))
XCTAssertEqual(router.pathCount, 3)
// When
router.popToRoot()
// Then
XCTAssertEqual(router.pathCount, 0)
}
func testGoBackOnEmptyPathDoesNothing() {
// Given
XCTAssertEqual(router.pathCount, 0)
// When
router.goBack()
// Then
XCTAssertEqual(router.pathCount, 0) // No crash
}
}āļāļģāļāļēāļĄāļāļĩāđ 9: āļāļ°āļāļąāļāļāļēāļĢāļŠāļāļēāļāļ°āļāļēāļĢāļāļģāļāļēāļāļāļĩāđāļāļąāļāļāđāļāļāđāļĄāļ·āđāļāļĄāļĩāļŦāļĨāļēāļĒāđāļāđāļāļāļĒāđāļēāļāđāļĢ?
āđāļāļāļāļĨāļīāđāļāļāļąāļāļāļĩāđāļĄāļĩ TabView āļāđāļāļāļāļēāļĢāļŠāļāļēāļāļ°āļāļēāļĢāļāļģāļāļēāļāļŠāļģāļŦāļĢāļąāļāđāļāđāļĨāļ°āđāļāđāļ āđāļāđāļĨāļ°āđāļāđāļāļāļ°āļāļāļŠāđāļāđāļāļāļĩāđāđāļāđāļāļāļīāļŠāļĢāļ°āļāļāļāļāļąāļ§āđāļāļāđāļ§āđ
// Navigation state for each tab
@Observable
class TabNavigationState {
var homePath = NavigationPath()
var searchPath = NavigationPath()
var profilePath = NavigationPath()
var selectedTab: Tab = .home
enum Tab: Hashable {
case home, search, profile
}
func resetCurrentTab() {
switch selectedTab {
case .home:
homePath.removeLast(homePath.count)
case .search:
searchPath.removeLast(searchPath.count)
case .profile:
profilePath.removeLast(profilePath.count)
}
}
func resetAllTabs() {
homePath.removeLast(homePath.count)
searchPath.removeLast(searchPath.count)
profilePath.removeLast(profilePath.count)
}
}
struct TabRootView: View {
@State private var tabState = TabNavigationState()
var body: some View {
TabView(selection: $tabState.selectedTab) {
// Home tab
NavigationStack(path: $tabState.homePath) {
HomeView()
.navigationDestination(for: HomeRoute.self) { route in
HomeRouteView(route: route)
}
}
.tabItem { Label("Home", systemImage: "house") }
.tag(TabNavigationState.Tab.home)
// Search tab
NavigationStack(path: $tabState.searchPath) {
SearchView()
.navigationDestination(for: SearchRoute.self) { route in
SearchRouteView(route: route)
}
}
.tabItem { Label("Search", systemImage: "magnifyingglass") }
.tag(TabNavigationState.Tab.search)
// Profile tab
NavigationStack(path: $tabState.profilePath) {
ProfileView()
.navigationDestination(for: ProfileRoute.self) { route in
ProfileRouteView(route: route)
}
}
.tabItem { Label("Profile", systemImage: "person") }
.tag(TabNavigationState.Tab.profile)
}
.environment(tabState)
}
}āļāļģāļāļēāļĄāļāļĩāđ 10: āļāļ°āļŦāļĨāļĩāļāđāļĨāļĩāđāļĒāļāļāļąāļāļŦāļēāļāļĢāļ°āļŠāļīāļāļāļīāļ āļēāļāļāļāļ NavigationStack āļāļĒāđāļēāļāđāļĢ?
āļŠāđāļāđāļāļāļēāļĢāļāļģāļāļēāļāļāļāļēāļāđāļŦāļāđāļŦāļĢāļ·āļāļāļĨāļēāļĒāļāļēāļāļāļĩāđāļāļąāļāļāđāļāļāļāļēāļāļŠāđāļāļāļĨāļāđāļāļāļĢāļ°āļŠāļīāļāļāļīāļ āļēāļ āļĄāļĩāļŦāļĨāļēāļĒāđāļāļāļāļīāļāļāļĩāđāļāđāļ§āļĒāļāļĢāļąāļāļāļēāļĢāđāļĢāļāđāļāļāļĢāđāđāļĨāļ°āļāļēāļĢāđāļāđāļŦāļāđāļ§āļĒāļāļ§āļēāļĄāļāļģāđāļŦāđāđāļŦāļĄāļēāļ°āļŠāļĄ
struct OptimizedNavigationStack: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
LazyContentView()
// â
Lazy-loaded destinations
.navigationDestination(for: HeavyRoute.self) { route in
// View only created on navigation
HeavyDetailView(route: route)
}
}
}
}
// â
View with lazy content loading
struct LazyContentView: View {
@State private var items: [Item] = []
var body: some View {
// LazyVStack only creates visible views
ScrollView {
LazyVStack(spacing: 12) {
ForEach(items) { item in
NavigationLink(value: HeavyRoute.detail(item.id)) {
ItemRow(item: item)
}
}
}
}
.task {
items = await loadItems()
}
}
}
// â
Detail with progressive loading
struct HeavyDetailView: View {
let route: HeavyRoute
@State private var data: DetailData?
var body: some View {
Group {
if let data {
DetailContent(data: data)
} else {
ProgressView()
}
}
.task {
// Load data only when view appears
data = await loadDetailData(for: route)
}
}
}
// â Avoid: eager creation of heavy views
struct BadNavigationStack: View {
let allItems: [Item]
var body: some View {
NavigationStack {
List(allItems) { item in
// Creates all destinations immediately
NavigationLink {
HeavyDetailView(item: item) // â Created upfront
} label: {
ItemRow(item: item)
}
}
}
}
}āļāļ§āļĢāđāļāđ navigationDestination(for:) āđāļāļ NavigationLink āļāļĩāđāļĄāļĩāļāļĨāļēāļĒāļāļēāļāđāļāļāļāļīāļāđāļĨāļāđāđāļŠāļĄāļ āļĢāļđāļāđāļāļāđāļĢāļāļāļ°āđāļŦāļĨāļāļ§āļīāļ§āļāļĨāļēāļĒāļāļēāļāđāļāļāļēāļ°āđāļĄāļ·āđāļāđāļāļīāļāļāļēāļĢāļāļģāļāļēāļāļāļĢāļīāļāđāļāđāļēāļāļąāđāļ
āļāļāļŠāļĢāļļāļ
NavigationStack āđāļāļĨāļĩāđāļĒāļāļ§āļīāļāļĩāļāļąāļāļāļēāļĢāļāļēāļĢāļāļģāļāļēāļāđāļ SwiftUI āļāđāļ§āļĒāļāļēāļĢāļĄāļāļāļāļēāļĢāļāļ§āļāļāļļāļĄāđāļāļīāļāđāļāļĢāđāļāļĢāļĄāļāļĩāđāļŠāļĄāļāļđāļĢāļāđ āļāļēāļĢāđāļāļĩāđāļĒāļ§āļāļēāļāļĢāļđāļāđāļāļāđāļŦāļĨāđāļēāļāļĩāđ āļāļąāđāļāđāļāđāļāļēāļĢāļāļģāļāļēāļāļāļ·āđāļāļāļēāļāđāļāļāļāļāļķāļ deep link āđāļĨāļ°āļāļēāļĢāđāļāđāļāļŠāļāļēāļāļ° āļāļģāđāļŦāđāļāļąāļāļāļąāļāļāļē iOS āļāļĩāđāļĄāļĩāļāļĢāļ°āļŠāļāļāļēāļĢāļāđāđāļāļāđāļāđāļāđāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ
āļĢāļēāļĒāļāļēāļĢāļāļĢāļ§āļāļŠāļāļ
- â
āđāļāđāļēāđāļāļāļ§āļēāļĄāđāļāļāļāđāļēāļāļĢāļ°āļŦāļ§āđāļēāļ
NavigationViewāđāļĨāļ°NavigationStack - â
āđāļāđ
NavigationPathāļŠāļģāļŦāļĢāļąāļāļāļēāļĢāļāļģāļāļēāļāđāļāļīāļāđāļāļĢāđāļāļĢāļĄāđāļāđāļ - â āļāļīāļāļāļąāđāļ router āđāļāļāļĢāļ§āļĄāļĻāļđāļāļĒāđāļŠāļģāļŦāļĢāļąāļāļāļēāļĢāļāļģāļāļēāļ
- â āļāļąāļāļāļēāļĢ deep link āļāļĢāđāļāļĄāļāļēāļĢāļŠāļĢāđāļēāļāļŠāđāļāđāļāļāļķāđāļāđāļŦāļĄāđ
- â
āđāļāđāļāđāļĨāļ°āļāļđāđāļāļ·āļāļŠāļāļēāļāļ°āļāļēāļĢāļāļģāļāļēāļāļāđāļ§āļĒ
Codable - â āđāļĒāļāđāļāļĨāļ§āđāļāļēāļĢāļĒāļ·āļāļĒāļąāļāļāļąāļ§āļāļāļāļāļāļāļēāļāļāļēāļĢāļāļģāļāļēāļāļŦāļĨāļąāļ
- â
āđāļāđāđāļĄāļāļāļĨāļĢāđāļ§āļĄāļāļąāļ
NavigationStackāđāļāđāļāļĒāđāļēāļāļāļđāļāļāđāļāļ - â āđāļāļĩāļĒāļ unit test āļŠāļģāļŦāļĢāļąāļāļāļĢāļĢāļāļ°āļāļēāļĢāļāļģāļāļēāļ
- â
āļāļąāļāļāļēāļĢāļāļēāļĢāļāļģāļāļēāļāđāļāļāļŦāļĨāļēāļĒāđāļāđāļāļāđāļ§āļĒ
TabView - â āļāļĢāļąāļāļāļĢāļ°āļŠāļīāļāļāļīāļ āļēāļāļŠāļģāļŦāļĢāļąāļāļŠāđāļāđāļāļāļēāļĢāļāļģāļāļēāļāļāļāļēāļāđāļŦāļāđ
āđāļĢāļīāđāļĄāļāļķāļāļāđāļāļĄāđāļĨāļĒ!
āļāļāļŠāļāļāļāļ§āļēāļĄāļĢāļđāđāļāļāļāļāļļāļāļāđāļ§āļĒāļāļąāļ§āļāļģāļĨāļāļāļŠāļąāļĄāļ āļēāļĐāļāđāđāļĨāļ°āđāļāļāļāļāļŠāļāļāđāļāļāļāļīāļāļāļĢāļąāļ
āđāļāđāļ
āđāļāļĢāđ
āļāļāļāļ§āļēāļĄāļāļĩāđāđāļāļĩāđāļĒāļ§āļāđāļāļ

āļŠāļąāļĄāļ āļēāļĐāļāđ MapKit SwiftUI āđāļāļāļĩ 2026: āļāļģāļāļāļīāļāļēāļĒāļāļĢāļ°āļāļāļ āļāļēāļĢāļāđāļāļāļāļąāļ āđāļĨāļ°āļāļēāļĢāļĢāļ°āļāļļāļāļģāđāļŦāļāđāļ
āđāļāļĩāđāļĒāļ§āļāļēāļ MapKit āļāđāļ§āļĒ SwiftUI āļŠāļģāļŦāļĢāļąāļāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ iOS: āļāļģāļāļāļīāļāļēāļĒāļāļĢāļ°āļāļāļāđāļāļāļāļģāļŦāļāļāđāļāļ āļāļēāļĢāļāđāļāļāļāļąāļ āļāļēāļĢāļĢāļ°āļāļļāļāļģāđāļŦāļāđāļ āļāļēāļĢāļāđāļāļŦāļēāļŠāļāļēāļāļāļĩāđ āđāļĨāļ°āļĢāļđāļāđāļāļāļāļēāļĢāļāļŠāļēāļāļĢāļ§āļĄāļāļąāļ Maps

āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļāđ StoreKit 2: āļāļēāļĢāļāļąāļāļāļēāļĢāļāļēāļĢāļŠāļĄāļąāļāļĢāļŠāļĄāļēāļāļīāļāđāļĨāļ°āļāļēāļĢāļāļĢāļ§āļāļŠāļāļāđāļāđāļŠāļĢāđāļ
āđāļāļĩāđāļĒāļ§āļāļēāļāļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ iOS āđāļāļĩāđāļĒāļ§āļāļąāļ StoreKit 2 āļāļēāļĢāļāļąāļāļāļēāļĢāļāļēāļĢāļŠāļĄāļąāļāļĢāļŠāļĄāļēāļāļīāļ āļāļēāļĢāļāļĢāļ§āļāļŠāļāļāđāļāđāļŠāļĢāđāļ āđāļĨāļ°āļāļēāļĢāļāļģāļāļēāļĢāļāļ·āđāļāđāļāđāļāļāđāļāđāļāđ āļāļĢāđāļāļĄāļāļąāļ§āļāļĒāđāļēāļāđāļāđāļ Swift āļāļĩāđāđāļāđāļāļēāļāđāļāđāļāļĢāļīāļ

Vision Framework āđāļĨāļ° CoreML: āļāļģāļāļēāļĄāļŠāļąāļĄāļ āļēāļĐāļāđ iOS āđāļĢāļ·āđāļāļ ML āļāļāļāļļāļāļāļĢāļāđ
āđāļāļĢāļĩāļĒāļĄāļāļąāļ§āļŠāļąāļĄāļ āļēāļĐāļāđ iOS āļāđāļ§āļĒāļāļģāļāļēāļĄāļŠāļģāļāļąāļāđāļāļĩāđāļĒāļ§āļāļąāļ Vision Framework āđāļĨāļ° CoreML: āļāļēāļĢāļĢāļđāđāļāļģāļ āļēāļ āļāļēāļĢāļāļĢāļ§āļāļāļąāļāļ§āļąāļāļāļļ āđāļĨāļ° ML āļāļāļāļļāļāļāļĢāļāđ