Como encontrar o controlador de visualização mais alto no iOS

253

Encontrei alguns casos agora em que seria conveniente encontrar o controlador de exibição "mais alto" (o responsável pela exibição atual), mas não encontrei uma maneira de fazê-lo.

Basicamente, o desafio é este: dado que alguém está executando em uma classe que não é um controlador de exibição (ou uma exibição) [e não tem o endereço de uma exibição ativa] e não recebeu o endereço do controlador de exibição superior ( ou, digamos, o endereço do controlador de navegação), é possível encontrar esse controlador de exibição? (E, se sim, como?)

Ou, na sua falta, é possível encontrar a vista superior?

Hot Licks
fonte
Então você está dizendo que não é possível.
Hot Licks
@ Daniel não, estou dizendo que parece que o seu código poderia usar alguma reformulação, porque você raramente precisa saber disso. Além disso, a idéia de "superior" é válida apenas em certos contextos e, mesmo assim, nem sempre.
Dave DeLong 26/05
@ Daniel, eu interpretei mal sua pergunta. Existem muitos ifs e buts tentando responder a este. Depende do fluxo do seu controlador de exibição. A resposta de @ Wilbur deve ser um bom ponto de partida para identificá-la.
Deepak Danduprolu 26/05
Bem, vamos simplificá-lo para um caso específico. Se eu quisesse escrever um clone do UIAlertView, como eu faria isso? Observe que ele pode funcionar bem sem passar qualquer endereçamento a outros controladores ou visualizações.
Hot Licks
4
@ Daniel: A adição de um segundo UIWindow funciona bem para sobreposições de exibição de alerta.
Wilbur Vandrsmith

Respostas:

75

O iOS 4 introduziu a propriedade rootViewController na UIWindow:

[UIApplication sharedApplication].keyWindow.rootViewController;

Você precisará configurá-lo depois de criar o controlador de exibição.

