Obtenha os melhores UIViewController

191

Não consigo obter o máximo de vantagem UIViewControllersem acesso a um UINavigationController. Aqui está o que eu tenho até agora:

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

No entanto, parece não fazer nada. Os valores keyWindowe também rootViewControllerparecem não-nulos, portanto, o encadeamento opcional não deve ser um problema.

NOTA: É uma má idéia fazer algo assim. Ele quebra o padrão MVC.

Zoyt
fonte
Aqui está uma solução alternativa disponível stackoverflow.com/a/39994115/1872233
iDevAmit

Respostas:

283

presentViewControllermostra um controlador de exibição. Não retorna um controlador de exibição. Se você não estiver usando um UINavigationController, provavelmente está procurando presentedViewControllere precisará começar pela raiz e percorrer as visualizações apresentadas.

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

    // topController should now be your topmost view controller
}

Para Swift 3+:

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

    // topController should now be your topmost view controller
}

Para 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
fonte
1
Alguém pode explicar o loop while? Para mim, parece que não há nada a fazer; Eu nem tenho certeza por que isso compila.
Professor Tom
15
@ProfessorTom O loop continua enquanto topController.presentedViewControllerretorna algo (ou seja, o controlador possui um controlador filho apresentado). É while letreforçar o fato de que topController.presentedViewControllerdeve retornar algo. Se retornar nulo (ou seja, este controlador não possui filhos apresentados), ele interromperá o loop. No corpo do loop, ele reatribui a criança como a corrente topControllere volta novamente, descendo a hierarquia do controlador de exibição. Ele pode ser reatribuído topController, pois é um varna ifdeclaração externa .
Rickbh
1
obrigado. Não consegui encontrar nenhum exemplo online de while let. Obviamente, há muitos if letexemplos a serem encontrados.
Professor Tom
1
A let x = somethingThatCouldBeNilsintaxe é um truque super útil para usar em qualquer lugar em que um valor / condição de verdade possa ser usado. Se não o usássemos aqui, teríamos que atribuir explicitamente um valor e testar para ver se ele realmente existe. Eu acho que é realmente sucinto e expressivo.
rickerbh
1
Estou familiarizado com o truque, é apenas um pouco mais difícil de raciocinar durante os loops while - pelos quais encontrei uma escassez de exemplos - especialmente este.
Professor Tom
270

tem essa extensão

Swift 2. *

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

Swift 3

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

Você pode usar isso em qualquer lugar do seu controlador

if let topController = UIApplication.topViewController() {

}
DLende
fonte
1
Obrigado pela sua dica de extensão :)
Thein
4
Tentei fazer uma edição importante desta resposta, mas ela foi rejeitada (não faço idéia do porquê e as razões do modelo fornecidas não faziam sentido): É importante verificar se o nav.visibleViewController é nulo antes de usá-lo na recursiva chamada (assim como a seleção de tab.selectedViewController) porque, caso contrário, se fosse nulo, você entraria em um loop infinito recursivo.
Ethan G
@ EthanG De acordo com o meu entendimento, se nav.visibleViewController for nulo, a função retornará nulo (vá para o último return). Como ele pode entrar em um loop infinito?
Desmond DAI
3
Eu acho que seria mais lógico fazer isso como função estática de UIViewController
Leszek Zarna
1
A verificação 'presentedViewController' provavelmente deve vir em primeiro lugar, se você quiser pegar controladores de vista modal apresentados UITabBarControllers ..
Tokuriku
65

Para Swift 4/5 + para obter a melhor vista

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

Como usar

if let topVC = UIApplication.getTopViewController() {
   topVC.view.addSubview(forgotPwdView)
}
Hardik Thakkar
fonte
2
Solução brilhante. Obrigado!
Andrey M.
2
'keyWindow' foi descontinuado no iOS 13.0.
rs7
2
'keyWindow' foi descontinuado no 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
            }
        }
    }
}

Uso:

if let topController = window.visibleViewController() {
    println(topController)
}
Bobj-C
fonte
esta solução se mostraram bastante promissoras, porém eu tentei executar este para obter o controlador de vista eu sou quando eu receber uma notificação push e jogou um erro nulo noreturn UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
Mike
@ Mike, você precisa usar apenas o PresentationViewController, não o PresentationViewController. apresentadoViewController
allaire
@allaire Se você apresentou um controlador de exibição modal em cima de um controlador de exibição modal .presentedViewController.presentedViewController, precisa ou não?
Baran Emre 30/10
6

