Jak znaleźć najwyżej kontroler widoku na iOS

253

Natknąłem się teraz na kilka przypadków, w których wygodnie byłoby znaleźć „najwyższy” kontroler widoku (ten odpowiedzialny za bieżący widok), ale nie znalazłem sposobu, aby to zrobić.

Zasadniczo wyzwanie jest następujące: biorąc pod uwagę, że wykonuje się w klasie, która nie jest kontrolerem widoku (lub widokiem) [i nie ma adresu aktywnego widoku] i nie został przekazany adres najwyższego kontrolera widoku ( lub powiedzmy adres kontrolera nawigacyjnego), czy można znaleźć tego kontrolera widoku? (A jeśli tak, to w jaki sposób?)

Lub, w przeciwnym razie, czy można znaleźć najwyższy widok?

Hot Licks
źródło
Więc mówisz, że to niemożliwe.
Hot Licks
@Daniel nie, mówię, że wygląda na to, że Twój kod może wymagać przeprojektowania, ponieważ rzadko powinieneś o tym wiedzieć. Także idea „najwyższego poziomu” jest ważna tylko w niektórych kontekstach, a nawet wtedy nie zawsze.
Dave DeLong
@Daniel Źle odczytałem twoje pytanie. Istnieje wiele pytań i odpowiedzi na to pytanie. To zależy od przepływu kontrolera widoku. Odpowiedź @ Wilbur powinna być dobrym punktem wyjścia do jej prześledzenia.
Deepak Danduprolu
Uprośćmy to w konkretnym przypadku. Gdybym chciał napisać klon UIAlertView, jak miałbym to zrobić? Należy pamiętać, że może działać dobrze bez przekazywania adresowalności innym kontrolerom lub widokom.
Hot Licks
4
@Daniel: Dodanie drugiego interfejsu użytkownika działa dobrze w przypadku ostrzeżeń nakładek podobnych do widoku.
Wilbur Vandrsmith

Odpowiedzi:

75

iOS 4 wprowadził właściwość rootViewController w UIWindow:

[UIApplication sharedApplication].keyWindow.rootViewController;

Musisz jednak ustawić go sam po utworzeniu kontrolera widoku.

Wilbur Vandrsmith
źródło
155
Wilbur, da ci to przeciwieństwo tego, o co prosiła operacja. rootViewController to kontroler widoku podstawowego, a nie najwyższy.
m4rkk
3
m4rkk: „Najwyższa” zależy od kierunku, z którego patrzysz. Czy nowe kontrolery są dodawane na górze (na stosie) lub na dole (na drzewie)? W każdym razie PO wspomniał, że kontroler nawigacyjny jest na górze, co implikuje widok narastania w dół.
Wilbur Vandrsmith
50
Słowo „góra” jest używane w kontrolerze widoku, czyli wizualnie na górze (jak -[UINavigationController topViewController]). Potem jest słowo „root”, które jest korzeniem drzewa (jak -[UIWindow rootViewController].
Tricertops
13
@ImpurestClub Nie mogę znaleźć w dokumentacji , nie wydaje się, że Xcode ją znajdzie.
Drux
4
@adib nie, należy do UINavigationController
David H
428

Myślę, że potrzebujesz kombinacji zaakceptowanej odpowiedzi i @ fishstix

+ (UIViewController*) topMostController
{
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;

    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }

    return topController;
}

Swift 3.0+