Wilbur Vandrsmith
fonte
155
Wilbur, isso lhe dará o oposto do que a operação pediu. O rootViewController é o controlador de exibição base, e não o principal.
M4rkk
3
m4rkk: "Top-most" depende de qual direção você está procurando. Os novos controladores são adicionados ao topo (tipo pilha) ou ao fundo (tipo árvore)? De qualquer forma, o OP mencionou o controlador de navegação como estando no topo, o que implica uma visão crescente.
Wilbur Vandrsmith
50
A palavra "top" é usada para o controlador de exibição, visualmente na parte superior (como -[UINavigationController topViewController]). Depois, há a palavra “raiz”, que é a raiz da árvore (como -[UIWindow rootViewController].
Tricertops
13
@ImpurestClub Não consigo encontrar na documentação , o Xcode parece não encontrá-lo.
Drux
4
@adib não, que pertence a UINavigationController
David H
428

Eu acho que você precisa de uma combinação da resposta aceita e do @ 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
fonte
4
Além disso, você pode verificar UINavigationControllere solicitar o seu, topViewControllerou mesmo verificar UITabBarControllere solicitar selectedViewController. Isso fornecerá o controlador de visualização atualmente visível ao usuário.
Tricertops
33
Essa é uma solução incompleta, pois apenas atravessa a hierarquia dos controladores de exibição apresentados de forma modal, não a hierarquia dos childViewControllers (conforme usado por UINavigationController, UITabBarController, etc.).
amigos estão
3
Essa é uma ótima maneira de resumir a apresentação de um controlador de exibição modal que é retomado no estado atual do aplicativo; no meu caso, era uma tela de reinserção de senha após o tempo limite do aplicativo. Obrigado!
21813 erversteeg
11
@ alggal: na verdade não: UITabBarController, UINavigationController já são os controladores de exibição mais altos da hierarquia. Dependendo do que você deseja fazer com o "controlador superior", talvez você não queira atravessá-los e mexer no conteúdo deles. No meu caso, era para apresentar um controlador modal em cima de tudo, e para isso eu preciso obter o UINaviationController ou UITabBarController, não o conteúdo deles !
Rick77
1
@ Rick77, se isso for verdade, seu pequeno comentário aqui embaixo torna desnecessárias as toneladas de modificações complicadas nas outras respostas. Como ninguém mais menciona isso, sinto que tenho que pedir que você afirme que é verdade. E se for, é tão importante que merece ser uma resposta própria. Porque a grande maioria das outras respostas faz backflips tentando resolver esse problema. Você estaria salvando vidas!
Le Mot Juiced
150

Para concluir a resposta do JonasG (que deixou de fora os controladores de barra de guias enquanto atravessava), aqui está minha versão do retorno do controlador de visualização atualmente visível:

- (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
fonte
2
Bom, sim, eu esqueci controladores TabBar: P
JonasG
9
Não incluichildViewControllers
Awesome-o
Olhada na minha resposta abaixo da qual melhora a resposta acima por tratamento dos casos @kleo deixado de fora, tal como popovers, vista controladores adicionados como subvisualizações para alguns outros controladores de vista enquanto atravessa
Clive
Se você estiver usando return [self-topViewControllerWithRootViewController: navigationController.visibleViewController] ;, visibleViewController retornando o controlador de exibição apresentado (SE QUALQUER), mesmo que seja um UIAlertController. Para alguém que precisa evitar controlador de alerta ui, use topViewController vez de visibleViewController
Johnykutty
Só para adicionar meus 50 centavos a isso - eu estava lutando para fazer isso funcionar no meu controlador de exibição que carrega um webView. A razão pela qual não consegui fazer isso funcionar foi porque a exibição ainda não estava pronta (não terminou o carregamento) e, portanto, não era visível. Isso levou a uma situação em que a obtenção de um topViewContoller estava falhando, porque o UINavigationController estava tentando obter um ViewController visível enquanto ainda não havia um ViewController visível. Portanto, se alguém enfrentar esse problema, verifique se a visualização terminou de carregar antes de fazer uma chamada para o método topViewController acima.
Mbuster # 17/16
52

Uma versão não recursiva completa, cuidando de diferentes cenários:

  • O controlador de visualização está apresentando outra visualização
  • O controlador de exibição é um UINavigationController
  • O controlador de exibição é um UITabBarController

Objetivo-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
fonte
2
Eu o nomeei visibleViewControllerpara deixar claro o que faz.
21918 Jonny
31

Obtendo o maior controle de visualização do Swift usando extensões

Código:

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

Uso:

UIApplication.sharedApplication().keyWindow!.rootViewController!.topMostViewController()
Varuna
fonte
excelente - muito obrigado por esta solução. O subviews'trick era necessário! Mais uma vez, muito obrigado, você salvou o meu dia.
iKK 20/02
25

Para concluir a resposta de Eric (que excluiu popovers, controladores de navegação, controladores de barra de guias e controladores de exibição adicionados como subvisões a alguns outros controladores de exibição durante a navegação), aqui está minha versão do retorno do controlador de exibição atualmente visível:

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

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

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

E agora tudo o que você precisa fazer para obter o maior controle de visualização é chamar o método acima, da seguinte maneira:

UIViewController *topMostViewControllerObj = [self topViewController];
Rajesh
fonte
Também está faltando o SplitViewController?
apinho 16/06
21

Esta resposta inclui childViewControllerse mantém uma implementação limpa e legível.

+ (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
fonte
Atualizado algum código, devido também mostrar qual é o controlador, minimizando e restaurando-o novamente. nik-kov-ios-developer.blogspot.ru/2016/12/…
Nik
Ei, qual é o seu "topVisibleViewController"?
Paradise
12

Recentemente, peguei essa situação em um projeto, que exigia exibir uma visualização de notificação, independentemente do controlador exibido e do tipo (UINavigationController, controlador clássico ou controlador de exibição personalizado), quando o status da rede era alterado.

Então, acabei de liberar meu código, que é bastante fácil e, na verdade, baseado em um protocolo, para que seja flexível com todos os tipos de controladores de contêiner. Parece estar relacionado com as últimas respostas, mas de uma maneira muito flexível.

Você pode pegar o código aqui: PPTopMostController

E obteve o melhor controle usando

UIViewController *c = [UIViewController topMostController];
ipodishima
fonte
10

Esta é uma melhoria na resposta de Eric:

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) é uma função auxiliar.

Agora tudo o que você precisa fazer é ligar topMostController()e o UIViewController superior deve ser retornado!

JonasG
fonte
7
Desde 1983 eu diria. Lembre-se de que o Objective-C contém C ... A quebra do código ObjC nas funções C é uma prática comum, então sim, esse é o código Objective-C.
JonasG
@JonasG Oi Jonas, Em que circunstâncias você prefere agrupar o código ObjC em C? Porque, às vezes, vejo funções C assim e não consigo distinguir o uso. O código de quebra de linha em C oferece algum benefício de desempenho?
OzBoz
1
@OzBoz Em situações em que não está claro imediatamente a qual classe selfdeve pertencer.
adib 16/05
8

Aqui está a minha opinião sobre isso. Obrigado a @Stakenborg por apontar o caminho para pular a obtenção do UIAlertView como o principal controlador

-(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
fonte
Você deve evitar nomear métodos como getSomething:em Objective-C. Isso tem um significado especial (mais: cocoadevcentral.com/articles/000082.php ) e você não atende a esses requisitos no seu código.
Vive
7
@implementation UIWindow (Extensões)

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

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

    return topController;
}

@fim
FishStix
fonte
Eu não acho que você satisfez a condição indicada no post original.
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
fonte
Eu usei isso, mas nota que ele quebra quando há mais de um controlador de vista apresentado
Chuck Boris
7

Para a versão Swift mais recente:
Crie um arquivo, nomeie-o UIWindowExtension.swifte cole o seguinte trecho:

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
}

Use-o em qualquer lugar como:

if let topVC = getTopViewController() {

}
Ashok
fonte
Não quero alterar muito sua resposta, mas gostaria de sugerir algumas coisas. 1. Adicione suporte ao UISplitViewController. 2. use em switchvez de se mais. 3. não tendo certeza de que você também precisa de uma função estática, acho que você poderia fazer isso facilmente no primeiro nível de instância var que você declarou. 4. Provavelmente, é melhor não criar muitas funções globais, mas isso é uma questão de gosto. Você pode usar uma linha de código para conseguir o efeito da função global:UIApplication.sharedApplication().delegate?.window?.visibleViewController
Jordan Smith
7

Extensão simples para UIApplicationno Swift:

NOTA:

Ele se importa com moreNavigationControllerdentroUITabBarController

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

Uso simples:

if let topViewController = UIApplication.topViewController() {
    //do sth with top view controller
}
Bartłomiej Semańczyk
fonte
SIM SIM SIM - existem muitas soluções na web para encontrar o topMostViewController, mas se o aplicativo tiver uma barra de guias com uma guia Mais, DEVE lidar com isso um pouco diferente.
Andy Obusek
7

Use a extensão abaixo para pegar a corrente visível UIViewController. Trabalhou para o Swift 4.0 e posterior

Swift 4.0 e posterior:

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

Como usar?

let objViewcontroller = UIApplication.topViewController()
COVID-19
fonte
Este teste não deveria ser realizado presentedViewControllerprimeiro, antes dos casos UINavigationControllere UITabBarController? Caso contrário, se um controlador de exibição for apresentado modalmente a partir de UINavigationControllerou UITabBarController, ele não será retornado como o controlador de exibição superior, mesmo que seja o controlador de exibição visível.
Drew
4

Mais uma solução Swift

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
fonte
4

Extensão 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
    }
}