Com base na resposta de Dianz, a versão do 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
fonte
Não funcionará para UINavigationController no UITabBarController. ele retornará UINavigationController, deve retornar o topController na navegação travada.
quer
Tnx Tnx Tnx Bro
reza_khalafi 3/17
6

Eu amei a resposta de @ dianz , e aqui está a versão rápida do 3. É basicamente a mesma coisa, mas ele estava com uma falta de chave e alguns dos nomes de sintaxe / variável / método foram alterados. Então aqui está!

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

O uso ainda é exatamente o mesmo:

if let topController = UIApplication.topViewController() {
    print("The view controller you're looking at is: \(topController)")
}
Ponyboy47
fonte
6

https://gist.github.com/db0company/369bfa43cb84b145dfd8 Fiz alguns testes nas respostas e comentários neste site. Para mim, os seguintes trabalhos

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

Em seguida, obtenha o viewController superior:

UIApplication.shared.topMostViewController()
Albert Zou
fonte
5

Use este código para encontrar os principais UIViewController

func getTopViewController() -> UIViewController? {
    var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    while topController?.presentedViewController != nil {
        topController = topController?.presentedViewController
    }
    return topController
}
Bibin Joseph
fonte
2
Como isso é diferente da resposta de rickerbh?
ElectroBuddha
5

Pequena variação no @AlberZou usando uma variável computada em vez de uma função

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

Então diga

if let topViewControler = UIApplication.shared.topMostViewController {
    ... do stuff
}
Ryan Heitner
fonte
4

Baseado em Bob -c acima:

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

Muitos sabores, mas nenhum elaborado iterativo. Combinado dos anteriores:

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

você pode definir uma variável UIViewController no AppDelegate e, em cada viewWillAppear, definir a variável como self (no entanto, a resposta dianz é a melhor resposta).

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
    appDel.currentVC = self
}
Arash Jamshidi
fonte
1
thanks a lot-lo funcionar bem para mim como a outra solução quando se tentar obter o navigationControll-lo retornar nil, então eu foi't capaz de empurrar qualquer novo vc
Amr irritado
Certifique-se de que o currentCV esteja definido como referência fraca, ou você terá vazamento de memória.
bubuxu
2

Para encontrar o viewController visível no Swift 3

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

     let prefs = UserDefaults.standard

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

     }
}

Esse código encontra o último controlador adicionado ou o último controlador ativo visível.

Isso eu usei no AppDelegate para encontrar o Active View Controller

Prateekro
fonte
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
fonte
Uso ambíguo de 'visibleViewController'
Omar N Shamali
1

Onde você inseriu o código?

Eu tento seu código na minha demonstração, descobri, se você colocar o código em

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

falhará, porque a janela principal já foi definida.

Mas eu coloquei seu código em algum view controller

override func viewDidLoad() {

Isso simplesmente funciona.

Tinyfool
fonte
Não está dentro didFinishLaunchingWithOptions. Eu só preciso disso para vários fins de depuração.
Zoyt
1

Em um caso muito raro, com segue personalizado, o controlador de visualização mais superior não está em uma pilha de navegação ou controlador de barra de guias ou é apresentado, mas sua visualização é inserida na parte superior das subvisões principais de windown.

Em tal situação, é necessário verificar se UIApplication.shared.keyWindow.subviews.last == self.viewpara determinar se o controlador de exibição atual é o mais superior.

BabyPanda
fonte
1

Para quem procura uma solução rápida 5 / iOS 13+ ( keywindowestá obsoleta desde o 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
fonte
0
  var topViewController: UIViewController? {
        guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }
        return topViewController
    }
Den
fonte
0

A melhor solução para mim é uma extensão com uma função. Crie um arquivo rápido com esta extensão

Primeiro é a extensão 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
            }
        }
    }
}

dentro desse arquivo adicionar função

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

E se você quiser usá-lo, pode chamá-lo em qualquer lugar. Exemplo :

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

O código do arquivo é assim :

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
fonte