func topMostController() -> UIViewController? {
    guard let window = UIApplication.shared.keyWindow, let rootViewController = window.rootViewController else {
        return nil
    }

    var topController = rootViewController

    while let newTopController = topController.presentedViewController {
        topController = newTopController
    }

    return topController
}
Eric
źródło
4
Ponadto możesz sprawdzić UINavigationControlleri poprosić o to, topViewControllera nawet sprawdzić UITabBarControlleri poprosić o selectedViewController. W ten sposób uzyskasz kontroler widoku, który jest obecnie widoczny dla użytkownika.
Tricertops
33
Jest to niekompletne rozwiązanie, ponieważ porusza się tylko w hierarchii kontrolerów widoku prezentowanych modalnie, a nie w hierarchii childViewControllers (używanych przez UINavigationController, UITabBarController itp.).
algal
3
Jest to świetny sposób na wyodrębnienie prezentacji modalnego kontrolera widoku, który powraca do bieżącego stanu aplikacji, w moim przypadku był to ekran ponownego wprowadzenia hasła po upływie limitu czasu aplikacji. Dzięki!
erversteeg
11
@algal: naprawdę nie: UITabBarController, UINavigationController już najwyższymi kontrolerami widoku w hierarchii. W zależności od tego, co chcesz zrobić z „najwyżej położonym kontrolerem”, możesz nie chcieć w ogóle przechodzić między nimi i majstrować przy ich zawartości. W moim przypadku chodziło o przedstawienie kontrolera modalnego na wierzchu wszystkiego i do tego potrzebuję UINaviationController lub UITabBarController, a nie ich zawartości !!
Rick77
1
@ Rick77, jeśli to prawda, twój jeden mały komentarz zakopany tutaj sprawia, że ​​zbędne staje się mnóstwo skomplikowanych modyfikacji w innych odpowiedziach. Ponieważ nikt o tym w ogóle nie wspomina, czuję, że muszę cię prosić o potwierdzenie, że to prawda. A jeśli tak, to jest tak ważne, że zasługuje na to, aby być odpowiedzią samą w sobie. Ponieważ znaczna większość innych odpowiedzi robi backflipy próbując rozwiązać ten problem. Ratowałbyś życie!
Le Mot Juiced
150

Aby uzupełnić odpowiedź JonasG (która pominęła kontrolery paska kart podczas przechodzenia), oto moja wersja zwracania obecnie widocznego kontrolera widoku:

- (UIViewController*)topViewController {
    return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
    if ([rootViewController isKindOfClass:[UITabBarController class]]) {
        UITabBarController* tabBarController = (UITabBarController*)rootViewController;
        return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
    } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController* navigationController = (UINavigationController*)rootViewController;
        return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
    } else if (rootViewController.presentedViewController) {
        UIViewController* presentedViewController = rootViewController.presentedViewController;
        return [self topViewControllerWithRootViewController:presentedViewController];
    } else {
        return rootViewController;
    }
}
kleo
źródło
2
Fajnie, tak, zapomniałem o kontrolerach TabBar: P
JonasG,
9
Nie obejmujechildViewControllers
Awesome-o
Spójrz na moją odpowiedź poniżej, która poprawia powyższą odpowiedź, zajmując się sprawami pominiętymi przez @kleo, takimi jak popovers, kontrolery widoku dodane jako podviewy do niektórych innych kontrolerów widoku podczas przechodzenia
Rajesh
Jeśli używasz return [self topViewControllerWithRootViewController: navigationController.visibleViewController] ;, widocznyViewController sam zwraca przedstawiony kontroler widoku (JEŚLI DOWOLNY), nawet jeśli jest to kontroler UIAlert. Dla kogoś, kto musi unikać kontrolera alertów interfejsu użytkownika, użyj topViewController zamiast visibleViewController
Johnykutty
Wystarczy dodać do tego moje 50 centów - walczyłem o to, aby działało to w moim kontroler widoku, który ładuje przeglądarkę internetową. Powodem, dla którego nie mogłem tego uruchomić, było to, że widok nie był jeszcze gotowy (ładowanie nie zakończyło się) i dlatego nie był widoczny. Doprowadziło to do sytuacji, w której uzyskanie topViewContoller nie powiodło się, ponieważ UINavigationController próbował uzyskać widoczny ViewController, podczas gdy nie było jeszcze widocznego ViewController. Jeśli więc ktoś napotka ten problem, upewnij się, że widok zakończy ładowanie, zanim wykonasz wywołanie powyższej metody topViewController.
mbuster
52

Kompletna wersja nierekurencyjna, dbająca o różne scenariusze:

  • Kontroler widoku przedstawia inny widok
  • Kontroler widoku to UINavigationController
  • Kontroler widoku to UITabBarController

Cel C

 UIViewController *topViewController = self.window.rootViewController;
 while (true)
 {
     if (topViewController.presentedViewController) {
         topViewController = topViewController.presentedViewController;
     } else if ([topViewController isKindOfClass:[UINavigationController class]]) {
         UINavigationController *nav = (UINavigationController *)topViewController;
         topViewController = nav.topViewController;
     } else if ([topViewController isKindOfClass:[UITabBarController class]]) {
         UITabBarController *tab = (UITabBarController *)topViewController;
         topViewController = tab.selectedViewController;
     } else {
         break;
     }
 }

