LoginSignup
51
29

More than 1 year has passed since last update.

iOS:最前面に画面を出す/最前面の画面を知る

Last updated at Posted at 2019-05-15

iOSアプリにおける、最前面のUIViewControllerについてまとめます。

※最新(2022年2月)のソースコードはこちらに記載しています。
https://www.fuwamaki.com/article/258

最前面のUIViewControllerは2種類あった

  1. UIApplication.shared.keyWindow?.rootViewController
  2. UIApplication.shared.delegate?.window??.rootViewController

の2種類、最前面のUIViewControllerを取得する方法があり、
これらを用いる際に区別するためにはどうすればいいか、
を考えたことがこの記事を書くことにした発端です。

他の方法が実はあるなど、知っている方がいればご教示ください。
また、以下自分の解釈が結構あるので、間違いあればご教示ください。

UIApplicationのkeyWindow

UIApplication.shared.keyWindowのref
https://developer.apple.com/documentation/uikit/uiapplication/1622924-keywindow

This property holds the UIWindow object in the windows array that is most recently sent the makeKeyAndVisible() message.

一番最近表示したWindow と自分は解釈しました。

その場合、keyWindow.rootViewControllerは
一番最近表示したUIViewController となります。

makiKeyAndVisibleのref

UIApplication DelegateにあるWindow

UIApplication.shared.delegate?.windowのref
https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623056-window

This property contains the window used to present the app’s visual content on the device’s main screen.
Implementation of this property is required if your app’s Info.plist file contains the UIMainStoryboardFile key. Fortunately, the Xcode project templates usually include a synthesized declaration of the property automatically for the app delegate. The default value of this synthesized property is nil, which causes the app to create a generic UIWindow object and assign it to the property. If you want to provide a custom window for your app, you must implement the getter method of this property and use it to create and return your custom window.

Viewをデバイスの最前面に表示するためのWindow と自分は解釈しました。

このWindowの利用は、MainStoryboardをInfo.plistに
設定することが前提となっているようです。

使い分けまとめ

最前面のUIViewControllerの利用は主に、

  • 最前面の画面を知りたい
  • 最前面に画面を出したい

の2つだと思います。これを元に表にするとこんな感じ。

最前面の画面を知りたい 最前面に画面を出したい
keyWindowTopViewController
topViewController ×

※keyWindowTopViewController...UIApplication.shared.keyWindow?.rootViewController
※topViewController...UIApplication.shared.delegate?.window??.rootViewController

正直、keyWindowTopViewControllerだけで両方の役割を果たせます。
しかし、下記の役割にする方が確実かと、自分は検証して思いました。

  • 最前面の画面が知りたければkeyWindowTopViewControllerを使う
  • 最前面に画面を出したければtopViewControllerを使う

keyWindowTopViewControllerで最前面に画面を出して不整合が出るケース

keyWindowTopViewControllerにはWindowを設定できます。
そのWindowの上に別の画面を出したい場合、
keyWindowTopViewController.present で画面を表示する場合、
下記のような不整合が発生します。

左側が今回の対象ケース、右側がtopViewControllerを用いたケースの
rootViewControllerを出力した結果です。

左側はほとんどはnil, viewだけ何故か取得できていることになります。
これはどうゆう状況かというと、

表示したい画面は表示されたけど、何も操作できない という状態です。

必ずしもこういうケースになるとは限らないと思いますが、
keyWindowTopViewControllerで最前面に画面を表示するのは
避けたほうが良いと思いました。

結論を元にしたextension

以上のことを考慮し、便利であろうextensionを考えてみました。
どちらも最前面をたどる実装にしています。

import UIKit

extension UIApplication {
    // 最前面の画面を知るために用いる。
    class func keyWindowTopViewController(on controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return keyWindowTopViewController(on: navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController,
            let selected = tabController.selectedViewController {
            return keyWindowTopViewController(on: selected)
        }
        if let presented = controller?.presentedViewController {
            return keyWindowTopViewController(on: presented)
        }
        return controller
    }

    // 最前面に画面を表示するために用いる。
    class func topViewController() -> UIViewController? {
        guard let rootViewController = UIApplication.shared.delegate?.window??.rootViewController else { return nil }
        var presentedViewController = rootViewController.presentedViewController
        if presentedViewController == nil {
            return rootViewController
        } else {
            while presentedViewController?.presentedViewController != nil {
                presentedViewController = presentedViewController?.presentedViewController
            }
            return presentedViewController
        }
    }
}

使い方例:

if UIApplication.keyWindowTopViewController() is HogeViewController {
    //処理
    return
}
UIApplication.topViewController()?.present(hogeViewController, animated: true, completion: nil)

まとめ

「いや、〇〇という例外があるので違う気がします」など指摘あればご教示ください。
ぜひ他の人にも↑使ってもらって試して頂ければと思います。

参考

51
29
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
51
29