Как интегрировать навигацию на основе UIKit в SwiftUI-приложение?

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

Ответ

Для интеграции UIKit-навигации (например, UINavigationController) в SwiftUI используется протокол UIViewControllerRepresentable.

Основной подход: Обертка для UINavigationController Создайте обертку, которая внедряет UINavigationController как корневой контроллер.

import SwiftUI
import UIKit

struct UIKitNavigationWrapper: UIViewControllerRepresentable {
    // 1. Создаем UINavigationController с SwiftUI View внутри
    func makeUIViewController(context: Context) -> UINavigationController {
        let rootSwiftUIView = ContentView() // Ваш корневой SwiftUI View
        let hostingController = UIHostingController(rootView: rootSwiftUIView)

        let navigationController = UINavigationController(rootViewController: hostingController)
        navigationController.navigationBar.prefersLargeTitles = true
        return navigationController
    }

    // 2. Метод обновления (часто пустой)
    func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
        // Обновляем при необходимости
    }
}

// 3. Использование в App
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            UIKitNavigationWrapper() // Заменяет обычный ContentView()
        }
    }
}

Как выполнить переход (push) из SwiftUI View: Чтобы управлять навигацией, вам нужен доступ к UINavigationController. Это можно сделать через UIApplication.shared или, лучше, через Coordinator в UIViewControllerRepresentable.

// Пример кнопки внутри SwiftUI View, которая выполняет push
Button("Show Details") {
    // 1. Находим активный UINavigationController
    guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
          let rootViewController = windowScene.windows.first?.rootViewController as? UINavigationController else {
        return
    }

    // 2. Создаем новый SwiftUI View и оборачиваем его для UIKit
    let detailView = DetailView()
    let hostingController = UIHostingController(rootView: detailView)

    // 3. Выполняем стандартный UIKit push
    rootViewController.pushViewController(hostingController, animated: true)
}

Ключевые компоненты:

  • UIViewControllerRepresentable: Мост для интеграции UIViewController в SwiftUI.
  • UIHostingController: Контроллер UIKit, который содержит и управляет SwiftUI View.
  • Доступ к корневому контроллеру: Через UIApplication.shared.connectedScenes получаем доступ к навигационному стеку.

Важно: Такой гибридный подход усложняет архитектуру. Используйте его только при необходимости (например, для интеграции существующих UIKit-экранов). Для чистых SwiftUI-проектов предпочтительна нативная навигация (NavigationStack/NavigationLink).