← Назад к вопросам

Как получить текущий ViewController?

1.3 Junior🔥 191 комментариев
#UIKit и верстка

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Получение текущего UIViewController в iOS

Получение текущего (видимого на экране) UIViewController — частая задача в iOS-разработке, необходимая для:

  • Отображения новых контроллеров (алерты, уведомления)
  • Передачи данных между контроллерами
  • Выполнения навигационных операций
  • Получения контекста для логирования или аналитики

Основные подходы

1. Рекурсивный поиск через UIWindow (наиболее универсальный)

extension UIApplication {
    static func getCurrentViewController(
        base: UIViewController? = UIApplication.shared.windows.first?.rootViewController
    ) -> UIViewController? {
        
        if let nav = base as? UINavigationController {
            return getCurrentViewController(base: nav.visibleViewController)
        }
        
        if let tab = base as? UITabBarController {
            return getCurrentViewController(base: tab.selectedViewController)
        }
        
        if let presented = base?.presentedViewController {
            return getCurrentViewController(base: presented)
        }
        
        return base
    }
}

// Использование
if let currentVC = UIApplication.getCurrentViewController() {
    print("Текущий контроллер: \(currentVC)")
}

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

  • Работает с любыми контейнерами (UINavigationController, UITabBarController)
  • Учитывает модально представленные контроллеры
  • Рекурсивно проходит по всей иерархии

2. Через keyWindow (для iOS 13+)

extension UIViewController {
    static var current: UIViewController? {
        guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
              let window = windowScene.windows.first(where: { $0.isKeyWindow }),
              let rootViewController = window.rootViewController else {
            return nil
        }
        
        return findCurrentViewController(from: rootViewController)
    }
    
    private static func findCurrentViewController(from controller: UIViewController) -> UIViewController {
        if let nav = controller as? UINavigationController {
            return findCurrentViewController(from: nav.visibleViewController ?? nav)
        }
        
        if let tab = controller as? UITabBarController {
            return findCurrentViewController(from: tab.selectedViewController ?? tab)
        }
        
        if let presented = controller.presentedViewController {
            return findCurrentViewController(from: presented)
        }
        
        return controller
    }
}

3. Для конкретных сценариев

Внутри самого UIViewController:

class MyViewController: UIViewController {
    func doSomething() {
        // Текущий контроллер — это self
        presentAlert(from: self)
    }
}

Для получения из AppDelegate (устаревший способ):

// Не рекомендуется для новых проектов
let currentVC = (UIApplication.shared.delegate as? AppDelegate)?.window?.rootViewController

Важные нюансы и предостережения

Безопасность и надежность

  • Всегда проверяйте опциональные значения (guard, if let)
  • Учитывайте возможное отсутствие keyWindow в определенных состояниях приложения
  • В многооконных сценариях (iPadOS, macOS Catalyst) определяйте нужное окно

Архитектурные соображения

  • Избегайте злоупотребления глобальным поиском контроллера — это может указывать на проблемы архитектуры
  • Рассмотрите передачу контроллера через делегаты, замыкания или роутеры
  • В MVVM или VIPER используйте координаторы для управления навигацией

SwiftUI и современные подходы

В SwiftUI концепция UIViewController менее актуальна, но при необходимости:

struct ContentView: View {
    var body: some View {
        Text("Hello")
            .onAppear {
                // Получение UIViewController для SwiftUI view
                if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
                   let rootVC = windowScene.windows.first?.rootViewController {
                    // Работа с rootVC
                }
            }
    }
}

Рекомендации по использованию

  1. Создайте расширение (extension) для переиспользования кода
  2. Добавьте логирование для отладки в сложных навигационных схемах
  3. Протестируйте все возможные состояния:
    • Модальные презентации
    • Вложенные навигационные контроллеры
    • Split-view контроллеры (iPad)
    • Переходы между табами

Пример полного решения

import UIKit

final class ViewControllerHelper {
    
    static let shared = ViewControllerHelper()
    
    private init() {}
    
    func currentViewController() -> UIViewController? {
        let scenes = UIApplication.shared.connectedScenes
        let windowScene = scenes.first as? UIWindowScene
        let window = windowScene?.windows.first(where: { $0.isKeyWindow })
        
        guard let rootViewController = window?.rootViewController else {
            return nil
        }
        
        return traverseViewControllerHierarchy(from: rootViewController)
    }
    
    private func traverseViewControllerHierarchy(from controller: UIViewController) -> UIViewController {
        // Обработка контейнерных контроллеров
        switch controller {
        case let nav as UINavigationController:
            return traverseViewControllerHierarchy(from: nav.visibleViewController ?? nav)
            
        case let tab as UITabBarController:
            return traverseViewControllerHierarchy(from: tab.selectedViewController ?? tab)
            
        case let split as UISplitViewController:
            return traverseViewControllerHierarchy(from: split.viewControllers.last ?? split)
            
        default:
            // Проверка на модально представленные контроллеры
            if let presented = controller.presentedViewController {
                return traverseViewControllerHierarchy(from: presented)
            }
            
            return controller
        }
    }
}

// Использование
if let current = ViewControllerHelper.shared.currentViewController() {
    let alert = UIAlertController(title: "Текущий контроллер", 
                                  message: String(describing: type(of: current)), 
                                  preferredStyle: .alert)
    current.present(alert, animated: true)
}

Ключевой вывод: Получение текущего UIViewController требует аккуратного рекурсивного обхода иерархии с учетом всех типов контейнерных контроллеров и модальных презентаций. Рекомендуется создать надежное расширение или сервис, который будет корректно работать во всех состояниях приложения.