Swift 4+

extension UIWindow {
    func topViewController() -> UIViewController? {
        var top = self.rootViewController
        while true {
            if let presented = top?.presentedViewController {
                top = presented
            } else if let nav = top as? UINavigationController {
                top = nav.visibleViewController
            } else if let tab = top as? UITabBarController {
                top = tab.selectedViewController
            } else {
                break
            }
        }
        return top
    }
}
Yuchen Zhong
źródło
2
Nazwałem to, visibleViewControlleraby wyjaśnić, co robi.
Jonny
31

Uzyskiwanie najlepszego kontrolera widoku dla Swift przy użyciu rozszerzeń

Kod:

extension UIViewController {
    @objc func topMostViewController() -> UIViewController {
        // Handling Modal views
        if let presentedViewController = self.presentedViewController {
            return presentedViewController.topMostViewController()
        }
        // Handling UIViewController's added as subviews to some other views.
        else {
            for view in self.view.subviews
            {
                // Key property which most of us are unaware of / rarely use.
                if let subViewController = view.next {
                    if subViewController is UIViewController {
                        let viewController = subViewController as! UIViewController
                        return viewController.topMostViewController()
                    }
                }
            }
            return self
        }
    }
}

extension UITabBarController {
    override func topMostViewController() -> UIViewController {
        return self.selectedViewController!.topMostViewController()
    }
}

extension UINavigationController {
    override func topMostViewController() -> UIViewController {
        return self.visibleViewController!.topMostViewController()
    }
}

Stosowanie:

UIApplication.sharedApplication().keyWindow!.rootViewController!.topMostViewController()
Waruna
źródło
doskonale - dziękuję bardzo za to rozwiązanie. Potrzebna była sztuczka podviewów! Jeszcze raz wielkie dzięki, uratowałeś mi dzień.
iKK
25

Aby wypełnić odpowiedź Erica (która pominęła okna popover, kontrolery nawigacyjne, kontrolery paska tabulatorów, kontrolery widoku dodane jako podviewy do niektórych innych kontrolerów widoku podczas przechodzenia), oto moja wersja zwracania obecnie widocznego kontrolera widoku:

================================================== ===================

- (UIViewController*)topViewController {
    return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)viewController {
    if ([viewController isKindOfClass:[UITabBarController class]]) {
        UITabBarController* tabBarController = (UITabBarController*)viewController;
        return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
    } else if ([viewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController* navContObj = (UINavigationController*)viewController;
        return [self topViewControllerWithRootViewController:navContObj.visibleViewController];
    } else if (viewController.presentedViewController && !viewController.presentedViewController.isBeingDismissed) {
        UIViewController* presentedViewController = viewController.presentedViewController;
        return [self topViewControllerWithRootViewController:presentedViewController];
    }
    else {
        for (UIView *view in [viewController.view subviews])
        {
            id subViewController = [view nextResponder];
            if ( subViewController && [subViewController isKindOfClass:[UIViewController class]])
            {
                if ([(UIViewController *)subViewController presentedViewController]  && ![subViewController presentedViewController].isBeingDismissed) {
                    return [self topViewControllerWithRootViewController:[(UIViewController *)subViewController presentedViewController]];
                }
            }
        }
        return viewController;
    }
}

================================================== ===================

A teraz wszystko, co musisz zrobić, aby uzyskać najwyższy kontroler widoku, to wywołaj powyższą metodę w następujący sposób:

UIViewController *topMostViewControllerObj = [self topViewController];
Rajesz
źródło
Brakuje również SplitViewController?
apinho
21

Ta odpowiedź obejmuje childViewControllersi utrzymuje czystą i czytelną implementację.

+ (UIViewController *)topViewController
{
    UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;

    return [rootViewController topVisibleViewController];
}

- (UIViewController *)topVisibleViewController
{
    if ([self isKindOfClass:[UITabBarController class]])
    {
        UITabBarController *tabBarController = (UITabBarController *)self;
        return [tabBarController.selectedViewController topVisibleViewController];
    }
    else if ([self isKindOfClass:[UINavigationController class]])
    {
        UINavigationController *navigationController = (UINavigationController *)self;
        return [navigationController.visibleViewController topVisibleViewController];
    }
    else if (self.presentedViewController)
    {
        return [self.presentedViewController topVisibleViewController];
    }
    else if (self.childViewControllers.count > 0)
    {
        return [self.childViewControllers.lastObject topVisibleViewController];
    }

    return self;
}
Awesome-o
źródło
Zaktualizowałem trochę kodu, ponieważ pokazuje też, jaki to kontroler, minimalizując go i przywracając. nik-kov-ios-developer.blogspot.ru/2016/12/…
Nik
Hej, daj spokój, gdzie jest twój „topVisibleViewController”?
Raj
12

Niedawno dostałem tę sytuację w jednym z moich projektów, który wymagał wyświetlenia widoku powiadomienia niezależnie od wyświetlanego kontrolera i typu (UINavigationController, klasyczny kontroler lub niestandardowy kontroler widoku), gdy zmienił się status sieci.

Właśnie wydałem swój kod, który jest dość łatwy i faktycznie oparty na protokole, dzięki czemu jest elastyczny dla każdego typu kontrolera kontenera. Wydaje się, że jest to związane z ostatnimi odpowiedziami, ale w bardzo elastyczny sposób.

Możesz pobrać kod tutaj: PPTopMostController

I uzyskał najwyższą liczbę kontrolerów

UIViewController *c = [UIViewController topMostController];
ipodishima
źródło
10

To jest ulepszenie odpowiedzi Erica:

UIViewController *_topMostController(UIViewController *cont) {
    UIViewController *topController = cont;

    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }

    if ([topController isKindOfClass:[UINavigationController class]]) {
        UIViewController *visible = ((UINavigationController *)topController).visibleViewController;
        if (visible) {
            topController = visible;
        }
    }

    return (topController != cont ? topController : nil);
}

