Как реализовать навигацию в SwiftUI без использования @ObservedObject во View?

«Как реализовать навигацию в SwiftUI без использования @ObservedObject во View?» — вопрос из категории SwiftUI, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, это возможно и часто рекомендуется для уменьшения ненужных перерисовок View. Вот основные подходы:

1. Использование @EnvironmentObject Инжектируйте общий роутер во все дочерние View через окружение.

class Router: ObservableObject {
    @Published var path = NavigationPath()
}

// В корне приложения
ContentView()
    .environmentObject(Router())

// В любой дочерней View
struct DetailView: View {
    @EnvironmentObject var router: Router

    var body: some View {
        Button("Back") {
            router.path.removeLast()
        }
    }
}

2. Передача Binding Для простых модальных окон или переключений.

struct ParentView: View {
    @State private var showSheet = false

    var body: some View {
        ChildView(isPresented: $showSheet)
    }
}

struct ChildView: View {
    @Binding var isPresented: Bool

    var body: some View {
        Button("Close") { isPresented = false }
    }
}

3. Координатор или Router-паттерн Навигация выносится в отдельный класс, который инжектируется через инициализатор.

protocol AppRouter {
    func navigate(to destination: Destination)
}

struct ContentView: View {
    let router: AppRouter

    var body: some View {
        Button("Go to Details") {
            router.navigate(to: .detail(id: 1))
        }
    }
}

Преимущества:

  • Меньше перерисовок — View не подписывается на изменения через @ObservedObject.
  • Лучшая инкапсуляция — состояние навигации управляется централизованно.
  • Проще тестирование — роутер можно легко подменить.