Zdobądź najwyższą liczbę UIViewController

192

Nie mogę uzyskać najwyższej pozycji UIViewControllerbez dostępu do UINavigationController. Oto co mam do tej pory:

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

Wydaje się jednak, że nic nie robi. Wartości keyWindowi rootViewControllerwydają się również wartościami zerowymi, więc opcjonalne tworzenie łańcuchów nie powinno stanowić problemu.

UWAGA: zrobienie czegoś takiego jest kiepskim pomysłem. Łamie wzór MVC.

Zoyt
źródło
Oto jedno alternatywne rozwiązanie dostępne stackoverflow.com/a/39994115/1872233
iDevAmit

Odpowiedzi:

287

presentViewControllerpokazuje kontroler widoku. Nie zwraca kontrolera widoku. Jeśli nie używasz UINavigationController, prawdopodobnie szukasz presentedViewControlleri musisz zacząć od katalogu głównego i iterować w dół przez przedstawione widoki.

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

    // topController should now be your topmost view controller
}

W przypadku Swift 3+:

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

    // topController should now be your topmost view controller
}

Dla 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
źródło
1
Czy ktoś może wyjaśnić pętlę while? Dla mnie wygląda na to, że nie ma nic do zapętlenia; Nie jestem nawet pewien, dlaczego to się kompiluje.
Profesor Tom
15
@ProfessorTom Pętla trwa tak długo, jak długo topController.presentedViewControllercoś zwraca (tzn. Kontroler ma przedstawiony kontroler potomny). Ma to na while letcelu wymuszenie faktu, że topController.presentedViewControllercoś musi zwrócić. Jeśli zwróci zero (tzn. To kontroler nie ma prezentowanych elementów potomnych), przestanie zapętlać. W treści pętli ponownie przypisuje dziecko jako bieżące topControlleri zapętla ponownie, przechodząc w dół hierarchii kontrolera widoku. Może zmienić przypisanie, topControllerponieważ znajduje się varw instrukcji zewnętrznej if.
rickerbh
1
Dziękuję Ci. Nie udało mi się znaleźć żadnych przykładów online while let. if letMożna oczywiście znaleźć wiele przykładów.
Profesor Tom
1
let x = somethingThatCouldBeNilSkładnia jest bardzo przydatna sztuczka używać gdziekolwiek wartość prawda / stan może być używany. Gdybyśmy go tutaj nie użyli, musielibyśmy jawnie przypisać wartość, a następnie sprawdzić, czy rzeczywiście tam jest. Myślę, że to naprawdę zwięzłe i wyraziste.
rickerbh
1
Znam tę sztuczkę, trudniej jest ją sobie wyobrazić w pętlach while - dla których znalazłem brak przykładów - szczególnie tej.
Profesor Tom
272

mieć to rozszerzenie

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

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

Możesz użyć tego w dowolnym miejscu na kontrolerze

if let topController = UIApplication.topViewController() {

}
DLende
źródło
1
Dzięki za wskazówkę dotyczącą przedłużenia :)
Thein
4
Próbowałem dokonać ważnej edycji tej odpowiedzi, ale została ona odrzucona (nie mam pojęcia, dlaczego i podane przyczyny szablonu nie miały sensu): Ważne jest sprawdzenie, czy nav.visibleViewController jest zerowy przed użyciem go w rekurencyjnym wywołanie (tak jak sprawdzane jest tab.selectedViewController), ponieważ w przeciwnym razie, gdyby było zero, dostałbyś się do cyklicznej nieskończonej pętli.
Ethan G
@EthanG Według mojego zrozumienia, jeśli nav.visibleViewController ma wartość zero, funkcja zwróci zero (spadek do ostatniej return). Jak może wejść w nieskończoną pętlę?
Desmond DAI,
3
Myślę, że logiczniej byłoby uczynić to statyczną funkcją UIViewController
Leszek
1
Sprawdzanie „przedstawionyViewController” powinno być prawdopodobnie pierwsze, jeśli chcesz złapać kontrolery widoku prezentowane modalnie na UITabBarControllers ..
Tokuriku
65

Dla szybkiego 4/5 +, aby uzyskać najwyższy widok kontrolera

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

Jak używać

if let topVC = UIApplication.getTopViewController() {
   topVC.view.addSubview(forgotPwdView)
}
Hardik Thakkar
źródło
2
Genialne rozwiązanie. Dzięki!
Andrey M.
2
„keyWindow” było przestarzałe w iOS 13.0.
rs7,
2
„keyWindow” jest przestarzałe w 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
            }
        }
    }
}

Stosowanie:

if let topController = window.visibleViewController() {
    println(topController)
}
Bobj-C
źródło
to rozwiązanie wyglądało naprawdę obiecująco, ale próbowałem uruchomić to, aby włączyć kontroler widoku, gdy otrzymuję powiadomienie push, i to spowodowało błąd zerowy nareturn UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
Mike
@Mike musisz używać tylko przedstawionyViewController, a nie przedstawionyViewController. przedstawionyViewController
allaire,
@allaire Jeśli zaprezentowałeś modalny kontroler widoku na modalnym kontrolerze, to potrzebujesz .presentedViewController.presentedViewController, czy nie?
Baran Emre
6

