Получить топ самых UIViewController

192

Кажется, я не могу получить самый верх UIViewControllerбез доступа к UINavigationController. Вот что у меня так далеко:

UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(vc, animated: true, completion: nil)

Однако, похоже, ничего не делает. keyWindowИ , как rootViewControllerпредставляется, не-нильполугруппа значения тоже, так что дополнительное сцепление не должно быть проблемой.

ПРИМЕЧАНИЕ. Плохо делать что-то подобное. Это нарушает схему MVC.

Zoyt
источник
Вот одно из доступных альтернативных решений stackoverflow.com/a/39994115/1872233
iDevAmit

Ответы:

287

presentViewControllerпоказывает контроллер вида. Это не возвращает контроллер представления. Если вы не используете UINavigationController, вы, вероятно, ищете, presentedViewControllerи вам нужно начать с корня и пройти вниз по представленным представлениям.

if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController should now be your topmost view controller
}

Для Swift 3+:

if var topController = UIApplication.shared.keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController should now be your topmost view controller
}

Для iOS 13+

let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

if var topController = keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

// topController should now be your topmost view controller
}
rickerbh
источник
1
Может кто-нибудь объяснить цикл while? Для меня это выглядит так, будто нечего зацикливаться; Я даже не уверен, почему это компилируется.
Профессор Том
15
@Prof ProfessorTom Цикл продолжается до тех пор, пока topController.presentedViewControllerчто-то возвращает (т. Е. Контроллер имеет представленный дочерний контроллер). Это для того, while letчтобы усилить тот факт, что topController.presentedViewControllerдолжен что-то вернуть Если он возвращает ноль (то есть, это контроллер не имеет представленных потомков), то он прекращает цикл. В теле цикла он переназначает дочерний элемент как текущий topControllerи повторяет цикл, спускаясь вниз по иерархии контроллера представления. Он может быть переназначен topControllerкак varво внешнем ifутверждении.
rickerbh
1
Спасибо. Я не смог найти ни одного примера в Интернете while let. Есть, конечно, много if letпримеров, которые можно найти.
Профессор Том
1
let x = somethingThatCouldBeNilСинтаксис супер удобный трюк , чтобы использовать везде , где значение истины / условие может быть использовано. Если бы мы не использовали его здесь, нам пришлось бы явно назначить значение, а затем проверить, есть ли оно на самом деле. Я думаю, что это действительно лаконично и выразительно.
rickerbh
1
Я знаком с этим трюком, просто немного сложнее рассуждать в циклах while, для которых я обнаружил недостаток примеров, особенно в этом.
Профессор Том
272

есть это расширение

Swift 2. *

extension UIApplication {
    class func topViewController(controller: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(presented)
        }
        return controller
    }
}

Swift 3

extension UIApplication {
    class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(controller: navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(controller: selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(controller: presented)
        }
        return controller
    }
}

Вы можете использовать это в любом месте на вашем контроллере

if let topController = UIApplication.topViewController() {

}
DLende
источник
1
Спасибо за ваш совет по расширению :)
Thein
4
Я попытался внести важные изменения в этот ответ, но он был отклонен (я понятия не имею, почему, и причины, приведенные в шаблоне, не имеют смысла): важно проверить, является ли nav.visibleViewController нулевым, прежде чем использовать его в рекурсивном вызов (точно так же, как проверяется tab.selectedViewController), потому что в противном случае, если бы он был нулем, вы попали бы в рекурсивный бесконечный цикл.
Итан Г
@EthanG Насколько я понимаю, если nav.visibleViewController равен nil, функция вернет nil (опустится до последнего return). Как это может войти в бесконечный цикл?
Десмонд DAI
3
Я думаю, что было бы более логичным сделать это статической функцией UIViewController
Лешек Зарна
1
Проверка «представленный контроль» должна, вероятно, идти первой, если вы хотите поймать модально представленные контроллеры представления на UITabBarControllers ..
Tokuriku
65

Для быстрого 4/5 +, чтобы получить самый верхний viewController

// MARK: UIApplication extensions

extension UIApplication {

    class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return getTopViewController(base: nav.visibleViewController)

        } else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
            return getTopViewController(base: selected)

        } else if let presented = base?.presentedViewController {
            return getTopViewController(base: presented)
        }
        return base
    }
}

Как пользоваться

if let topVC = UIApplication.getTopViewController() {
   topVC.view.addSubview(forgotPwdView)
}
Хардик Таккар
источник
2
Гениальное решение. Спасибо!
Андрей М.
2
'keyWindow' устарел в iOS 13.0.
RS7
2
«keyWindow» устарело в iOS 13.0 stackoverflow.com/a/57899013/4514671
Rebeloper
19
extension UIWindow {

    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }

    static func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
        if let navigationController = vc as? UINavigationController,
            let visibleController = navigationController.visibleViewController  {
            return UIWindow.getVisibleViewControllerFrom( vc: visibleController )
        } else if let tabBarController = vc as? UITabBarController,
            let selectedTabController = tabBarController.selectedViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: selectedTabController )
        } else {
            if let presentedViewController = vc.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)
            } else {
                return vc
            }
        }
    }
}