Use-o de qualquer lugar como,

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

ou como

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

Ajuste a qualquer classe como UINavigationController, UITabBarController

Aproveitar!

Saranjith
fonte
1
@BilalBakhrom diz "Voto positivo. Acho que sua resposta é a melhor. Você não pode chamar diretamente o método topViewController (). A classe UIApplication é singleton, use uma instância chamada" shared "." em uma edição que votei para rejeitar. Se isso estiver realmente correto, clique aqui para ser levado para um menu em que você pode aprovar essa edição.
precisa saber é o seguinte
3

Aqui está o que funcionou para mim.

Descobri que, às vezes, o controlador estava nulo na janela da chave, pois a keyWindow é algo do sistema operacional como um alerta, etc.

 + (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
fonte
3

Expandindo a resposta de @ Eric, você precisa ter cuidado para que a keyWindow seja realmente a janela que você deseja. Se você estiver tentando utilizar esse método depois de tocar em algo em uma exibição de alerta, por exemplo, a keyWindow será realmente a janela do alerta, o que causará problemas para você, sem dúvida. Isso aconteceu comigo na natureza ao lidar com links diretos por meio de um alerta e causar SIGABRTs com NO STACK TRACE. Cadela total para depuração.

Aqui está o código que estou usando agora:

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

Sinta-se à vontade para misturar isso com qualquer sabor de recuperação do controlador da vista superior que você gosta das outras respostas desta pergunta.

Stakenborg
fonte
Você achou que esta é uma solução completa? Muitas das outras respostas são extremamente complicadas, tentando explicar tantos casos extremos. Eu quero que isso seja verdade, é tão simples e elegante.
Le Mot Juiced
Eu nunca tive um problema com isso. Se você não estiver fazendo nada incomum com sua pilha de nav, isso deve funcionar, caso contrário, algumas das outras soluções lidam com casos mais complicados.
Stakenborg 29/11
3

Solução Swift alternativa:

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

    return topController!
}
Esqarrouth
fonte
3