UIViewController *topMostController() {
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;

    UIViewController *next = nil;

    while ((next = _topMostController(topController)) != nil) {
        topController = next;
    }

    return topController;
}

_topMostController(UIViewController *cont) jest funkcją pomocniczą.

Teraz wystarczy zadzwonić, topMostController()a najwyższy UIViewController powinien zostać zwrócony!

JonasG
źródło
7
Od 1983 r. Powiedziałbym. Pamiętaj, że Objective-C zawiera C ... Zawijanie kodu ObjC w funkcjach C jest powszechną praktyką, więc tak, to jest kod Objective-C.
JonasG
@JonasG Cześć Jonas, W jakich okolicznościach wolisz pakować kod ObjC w C? Ponieważ czasami widzę takie funkcje C i nie mogę rozróżnić użycia. Czy zawijanie kodu w C zapewnia jakieś korzyści w zakresie wydajności?
OzBoz
1
@OzBoz W sytuacjach, gdy nie jest od razu jasne, do której klasy selfnależy.
adib
8

Oto moje zdanie na ten temat. Dzięki @Stakenborg za wskazanie sposobu na pominięcie uzyskania UIAlertView jako najlepszego kontrolera

-(UIWindow *) returnWindowWithWindowLevelNormal
{
    NSArray *windows = [UIApplication sharedApplication].windows;
    for(UIWindow *topWindow in windows)
    {
        if (topWindow.windowLevel == UIWindowLevelNormal)
            return topWindow;
    }
    return [UIApplication sharedApplication].keyWindow;
}

-(UIViewController *) getTopMostController
{
    UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
    if (topWindow.windowLevel != UIWindowLevelNormal)
    {
        topWindow = [self returnWindowWithWindowLevelNormal];
    }

    UIViewController *topController = topWindow.rootViewController;
    if(topController == nil)
    {
        topWindow = [UIApplication sharedApplication].delegate.window;
        if (topWindow.windowLevel != UIWindowLevelNormal)
        {
            topWindow = [self returnWindowWithWindowLevelNormal];
        }
        topController = topWindow.rootViewController;
    }

    while(topController.presentedViewController)
    {
        topController = topController.presentedViewController;
    }

    if([topController isKindOfClass:[UINavigationController class]])
    {
        UINavigationController *nav = (UINavigationController*)topController;
        topController = [nav.viewControllers lastObject];

        while(topController.presentedViewController)
        {
            topController = topController.presentedViewController;
        }
    }

    return topController;
}
Kamran Khan
źródło
Powinieneś unikać metod nazewnictwa jak getSomething:w Objective-C. Ma to specjalne znaczenie (więcej: cocoadevcentral.com/articles/000082.php ) i nie spełniasz tych wymagań w kodzie.
Vive,
7
@implementation UIWindow (rozszerzenia)