Na podstawie odpowiedzi Dianza, wersja 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;
}
Tibidabo
źródło
Nie będzie działać dla UINavigationController w UITabBarController. zwróci UINavigationController, powinien zwrócić topController w nawigacji zablokowany.
Mike.R
Tnx Tnx Tnx Bro
reza_khalafi
6

Uwielbiałem odpowiedź @ dianz , więc oto jej wersja dla szybkiego 3. To w zasadzie to samo, ale brakowało mu nawiasów klamrowych i niektóre nazwy składni / zmiennych / metod uległy zmianie. Więc oto jest!

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

Jednak użycie jest nadal takie samo:

if let topController = UIApplication.topViewController() {
    print("The view controller you're looking at is: \(topController)")
}
Ponyboy47
źródło
6

https://gist.github.com/db0company/369bfa43cb84b145dfd8 Zrobiłem kilka testów odpowiedzi i komentarzy na tej stronie. Dla mnie następujące prace

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

Następnie uzyskaj widok z góryController przez:

UIApplication.shared.topMostViewController()
Albert Zou
źródło
5

Użyj tego kodu, aby znaleźć najlepsze narzędzie UIViewController

func getTopViewController() -> UIViewController? {
    var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    while topController?.presentedViewController != nil {
        topController = topController?.presentedViewController
    }
    return topController
}
Bibin Joseph
źródło
2
Czym różni się to od odpowiedzi rickerbh?
ElectroBuddha,
5

Niewielka zmiana na @AlberZou przy użyciu obliczonej zmiennej zamiast funkcji

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

Więc powiedz

if let topViewControler = UIApplication.shared.topMostViewController {
    ... do stuff
}
Ryan Heitner
źródło
4

Na podstawie Bob-c powyżej:

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;
            }
        }
    }
}
Daniel
źródło
3

Zbyt wiele smaków, ale żaden z nich nie został opracowany iteracyjnie. W połączeniu z poprzednimi:

     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
    }
Jaime Agudo
źródło
2

możesz zdefiniować zmienną UIViewController w AppDelegate i w każdym viewWillAppear ustaw zmienną na self. (jednak odpowiedź dianz jest najlepszą odpowiedzią).

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
    appDel.currentVC = self
}
Arash Jamshidi
źródło
1
wielkie dzięki, to działa dobrze dla mnie jako inne rozwiązanie, gdy próbuję uzyskać nawigację Kontroluj, zwróć zero, więc nie byłem w stanie wypchnąć żadnego nowego vc
Amr Angry
Upewnij się, że currentVC zdefiniowane jako słabe odniesienie, w przeciwnym razie wystąpi przeciek pamięci.
bubuxu
2

Aby znaleźć widoczny kontroler widoku w Swift 3

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

     let prefs = UserDefaults.standard

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

     }
}

Ten kod znajduje ostatnio dodany lub ostatni aktywny kontroler widoczny.

Użyłem tego w AppDelegate, aby znaleźć kontroler widoku aktywnego

Prateekro
źródło
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
    }
}
Rakesh Kusuma
źródło
Niejednoznaczne użycie „visibleViewController”
Omar N Shamali
1

Gdzie umieściłeś kod?

Sprawdziłem twój kod w mojej wersji demo, dowiedziałem się, jeśli umieścisz kod

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

zawiedzie, ponieważ okno klucza zostało już ustawione.

Ale umieściłem twój kod w kontrolerze widoku

override func viewDidLoad() {

To po prostu działa.

Tinyfool
źródło
Nie ma go didFinishLaunchingWithOptions. Potrzebuję tego tylko do różnych celów debugowania.
Zoyt
1

W bardzo rzadkim przypadku, przy niestandardowym segue, najwyżej umieszczony kontroler widoku nie znajduje się w stosie nawigacji lub kontrolerze paska kart ani nie jest prezentowany, ale jego widok jest wstawiany w górnej części podrzędnych okien widokowych.

W takiej sytuacji konieczne jest sprawdzenie, UIApplication.shared.keyWindow.subviews.last == self.viewczy bieżący kontroler widoku jest najwyższy.

BabyPanda
źródło
1

Dla każdego, kto szuka szybkiego rozwiązania 5 / iOS 13+ ( keywindowjest przestarzałe od 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
źródło
Jak miałbym tego użyć?
Chris Comas
Nazwij to tak. UIApplication.getTopMostViewController()wewnątrz twojego 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
    }
Legowisko
źródło
0

Najlepszym rozwiązaniem dla mnie jest rozszerzenie z funkcją. Utwórz szybki plik z tym rozszerzeniem

Pierwszy to rozszerzenie 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
            }
        }
    }
}

wewnątrz tego pliku dodaj funkcję

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

A jeśli chcesz go użyć, możesz do niego zadzwonić w dowolnym miejscu. Przykład :

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

Kod pliku wygląda następująco :

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
źródło