Использование:

if let topController = window.visibleViewController() {
    println(topController)
}
BOBJ-С
источник
это решение выглядело действительно многообещающим, однако я попытался запустить его, чтобы получить контроллер представления, на котором я работаю, когда я получаю push-уведомление, и оно выдало нулевую ошибку наreturn UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
Mike
@ Майк, вам нужно использовать только представленный ViewController, а не представленный ViewController. представил
ViewController
@allaire Если вы представляли контроллер модального представления поверх контроллера модального просмотра, то вам нужно .presentedViewController.presentedViewController, или нет?
Баран Эмре
6

Основываясь на ответе Dianz, версия Objective-C

- (UIViewController *) topViewController {
   UIViewController *baseVC = UIApplication.sharedApplication.keyWindow.rootViewController;
   if ([baseVC isKindOfClass:[UINavigationController class]]) {
       return ((UINavigationController *)baseVC).visibleViewController;
   }

   if ([baseVC isKindOfClass:[UITabBarController class]]) {
       UIViewController *selectedTVC = ((UITabBarController*)baseVC).selectedViewController;
       if (selectedTVC) {
           return selectedTVC;
       }
   }

   if (baseVC.presentedViewController) {
       return baseVC.presentedViewController;
   }
   return baseVC;
}
Тибидабо
источник
Не будет работать для UINavigationController в UITabBarController. он вернет UINavigationController, должен вернуть topController в застрявшей навигации.
Mike.R
Tnx Tnx Tnx Bro
reza_khalafi
6

Мне понравился ответ @ dianz , и вот его версия Swift 3. Это в основном то же самое, но ему не хватало фигурной скобки, и некоторые имена синтаксиса / переменных / методов изменились. Так что вот оно!

extension UIApplication {
    class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }
        if let tab = base as? UITabBarController {
            if let selected = tab.selectedViewController {
                return topViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }
        return base
    }
}

Использование остается прежним, хотя:

if let topController = UIApplication.topViewController() {
    print("The view controller you're looking at is: \(topController)")
}
Ponyboy47
источник
6

https://gist.github.com/db0company/369bfa43cb84b145dfd8 Я провел несколько тестов на ответы и комментарии на этом сайте. Для меня следующие работы

extension UIViewController {
    func topMostViewController() -> UIViewController {

        if let presented = self.presentedViewController {
            return presented.topMostViewController()
        }

        if let navigation = self as? UINavigationController {
            return navigation.visibleViewController?.topMostViewController() ?? navigation
        }

        if let tab = self as? UITabBarController {
            return tab.selectedViewController?.topMostViewController() ?? tab
    }

        return self
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return self.keyWindow?.rootViewController?.topMostViewController()
    }
}

Затем получите верхний viewController по:

UIApplication.shared.topMostViewController()
Альберт Зоу
источник
5

Используйте этот код, чтобы найти самый верхний UIViewController

func getTopViewController() -> UIViewController? {
    var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    while topController?.presentedViewController != nil {
        topController = topController?.presentedViewController
    }
    return topController
}
Бибин Джозеф
источник
2
Чем это отличается от ответа Риккерба?
ElectroBuddha
5

Небольшое изменение @AlberZou с использованием вычисляемой переменной, а не функции

extension UIViewController {
  var topMostViewController : UIViewController {

    if let presented = self.presentedViewController {
      return presented.topMostViewController
    }

    if let navigation = self as? UINavigationController {
      return navigation.visibleViewController?.topMostViewController ?? navigation
    }

    if let tab = self as? UITabBarController {
      return tab.selectedViewController?.topMostViewController ?? tab
    }

    return self
  }
}

extension UIApplication {
  var topMostViewController : UIViewController? {
    return self.keyWindow?.rootViewController?.topMostViewController
  }
}

Тогда скажи

if let topViewControler = UIApplication.shared.topMostViewController {
    ... do stuff
}
Райан Хайтнер
источник
4

На основании Боба -c выше:

Swift 3.0

extension UIWindow {


    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController  = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }

    class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {

        if vc.isKind(of: UINavigationController.self) {

            let navigationController = vc as! UINavigationController
            return UIWindow.getVisibleViewControllerFrom( vc: navigationController.visibleViewController!)

        } else if vc.isKind(of: UITabBarController.self) {

            let tabBarController = vc as! UITabBarController
            return UIWindow.getVisibleViewControllerFrom(vc: tabBarController.selectedViewController!)

        } else {

            if let presentedViewController = vc.presentedViewController {

                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)

            } else {

                return vc;
            }
        }
    }
}
Даниил
источник
4