- (UIViewController *) topMostController
{
    UIViewController * topController = [self rootViewController];

    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }

    return topController;
}

@koniec
FishStix
źródło
Nie sądzę, abyś spełnił warunek określony w oryginalnym poście.
Hot Licks
7
- (UIViewController*)topViewController {
    return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
    if ([rootViewController isKindOfClass:[UITabBarController class]]) {
        UITabBarController* tabBarController = (UITabBarController*)rootViewController;
        return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
    } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController* navigationController = (UINavigationController*)rootViewController;
        return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
    } else if (rootViewController.presentedViewController) {
        UIViewController* presentedViewController = rootViewController.presentedViewController;
        return [self topViewControllerWithRootViewController:presentedViewController];
    } else {
        return rootViewController;
    }
}
lifuqing_ios
źródło
Użyłem tego, ale zauważ, że pęka, gdy jest więcej niż jeden kontroler widoku
Chuck Boris
7

Najnowsza wersja Swift:
utwórz plik, nazwij go UIWindowExtension.swifti wklej następujący fragment:

import UIKit

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

    public static func getVisibleViewControllerFrom(vc: UIViewController?) -> UIViewController? {
        if let nc = vc as? UINavigationController {
            return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
        } else if let tc = vc as? UITabBarController {
            return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
        } else {
            if let pvc = vc?.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(pvc)
            } else {
                return vc
            }
        }
    }
}

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

Użyj go w dowolnym miejscu jako:

if let topVC = getTopViewController() {

}
Ashok
źródło
Nie chcę zbytnio zmieniać odpowiedzi, ale sugerowałbym kilka rzeczy. 1. Dodaj obsługę UISplitViewController. 2. użyj switchzamiast jeśli inaczej. 3. nie jestem pewien, czy potrzebujesz również funkcji statycznej, myślę, że możesz to zrobić z łatwością na poziomie pierwszej instancji var, który zadeklarowałeś. 4. Prawdopodobnie najlepiej nie tworzyć zbyt wielu globalnych funkcji, ale to kwestia gustu. Możesz użyć jednego wiersza kodu, aby osiągnąć efekt funkcji globalnej:UIApplication.sharedApplication().delegate?.window?.visibleViewController
Jordan Smith
7

Proste rozszerzenie UIApplicationw Swift:

UWAGA:

Dba o to moreNavigationControllerw środkuUITabBarController

extension UIApplication {

    class func topViewController(baseViewController: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {

        if let navigationController = baseViewController as? UINavigationController {
            return topViewController(navigationController.visibleViewController)
        }

        if let tabBarViewController = baseViewController as? UITabBarController {

            let moreNavigationController = tabBarViewController.moreNavigationController

            if let topViewController = moreNavigationController.topViewController where topViewController.view.window != nil {
                return topViewController(topViewController)
            } else if let selectedViewController = tabBarViewController.selectedViewController {
                return topViewController(selectedViewController)
            }
        }

        if let splitViewController = baseViewController as? UISplitViewController where splitViewController.viewControllers.count == 1 {
            return topViewController(splitViewController.viewControllers[0])
        }

        if let presentedViewController = baseViewController?.presentedViewController {
            return topViewController(presentedViewController)
        }

        return baseViewController
    }
}

Proste użycie:

if let topViewController = UIApplication.topViewController() {
    //do sth with top view controller
}
Bartłomiej Semańczyk
źródło
TAK TAK TAK - w sieci istnieje wiele rozwiązań umożliwiających znalezienie topMostViewController, ale jeśli Twoja aplikacja ma pasek kart z kartą Więcej, MUSISZ to zrobić trochę inaczej.
Andy Obusek,
7

Użyj poniższego rozszerzenia, aby złapać prąd widoczny UIViewController. Pracował dla Swift 4.0 i późniejszych

Swift 4.0 i nowsze:

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

Jak używać?

let objViewcontroller = UIApplication.topViewController()
COVID-19
źródło
Jeżeli nie jest to test na presentedViewControllerpoczątku, przed UINavigationControlleri UITabBarControllerprzypadkach? W przeciwnym razie, jeśli kontroler widoku jest modalnie prezentowany z UINavigationControllerlub UITabBarController, nie zostanie zwrócony jako kontroler widoku z góry, nawet jeśli jest to kontroler widoku, który jest widoczny.
Drew
4

Kolejne szybkie rozwiązanie

func topController() -> UIViewController? {

    // recursive follow
    func follow(from:UIViewController?) -> UIViewController? {
        if let to = (from as? UITabBarController)?.selectedViewController {
            return follow(to)
        } else if let to = (from as? UINavigationController)?.visibleViewController {
            return follow(to)
        } else if let to = from?.presentedViewController {
            return follow(to)
        }
        return from
    }

    let root = UIApplication.sharedApplication().keyWindow?.rootViewController

    return follow(root)

}
Martin Algesten
źródło
4

Rozszerzenie Swift 4.2


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

Używaj go z dowolnego miejsca, np.

