В SwiftUI приложении с множеством переходов, можно ли использовать один NavigationLink?

Ответ

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

Один NavigationLink с параметрами tag и selection оправдан только для динамической навигации, где пункты назначения определяются во время выполнения:

enum AppRoute { case profile, settings, detail(id: UUID) }

struct ContentView: View {
    @State private var activeRoute: AppRoute?

    var body: some View {
        NavigationView {
            VStack {
                Button("Open Profile") { activeRoute = .profile }
                Button("Open Settings") { activeRoute = .settings }
            }
            .background(
                NavigationLink(
                    destination: destinationView(for: activeRoute),
                    tag: activeRoute,
                    selection: $activeRoute,
                    label: { EmptyView() }
                )
            )
        }
    }

    @ViewBuilder
    private func destinationView(for route: AppRoute?) -> some View {
        switch route {
        case .profile: ProfileView()
        case .settings: SettingsView()
        case .detail(let id): DetailView(id: id)
        case nil: EmptyView()
        }
    }
}

Современный подход (iOS 16+): Используйте NavigationStack с модификатором navigationDestination, который декларативно связывает значение с представлением:

NavigationStack(path: $navigationPath) {
    RootView()
        .navigationDestination(for: AppRoute.self) { route in
            switch route {
                case .profile: ProfileView()
                case .settings: SettingsView()
            }
        }
}

Ответ 18+ 🔞

Смотри, тут такая история, как с этим твоим NavigationLink. Технически-то, блядь, можно, конечно. Но это как ебать кота за хвост — вроде и получается, но всем хуёво и непонятно зачем.

Вот представь: ты пишешь статическую навигацию, где всё известно заранее. И вместо того, чтобы просто на каждый пункт меню нацепить свой NavigationLink, ты начинаешь этот цирк с tag и selection. Зачем, сука? Код превращается в такую кашу, что через неделю сам себя ебнешь, пытаясь вспомнить, какой case за какой экран отвечает. Читаемость — в пизду, поддержка — ноль ебать. Просто не надо так.

Этот фокус с одним линком оправдан только в одном случае — когда у тебя динамическая навигация. То есть ты заранее не знаешь, куда тебя пошлют. Например, по глубокой ссылке или после какого-то асинхронного хуяк-хуяк.

enum AppRoute { case profile, settings, detail(id: UUID) }

struct ContentView: View {
    @State private var activeRoute: AppRoute?

    var body: some View {
        NavigationView {
            VStack {
                Button("Open Profile") { activeRoute = .profile }
                Button("Open Settings") { activeRoute = .settings }
            }
            .background(
                NavigationLink(
                    destination: destinationView(for: activeRoute),
                    tag: activeRoute,
                    selection: $activeRoute,
                    label: { EmptyView() }
                )
            )
        }
    }

    @ViewBuilder
    private func destinationView(for route: AppRoute?) -> some View {
        switch route {
        case .profile: ProfileView()
        case .settings: SettingsView()
        case .detail(let id): DetailView(id: id)
        case nil: EmptyView()
        }
    }
}

Видишь? Тут логика есть. Мы не знаем, какой route прилетит, поэтому один универсальный линк — самое то.

Но если ты на iOS 16+, то вообще забудь эту старую хуйню. Там есть NavigationStack, и это просто ёперный театр, как удобно!

NavigationStack(path: $navigationPath) {
    RootView()
        .navigationDestination(for: AppRoute.self) { route in
            switch route {
                case .profile: ProfileView()
                case .settings: SettingsView()
            }
        }
}

Вот это, блядь, красота! Всё декларативно, всё на своих местах. Никаких костылей в виде EmptyView() и скрытых линков. Apple, сука, наконец-то додумалась.

Короче, резюме: не умничай просто так. Для статики — отдельные линки, для динамики — можно один с selection, а для нового стека — ваще песня. Выбирай по обстановке, а не потому что "а я так умею".