Слишком много ароматов, но нет итеративно разработанного. В сочетании с предыдущими:

     func topMostController() -> UIViewController? {
        var from = UIApplication.shared.keyWindow?.rootViewController
        while (from != nil) {
            if let to = (from as? UITabBarController)?.selectedViewController {
                from = to
            } else if let to = (from as? UINavigationController)?.visibleViewController {
                from = to
            } else if let to = from?.presentedViewController {
                from = to
            } else {
                break
            }
        }
        return from
    }
Хайме Агудо
источник
2

Вы можете определить переменную UIViewController в AppDelegate, и в каждом viewWillAppear установите для переменной значение self (однако dianz-ответ - лучший ответ).

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
    appDel.currentVC = self
}
Араш Джамшиди
источник
1
Большое спасибо, он отлично работает для меня, как другое решение, когда он пытается получить навигацию. Контролируйте его, возвращайте ноль, так что я не смог нажать на любой новый vc
Amr Angry
Убедитесь, что currentVC определен как слабая ссылка, иначе у вас будет утечка памяти.
Bubuxu
2

Чтобы найти видимый viewController в Swift 3

if let viewControllers = window?.rootViewController?.childViewControllers {

     let prefs = UserDefaults.standard

     if viewControllers[viewControllers.count - 1] is ABCController{
        print("[ABCController] is visible")

     }
}

Этот код находит последний добавленный или последний видимый активный контроллер.

Это я использовал в AppDelegate, чтобы найти активный контроллер представления

Prateekro
источник
2
import UIKit

extension UIApplication {

    // MARK: Choose keyWindow as per your choice
    var currentWindow: UIWindow? {
        connectedScenes
        .filter({$0.activationState == .foregroundActive})
        .map({$0 as? UIWindowScene})
        .compactMap({$0})
        .first?.windows
        .filter({$0.isKeyWindow}).first
    }

    // MARK: Choose keyWindow as per your choice
    var keyWindow: UIWindow? {
        UIApplication.shared.windows.first { $0.isKeyWindow }
    }

    class func topMostViewController(base: UIViewController? = UIApplication.shared.currentWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return topMostViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            let moreNavigationController = tab.moreNavigationController

            if let top = moreNavigationController.topViewController, top.view.window != nil {
                return topMostViewController(base: top)
            } else if let selected = tab.selectedViewController {
                return topMostViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topMostViewController(base: presented)
        }
        return base
    }
}
Ракеш Кусума
источник
Неоднозначное использование 'visibleViewController'
Омар Н Шамали
1

Где вы положили код?

Я попробую ваш код в моей демонстрации, я узнал, если вы поместите код в

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 

потерпит неудачу, потому что окно ключа было установлено еще.

Но я поместил ваш код в некоторые контроллеры представления

override func viewDidLoad() {

Это просто работает.

Tinyfool
источник
Это не в didFinishLaunchingWithOptions. Мне просто нужно это для различных целей отладки.
Зойт
1

В очень редком случае, с пользовательским переходом, самый верхний контроллер представления не находится в стеке навигации или контроллере панели вкладок или не представлен, но его представление вставляется в верхнюю часть подпредставлений ключевых ветров.

В такой ситуации необходимо проверить, UIApplication.shared.keyWindow.subviews.last == self.viewчтобы определить, является ли текущий контроллер представления самым верхним.

BabyPanda
источник
1

Для тех, кто ищет быстрое решение 5 / iOS 13+ ( keywindowустарело с iOS 13)

extension UIApplication {

    class func getTopMostViewController() -> UIViewController? {
        let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
        if var topController = keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            return topController
        } else {
            return nil
        }
    }
}
Virendra
источник
Как бы я использовал это?
Крис Комас
Просто назови это так. UIApplication.getTopMostViewController()внутри вашего ViewController. @ChrisComas
Virendra
0
  var topViewController: UIViewController? {
        guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }
        return topViewController
    }
логово
источник
0

Лучшее решение для меня - это расширение с функцией. Создайте файл swift с этим расширением

Во-первых, это расширение UIWindow :

public extension UIWindow {
    var visibleViewController: UIViewController? {
        return UIWindow.visibleVC(vc: self.rootViewController)
    }

    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

внутри этого файла добавить функцию

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}

И если вы хотите использовать его, вы можете позвонить куда угодно. Пример :

  override func viewDidLoad() {
    super.viewDidLoad()
      if let topVC = visibleViewController() {
             //show some label or text field 
    }
}

Код файла выглядит так :

import UIKit

public extension UIWindow {
    var visibleViewController: UIViewController? {
        return UIWindow.visibleVC(vc: self.rootViewController)
    }

    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}
tBug
источник