 UIApplication.topViewController()?.present(yourController, animated: true, completion: nil)

lub jak

 UIApplication.topViewController()?
                    .navigationController?
                    .popToViewController(yourController,
                                         animated: true)

Dopasuj do dowolnych klas, takich jak UINavigationController, UITabBarController

Cieszyć się!

Saranjith
źródło
1
@BilalBakhrom mówi: „Upvoted. Myślę, że twoja odpowiedź jest najlepsza. Nie możesz bezpośrednio wywołać metody topViewController (). Klasa UIApplication jest singleton, użyj instancji o nazwie„ shared ”. w edycji , którą głosowałem za odrzuceniem. Jeśli jest to rzeczywiście poprawne, kliknij tutaj, aby przejść do menu, w którym możesz zatwierdzić tę edycję.
wizzwizz4 27.04.19
3

Oto, co zadziałało dla mnie.

Odkryłem, że czasami kontroler był zerowy w oknie klucza, ponieważ keyWindow to coś takiego jak alert itp.

 + (UIViewController*)topMostController
 {
     UIWindow *topWndow = [UIApplication sharedApplication].keyWindow;
     UIViewController *topController = topWndow.rootViewController;

     if (topController == nil)
     {
         // The windows in the array are ordered from back to front by window level; thus,
         // the last window in the array is on top of all other app windows.
         for (UIWindow *aWndow in [[UIApplication sharedApplication].windows reverseObjectEnumerator])
         {
             topController = aWndow.rootViewController;
             if (topController)
                 break;
         }
     }

     while (topController.presentedViewController) {
         topController = topController.presentedViewController;
     }

     return topController;
 }
Tom Andersen
źródło
3

Rozwijając odpowiedź @ Erica, musisz uważać, aby keyWindow było faktycznie oknem, które chcesz. Jeśli próbujesz użyć tej metody na przykład po stuknięciu czegoś w widoku alertu, keyWindow będzie w rzeczywistości oknem alertu, co z pewnością spowoduje problemy. Zdarzyło mi się to na wolności, gdy obsługiwałem głębokie linki za pośrednictwem alertu i spowodowało SIGABRT bez BRAKU ŚLADU. Całkowita suka do debugowania.

Oto kod, którego teraz używam:

- (UIViewController *)getTopMostViewController {
    UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
    if (topWindow.windowLevel != UIWindowLevelNormal) {
        NSArray *windows = [UIApplication sharedApplication].windows;
        for(topWindow in windows)
        {
            if (topWindow.windowLevel == UIWindowLevelNormal)
                break;
        }
    }

    UIViewController *topViewController = topWindow.rootViewController;

    while (topViewController.presentedViewController) {
        topViewController = topViewController.presentedViewController;
    }

    return topViewController;
}

Możesz to połączyć z dowolnym smakiem pobierania kontrolera widoku z góry, który lubisz z innych odpowiedzi na to pytanie.

Stakenborg
źródło
Czy uważasz to za kompletne rozwiązanie? Tak wiele innych odpowiedzi jest niezwykle skomplikowanych, próbując wyjaśnić tak wiele przypadków skrajnych. Chcę, żeby to była prawda, to takie proste i eleganckie.
Le Mot Juiced
Nigdy nie miałem z tym problemu. Jeśli nie robisz nic niezwykłego ze stosem nawigacji, powinno to działać, w przeciwnym razie niektóre inne rozwiązania obsługują bardziej skomplikowane przypadki.
Stakenborg
3

Alternatywne rozwiązanie Swift:

static func topMostController() -> UIViewController {
    var topController = UIApplication.sharedApplication().keyWindow?.rootViewController
    while (topController?.presentedViewController != nil) {
        topController = topController?.presentedViewController
    }

    return topController!
}
Esqarrouth
źródło
3

To rozwiązanie jest najbardziej kompletne. Uwzględnia: UINavigationController UIPageViewController UITabBarController I najwyżej przedstawiony kontroler widoku z kontrolera widoku z góry

Przykład znajduje się w Swift 3.

Istnieją 3 przeciążenia

//Get the topmost view controller for the current application.
public func MGGetTopMostViewController() -> UIViewController?  {

    if let currentWindow:UIWindow = UIApplication.shared.keyWindow {
        return MGGetTopMostViewController(fromWindow: currentWindow)
    }

    return nil
}

//Gets the topmost view controller from a specific window.
public func MGGetTopMostViewController(fromWindow window:UIWindow) -> UIViewController? {

    if let rootViewController:UIViewController = window.rootViewController
    {
        return MGGetTopMostViewController(fromViewController:  rootViewController)
    }

    return nil
}


//Gets the topmost view controller starting from a specific UIViewController
//Pass the rootViewController into this to get the apps top most view controller
public func MGGetTopMostViewController(fromViewController viewController:UIViewController) -> UIViewController {

    //UINavigationController
    if let navigationViewController:UINavigationController = viewController as? UINavigationController {
        let viewControllers:[UIViewController] = navigationViewController.viewControllers
        if navigationViewController.viewControllers.count >= 1 {
            return MGGetTopMostViewController(fromViewController: viewControllers[viewControllers.count - 1])
        }
    }

    //UIPageViewController
    if let pageViewController:UIPageViewController = viewController as? UIPageViewController {
        if let viewControllers:[UIViewController] = pageViewController.viewControllers {
            if viewControllers.count >= 1 {
                return MGGetTopMostViewController(fromViewController: viewControllers[0])
            }
        }
    }

    //UITabViewController
    if let tabBarController:UITabBarController = viewController as? UITabBarController {
        if let selectedViewController:UIViewController = tabBarController.selectedViewController {
            return MGGetTopMostViewController(fromViewController: selectedViewController)
        }
    }

    //Lastly, Attempt to get the topmost presented view controller
    var presentedViewController:UIViewController! = viewController.presentedViewController
    var nextPresentedViewController:UIViewController! = presentedViewController?.presentedViewController

    //If there is a presented view controller, get the top most prensentedViewController and return it.
    if presentedViewController != nil {
        while nextPresentedViewController != nil {

            //Set the presented view controller as the next one.
            presentedViewController = nextPresentedViewController

            //Attempt to get the next presented view controller
            nextPresentedViewController = presentedViewController.presentedViewController
        }
        return presentedViewController
    }

    //If there is no topmost presented view controller, return the view controller itself.
    return viewController
}
Marc Renaud
źródło
3

Zwięzłe, ale kompleksowe rozwiązanie w Swift 4.2, bierze pod uwagę UINavigationControllers , UITabBarControllers , kontrolery prezentowane i potomne :

extension UIViewController {
  func topmostViewController() -> UIViewController {
    if let navigationVC = self as? UINavigationController,
      let topVC = navigationVC.topViewController {
      return topVC.topmostViewController()
    }
    if let tabBarVC = self as? UITabBarController,
      let selectedVC = tabBarVC.selectedViewController {
      return selectedVC.topmostViewController()
    }
    if let presentedVC = presentedViewController {
      return presentedVC.topmostViewController()
    }
    if let childVC = children.last {
      return childVC.topmostViewController()
    }
    return self
  }
}

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

Stosowanie:

let viewController = UIApplication.shared.topmostViewController()
nalexn
źródło
2

Świetne rozwiązanie w Swift, wdrożenie w AppDelegate

func getTopViewController()->UIViewController{
    return topViewControllerWithRootViewController(UIApplication.sharedApplication().keyWindow!.rootViewController!)
}
func topViewControllerWithRootViewController(rootViewController:UIViewController)->UIViewController{
    if rootViewController is UITabBarController{
        let tabBarController = rootViewController as! UITabBarController
        return topViewControllerWithRootViewController(tabBarController.selectedViewController!)
    }
    if rootViewController is UINavigationController{
        let navBarController = rootViewController as! UINavigationController
        return topViewControllerWithRootViewController(navBarController.visibleViewController)
    }
    if let presentedViewController = rootViewController.presentedViewController {
        return topViewControllerWithRootViewController(presentedViewController)
    }
    return rootViewController
}
Edward Novelo
źródło
1

Myślę, że większość odpowiedzi została całkowicie zignorowana UINavigationViewController, więc poradziłem sobie z tym przypadkiem użycia z następującą implementacją.

+ (UIViewController *)topMostController {
    UIViewController * topController = [UIApplication sharedApplication].keyWindow.rootViewController;
    while (topController.presentedViewController || [topController isMemberOfClass:[UINavigationController class]]) {
        if([topController isMemberOfClass:[UINavigationController class]]) {
            topController = [topController childViewControllers].lastObject;
        } else {
            topController = topController.presentedViewController;
        }
    }

    return topController;
}
Aamir
źródło
1

Wiem, że jest bardzo późno i może być zbędne. Ale poniższy fragment, który wymyśliłem, działa dla mnie:

    static func topViewController() -> UIViewController? {
        return topViewController(vc: UIApplication.shared.keyWindow?.rootViewController)
    }

    private static func topViewController(vc:UIViewController?) -> UIViewController? {
        if let rootVC = vc {
            guard let presentedVC = rootVC.presentedViewController else {
                return rootVC
            }
            if let presentedNavVC = presentedVC as? UINavigationController {
                let lastVC = presentedNavVC.viewControllers.last
                return topViewController(vc: lastVC)
            }
            return topViewController(vc: presentedVC)
        }
        return nil
    }
Anil Arigela
źródło
0

Działa to doskonale w celu znalezienia kontrolera widoku z góry 1 z dowolnego elementu sterującego widokiem katalogu głównego

+ (UIViewController *)topViewControllerFor:(UIViewController *)viewController
{
    if(!viewController.presentedViewController)
        return viewController;
    return [MF5AppDelegate topViewControllerFor:viewController.presentedViewController];
}

/* View Controller for Visible View */

AppDelegate *app = [UIApplication sharedApplication].delegate;
UIViewController *visibleViewController = [AppDelegate topViewControllerFor:app.window.rootViewController]; 
johnnyg17
źródło
0

Nie jestem pewien, czy to pomoże w osiągnięciu tego, co próbujesz osiągnąć, znajdując najwyższy kontroler widoku, ale próbowałem przedstawić nowy kontroler widoku, ale jeśli mój główny kontroler widoku miał już modalne okno dialogowe, zostanie ono zablokowane, więc ja przejdzie na górę wszystkich kontrolerów widoku modalnego, używając tego kodu:

UIViewController* parentController =[UIApplication sharedApplication].keyWindow.rootViewController;

while( parentController.presentedViewController &&
       parentController != parentController.presentedViewController )
{
    parentController = parentController.presentedViewController;
}
Toland Hon
źródło
0

możesz znaleźć najwyżej kontroler widoku, używając

NSArray *arrViewControllers=[[self navigationController] viewControllers];
UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1];
Tapas Pal
źródło
Tyle że, jeśli faktycznie przeczytałeś pytanie, selfnie ma żadnej navigationControllerwłaściwości.
Hot Licks
0

Inne rozwiązanie opiera się na łańcuchu respondentów, który może, ale nie musi działać, w zależności od tego, kto jest pierwszym responderem:

  1. Uzyskaj pierwszą odpowiedź .
  2. Uzyskaj UIViewController skojarzony z tym pierwszym responderem .

Przykład pseudo kodu:

+ (UIViewController *)currentViewController {
    UIView *firstResponder = [self firstResponder]; // from the first link above, but not guaranteed to return a UIView, so this should be handled more appropriately.
    UIViewController *viewController = [firstResponder viewController]; // from the second link above
    return viewController;
}
Rozsądny
źródło
0

Szybki:

extension UIWindow {

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

class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
if vc.isKindOfClass(UINavigationController.self) {

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

} else if vc.isKindOfClass(UITabBarController.self) {

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

} else {

    if let presentedViewController = vc.presentedViewController {

        return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)

    } else {

        return vc;
    }
}
}

Stosowanie:

 if let topController = window.visibleViewController() {
            println(topController)
        }
Bobj-C
źródło