Esta solução é a mais completa. Ele leva em consideração: UINavigationController UIPageViewController UITabBarController E o controlador de exibição mais apresentado do controlador de exibição superior

O exemplo está no Swift 3.

Existem 3 sobrecargas

//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
fonte
3

Uma solução concisa, porém abrangente, no Swift 4.2, leva em consideração UINavigationControllers , UITabBarControllers , controladores apresentados e filhos :

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

Uso:

let viewController = UIApplication.shared.topmostViewController()
nalexn
fonte
2

Ótima solução no Swift, implemente no 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
fonte
1

Acho que a maioria das respostas foi completamente ignorada UINavigationViewController, então lidei com esse caso de uso com a implementação a seguir.

+ (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
fonte
1

Eu sei que é muito tarde e pode ser redundante. Mas a seguir está o trecho que eu criei e que está funcionando para mim:

    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
fonte
0

Isso funciona muito bem para encontrar o viewController superior 1 a partir de qualquer controle de visualização raiz

+ (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
fonte
0

Não tenho certeza se isso ajudará o que você está tentando realizar ao encontrar o controlador de exibição mais alto, mas eu estava tentando apresentar um novo controlador de exibição, mas se meu controlador de exibição raiz já tivesse um diálogo modal, ele seria bloqueado. passaria para o topo de todos os controladores de exibição modal usando este código:

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

while( parentController.presentedViewController &&
       parentController != parentController.presentedViewController )
{
    parentController = parentController.presentedViewController;
}
Toland Hon
fonte
0

você pode encontrar o controlador mais visualizado usando

NSArray *arrViewControllers=[[self navigationController] viewControllers];
UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1];
Tapas Pal
fonte
Exceto que, se você realmente leu a pergunta, selfnão tem navigationControllerpropriedade.
Hot Licks
0

Outra solução depende da cadeia de respostas, que pode ou não funcionar, dependendo do que é a primeira resposta:

  1. Obtenha o primeiro respondedor .
  2. Obtenha o UIViewController associado a esse primeiro respondedor .

Exemplo de pseudo-código:

+ (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;
}
Sensível
fonte
0

Rápido:

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

Uso:

 if let topController = window.visibleViewController() {
            println(topController)
        }
Bobj-C
fonte