一番上のUIViewControllerを取得する


191

UIViewControllerアクセスしないと、トップを獲得することはできませんUINavigationController。ここに私がこれまでに持っているものがあります:

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

ただし、何もしないようです。keyWindowそしてrootViewControllerオプションのチェーンは問題ではありませんので、あまりにも非nilの値であるように見えます。

注: このようなことをするのは悪い考えです。MVCパターンを壊します。


ここでは一つの代替ソリューション入手可能ですstackoverflow.com/a/39994115/1872233は
iDevAmit

回答:


283

presentViewControllerビューコントローラを示しています。ビューコントローラは返しません。を使用していない場合UINavigationController、おそらく探しているpresentedViewControllerので、ルートから始めて、表示されたビューを繰り返し処理する必要があります。

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

    // topController should now be your topmost view controller
}

Swift 3以降の場合:

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

    // topController should now be your topmost view controller
}

iOS 13以降の場合

let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

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

// topController should now be your topmost view controller
}

1
誰かがwhileループを説明できますか?私には、ループするものは何もないように見えます。これがコンパイルされる理由はよくわかりません。
トム教授、

15
@ProfessorTomループは、topController.presentedViewController何かが返される(つまり、コントローラーに提示された子コントローラーがある)限り継続します。それは何かを返さなければならないwhile letという事実を強制することtopController.presentedViewControllerです。nilが返された場合(つまり、これはコントローラーに子が提示されていない場合)、ループを停止します。ループの本体では、子を現在のとして再割り当てし、topController再びループして、View Controllerの階層を下っていきます。外側のステートメントtopControllerのように再割り当てできます。varif
rickerbh

1
ありがとうございました。の例をオンラインで見つけることができませんでしたwhile let。もちろん、if let見つけられる例はたくさんあります。
トム教授、

1
let x = somethingThatCouldBeNil構文は、真理値/条件を使用することができるどこでも使用する超便利なトリックです。ここで使用しなかった場合は、明示的に値を割り当て、実際に存在するかどうかをテストする必要があります。とても簡潔で表現力があると思います。
rickerbh

1
私はそのトリックに精通しています。whileループについて推論するのは少し難しいです。そのため、私は例の不足を発見しました–特にこれは。
トム教授、

270

この拡張子を持っている

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

スウィフト3

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

これはコントローラーのどこでも使用できます

if let topController = UIApplication.topViewController() {

}

1
拡張機能のヒントをありがとう:)
2015年

4
私はこの回答に重要な編集を加えようとしましたが、拒否されました(理由がわからないので、テンプレートの理由が意味をなさない):再帰的に使用する前に、nav.visibleViewControllerがnilであるかどうかを確認することが重要です呼び出し(tab.selectedViewControllerがチェックされる方法と同じ)。それ以外の場合、nilの場合、再帰的な無限ループに陥ります。
イーサンG

@EthanG私の理解によると、nav.visibleViewControllerがnilの場合、関数はnilを返します(最後にドロップしますreturn)。どのようにして無限ループに入ることができますか?
Desmond DAI

3
UIViewControllerの静的関数としてこれを作成する方がより論理的だと思います
Leszek

1
あなたはUITabBarControllersにモーダル提示View Controllerをキャッチしたい場合は、「presentedViewController」のチェックは、おそらく最初に来る必要があります...
Tokuriku

65

迅速な4/5 +の場合、最上位のviewControllerを取得します

// MARK: UIApplication extensions

extension UIApplication {

    class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return getTopViewController(base: nav.visibleViewController)

        } else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
            return getTopViewController(base: selected)

        } else if let presented = base?.presentedViewController {
            return getTopViewController(base: presented)
        }
        return base
    }
}

使い方

if let topVC = UIApplication.getTopViewController() {
   topVC.view.addSubview(forgotPwdView)
}

2
素晴らしいソリューション。ありがとう!
Andrey M.

2
'keyWindow'はiOS 13.0で廃止されました。
rs7

2
'keyWindow'は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
            }
        }
    }
}

使用法:

if let topController = window.visibleViewController() {
    println(topController)
}

このソリューションは非常に有望に見えましたが、プッシュ通知を受け取ったときにビューコントローラーを取得するためにこれを実行しようとすると、nilエラーが発生しましたreturn UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
Mike

@Mikeは、presentedViewControllerではなく、presentedViewControllerのみを使用する必要があります。presentedViewController
アライア

@allaireモーダルビューコントローラーの上にモーダルビューコントローラーを表示した場合.presentedViewController.presentedViewController、が必要かどうか。
Baran Emre 2018年

6

Dianzの回答に基づく、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;
}

UITabBarControllerのUINavigationControllerでは機能しません。UINavigationControllerを返します。ナビゲーションスタックのtopControllerを返す必要があります。
Mike.R 2016年

Tnx Tnx Tnx Bro
reza_khalafi 2017

6

私は@dianzの答えが好きだったので、ここにそれの迅速な3のバージョンがあります。基本的には同じですが、中かっこがなく、構文/変数/メソッド名の一部が変更されています。だからここにあります!

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

ただし、使用方法はまったく同じです。

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

6

https://gist.github.com/db0company/369bfa43cb84b145dfd8 このサイトの回答とコメントに対していくつかのテストを行いました。私にとって、次の作品

