Отримайте найкращий топ UIViewController


192

Я, здається, не можу отримати найвищий топ UIViewControllerбез доступу до UINavigationController. Ось що я маю досі:

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

Однак, здається, це нічого не робить. Значення keyWindowі, rootViewControllerздається, є ненульовими значеннями, тому необов'язкове ланцюжок не повинно бути проблемою.

ПРИМІТКА. Дуже погано робити щось подібне. Він порушує схему MVC.


Ось один альтернативне рішення є stackoverflow.com/a/39994115/1872233
iDevAmit

Відповіді:


287

presentViewControllerпоказує контролер перегляду. Він не повертає контролер перегляду. Якщо ви не використовуєте a 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
}

1
Може хтось пояснить цикл while? Мені здається, що немає чого зациклюватися; Я навіть не впевнений, чому це складається.
Професор Том

15
@ProfessorTom Цикл продовжується до тих пір, поки topController.presentedViewControllerщось поверне (тобто контролер має представлений дочірний контролер). Це while letнав'язати факт, який topController.presentedViewControllerповинен щось повернути. Якщо він поверне нуль (тобто у цього контролера немає представлених дітей), він перестане циклічно. У тілі циклу він переназначає дитину як струм topControllerі знову циклічно , спускаючись вниз по ієрархії контролера перегляду. Він може перепризначити, topControllerяк це є varу зовнішньому ifтвердженні.
rickerbh

1
спасибі. Я не зміг знайти жодного прикладу в Інтернеті while let. Звичайно, є багато if letприкладів.
професор Том

1
let x = somethingThatCouldBeNilСинтаксис супер зручний трюк , щоб використовувати всюди , де значення істини / умова може бути використано. Якщо ми не використали його тут, нам доведеться явно призначити значення, а потім перевірити, чи існує воно насправді. Я думаю, що це дійсно лаконічно і виразно.
rickerbh

1
Мені знайома хитрість, просто трохи важче міркувати в той час, коли цикли - для яких я знайшов дещо прикладів - особливо цей.
Професор Том

272

мають це розширення

Швидкий 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
    }
}

Швидкий 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() {

}

1
Дякую за пораду про розширення :)
Thein

4
Я намагався внести важливу редакцію в цю відповідь, але її відхилили (я не маю поняття, чому і вказані причини шаблону не мали сенсу): Важливо перевірити, чи не встановлено nav.visibleViewController нульовим, перш ніж використовувати його в рекурсивному режимі виклик (так само, як перевіряється tab.selectedViewController), оскільки в іншому випадку, якби він був нульовим, ви потрапляли б у рекурсивний нескінченний цикл.
Етан Г

@EthanG Згідно з моїм розумінням, якщо nav.visibleViewController є нульовим, функція поверне нуль (впаде до останнього return). Як він може потрапити в нескінченну петлю?
Десмонд DAI

3
Думаю, було б логічніше зробити це статичною функцією UIViewController
Лешек Зарна

1
Перевірка 'presenViewController', ймовірно, повинна бути першою, якщо ви хочете спіймати на UITabBarControllers представники контролера представлених представлень.
Токуріку,

65

Для швидкого 4/5 +, щоб отримати найкращий виглядКонтролер

// 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» застаріла в прошивкою 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)
}

це рішення виглядало дійсно багатообіцяючим, проте я намагався запустити це, щоб отримати контролер перегляду, на якому я ввімкнувся, коли я отримав сповіщення про натиск, і він кинув нульову помилку наreturn UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
Майк

@Міке вам потрібно використовувати лише представленийViewController, а не представленийViewController. представленийViewController
allaire

@allaire Якщо ви подавали контролер модального перегляду поверх контролера модального перегляду, то вам це потрібно .presentedViewController.presentedViewController, чи ні?
Баран Емре

6

На основі відповіді Діанца, версії 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 у навігаційному режимі.
Майк.R

Tnx Tnx Tnx Bro
reza_khalafi

6

Мені сподобалася відповідь @ dianz , і ось ось версія її швидкої 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)")
}

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()
    }
}

Потім перегляньте вид зверхуController:

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
Чим це відрізняється від відповіді rickerbh?
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

На основі Bob -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;
            }
        }
    }
}

3

Занадто багато ароматів, але жодного ітераційного не було. Поєднано з попередніми:

     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
велике спасибі, це працює добре для мене, як і інше рішення, коли він намагається отримати навігацію. Контролюйте це повернення нульовим, тому я не зміг підштовхнути будь-який новий відеокамін
Amr Angry

Переконайтеся, що поточний VC визначений як слабкий посилання, або у вас буде витік пам'яті.
bubuxu

2

Щоб знайти видимий виглядController у Swift 3

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

     let prefs = UserDefaults.standard

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

     }
}

Цей код знаходить останній доданий або останній активний контролер видимим.

Це я використовував у AppDelegate, щоб знайти контролер активного перегляду


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
    }
}

Неоднозначне використання "видимого ViewController"
Омар N Шамалі

1

Куди ви поставили код?

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

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

не вдасться, тому що ключове вікно ще налаштоване.

Але я поклав ваш код у якийсь контролер перегляду

override func viewDidLoad() {

Це просто працює.


Це не в didFinishLaunchingWithOptions. Мені просто це потрібно для різних цілей налагодження.
Зойт

1

У дуже рідкісному випадку, якщо користувальницька програма Segue є найпопулярнішим контролером перегляду, не знаходиться у навігаційному стеку чи контролері панелі вкладок або представлена, але його погляд вставляється у верхню частину підвидів ключового віндауна.

У такій ситуації необхідно перевірити, чи UIApplication.shared.keyWindow.subviews.last == self.viewвизначити, чи є поточний контролер перегляду найбільше.


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
        }
    }
}

Як би я ним користувався?
Кріс Комас

Просто назвіть це так. 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

Найкраще рішення для мене - розширення з функцією. Створіть швидкий файл із цим розширенням

Спочатку розширення 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
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.