extension UIViewController {
    func topMostViewController() -> UIViewController {

        if let presented = self.presentedViewController {
            return presented.topMostViewController()
        }

        if let navigation = self as? UINavigationController {
            return navigation.visibleViewController?.topMostViewController() ?? navigation
        }

        if let tab = self as? UITabBarController {
            return tab.selectedViewController?.topMostViewController() ?? tab
    }

        return self
    }
}

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

次に、次のようにしてトップビューコントローラを取得します。

UIApplication.shared.topMostViewController()

5

このコードを使用して、最上位のUIViewControllerを見つけます

func getTopViewController() -> UIViewController? {
    var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    while topController?.presentedViewController != nil {
        topController = topController?.presentedViewController
    }
    return topController
}

2
これはrickerbhの回答とどう違うのですか?
ElectroBuddha 2017

5

関数ではなく計算された変数を使用した@AlberZouのわずかな変動

extension UIViewController {
  var topMostViewController : UIViewController {

    if let presented = self.presentedViewController {
      return presented.topMostViewController
    }

    if let navigation = self as? UINavigationController {
      return navigation.visibleViewController?.topMostViewController ?? navigation
    }

    if let tab = self as? UITabBarController {
      return tab.selectedViewController?.topMostViewController ?? tab
    }

    return self
  }
}

extension UIApplication {
  var topMostViewController : UIViewController? {
    return self.keyWindow?.rootViewController?.topMostViewController
  }
}

次に言う

if let topViewControler = UIApplication.shared.topMostViewController {
    ... do stuff
}

4

上記のBob -cに基づく:

Swift 3.0

extension UIWindow {


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

    class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {

        if vc.isKind(of: UINavigationController.self) {

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

        } else if vc.isKind(of: UITabBarController.self) {

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

        } else {

            if let presentedViewController = vc.presentedViewController {

                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)

            } else {

                return vc;
            }
        }
    }
}

3

フレーバーが多すぎますが、繰り返し作成されたものはありません。以前のものと組み合わせて:

     func topMostController() -> UIViewController? {
        var from = UIApplication.shared.keyWindow?.rootViewController
        while (from != nil) {
            if let to = (from as? UITabBarController)?.selectedViewController {
                from = to
            } else if let to = (from as? UINavigationController)?.visibleViewController {
                from = to
            } else if let to = from?.presentedViewController {
                from = to
            } else {
                break
            }
        }
        return from
    }

2

AppDelegateでUIViewController変数を定義し、すべてのviewWillAppearで変数をselfに設定できます(ただし、dianz回答が最良の回答です)。

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
    appDel.currentVC = self
}

1
おかげで、navigationControllを取得しようとするとnilが返され、新しいvcをプッシュできませんでした
Amr Angry

弱参照として定義されているcurrentVCを確認してください。そうしないと、メモリリークが発生します。
bubuxu

2

Swift 3で可視のviewControllerを見つけるには

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

     let prefs = UserDefaults.standard

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

     }
}

このコードは、最後に追加された、または表示されている最後のアクティブなコントローラーを見つけます。

これはAppDelegateでアクティブビューコントローラーを見つけるために使用しました


2
import UIKit

extension UIApplication {

    // MARK: Choose keyWindow as per your choice
    var currentWindow: UIWindow? {
        connectedScenes
        .filter({$0.activationState == .foregroundActive})
        .map({$0 as? UIWindowScene})
        .compactMap({$0})
        .first?.windows
        .filter({$0.isKeyWindow}).first
    }

    // MARK: Choose keyWindow as per your choice
    var keyWindow: UIWindow? {
        UIApplication.shared.windows.first { $0.isKeyWindow }
    }

    class func topMostViewController(base: UIViewController? = UIApplication.shared.currentWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return topMostViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            let moreNavigationController = tab.moreNavigationController

            if let top = moreNavigationController.topViewController, top.view.window != nil {
                return topMostViewController(base: top)
            } else if let selected = tab.selectedViewController {
                return topMostViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topMostViewController(base: presented)
        }
        return base
    }
}

「visibleViewController」のあいまいな使用
Omar N Shamali

1

コードをどこに配置しましたか?

私のデモであなたのコードを試してみました、あなたがコードを入れたら

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

キーウィンドウがまだ設定されているため、失敗します。

しかし、私はあなたのコードをいくつかのビューコントローラに入れました

override func viewDidLoad() {

うまくいきます。


にはありませんdidFinishLaunchingWithOptions。これは、さまざまなデバッグ目的で必要です。
Zoyt 2014年

1

非常にまれなケースですが、カスタムセグエでは、最上位のビューコントローラーがナビゲーションスタックまたはタブバーコントローラーにないか、表示されませんが、そのビューは主要なウィンドウのサブビューの上部に挿入されます。

このような状況でUIApplication.shared.keyWindow.subviews.last == self.viewは、現在のビューコントローラーが最上位であるかどうかを確認する必要があるかどうかを確認する必要があります。


1

迅速な5 / iOS 13+ソリューションをお探しの方(keywindowiOS 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
        }
    }
}

0
  var topViewController: UIViewController? {
        guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }
        return topViewController
    }

0

私にとって最良の解決策は、関数を使用した拡張機能です。この拡張子を持つSwiftファイルを作成します

最初はUIWindow拡張です:

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

    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

そのファイル内の追加機能

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

そして、それを使用したい場合は、どこでも呼び出すことができます。

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

ファイルコードは次のとおりです:

import UIKit

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

    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.