AppDelegateから一番上のUIViewControllerにアクセスする方法

AppDelegateからUI操作を行う時、UIViewControllerが複数重なっている時は一番上(最前面)のUIViewControllerにアクセスする必要がありますよね。
今回はその方法について調べてみました。

#準備
以下の様な3つのUIViewControllerをSegueで順に起動するサンプルプログラムを作成します。
<img width="704" alt="AppDelegate.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362719/766878e3-db33-e8b4-040f-7b7471e70795.png">
青いUIViewControllerにはFirstViewController、黄色いUIViewControllerにはSecondViewController、赤いUIViewControllerにはThirdViewControllerというクラス名をつけておきます。

#処理
今回はAppDelegateのapplicationWillEnterForegroundに処理を記述しました。
ホームボタンやホームインジケータを押下してアプリを一度バックグラウンドにして、その後フォアグラウンドに復帰させた時に走るイベントです。

```swift:AppDelegate.swift
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

-------------------- (中略) --------------------
    func applicationWillEnterForeground(_ application: UIApplication) {
        //一番手前のViewControllerを取得
        var viewController = UIApplication.shared.keyWindow?.rootViewController
        
        //PresentedViewControllerが取得できなくなるまでループ
        while viewController!.presentedViewController != nil {
            
            let presentedViewController = viewController!.presentedViewController
            
            //UIAlertControllerは対象外とする
            if let alertController = presentedViewController as? UIAlertController {
                //一番上がUIAlertControllerであればUIAlertControllerを閉じる
                alertController.dismiss(animated: true, completion: nil)
                break
            } else {
                //UIAlertControllerでなければ取得
                viewController = presentedViewController
            }
        }
        
        var message: String = ""
        
        if viewController is FirstViewController {
            message = "1番目のViewControllerです"
        }
        
        if viewController is SecondViewController {
            message = "2番目のViewControllerです"
        }
        
        if viewController is ThirdViewController {
            message = "3番目のViewControllerです"
        }
        
        let alertController: UIAlertController = UIAlertController(title: "", message: message, preferredStyle: .alert)
        let defaultAction: UIAlertAction = UIAlertAction(title: "OK", style: .default, handler: nil)
        alertController.addAction(defaultAction)
        viewController!.present(alertController, animated: true, completion: nil)
    }
}
```
一番上のUIViewControllerを取得して、そこからアラートを表示する処理を記述しています。

サンプルプログラムを動かすとフォアグラウンドになった時に現在表示している画面をアラートで表示します。
![ezgif-4-4554282cb2d2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362719/712f90d5-8b59-8d74-b995-49ba30b39039.gif)

#解説
まずUIApplicationから[rootViewController](https://developer.apple.com/documentation/uikit/uiwindow/1621581-rootviewcontroller)(一番手前のUIViewController)を取得します。

```swift
var viewController = UIApplication.shared.keyWindow?.rootViewController
```

そしてrootViewControllerから[presentedViewController](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621407-presentedviewcontroller?language=objc)をループして取得します。

```swift
while viewController!.presentedViewController != nil {
    let presentedViewController = viewController!.presentedViewController
```

一番最後に返ってきたpresentedViewControllerが一番上のUIViewControllerになるのですが、ここで1点注意が必要です。
アラートを表示する[UIAlertController](https://developer.apple.com/documentation/uikit/uialertcontroller)や、App Extensionsを表示する[UIActivityViewController](https://developer.apple.com/documentation/uikit/uiactivityviewcontroller)もUIViewControllerを継承しているので、presentedViewControllerで返ってきます。
その為、一番上がUIAlertControllerやUIActiveViewControllerであった場合はその一つ手前のUIViewControllerが一番上のUIViewControllerになります。
今回のサンプルでもUIAlertControllerが表示されている可能性はあるので、UIAlertControllerだったらそれを閉じて、その一つ手前のUIViewControllerを一番上のUIViewControllerとして取得するようにしています。

```swift
while viewController!.presentedViewController != nil {
            
    let presentedViewController = viewController!.presentedViewController
            
    //UIAlertControllerは対象外とする
    if let alertController = presentedViewController as? UIAlertController {
       //一番上がUIAlertControllerであればUIAlertControllerを閉じる
       alertController.dismiss(animated: true, completion: nil)
       break
    } else {
       //UIAlertControllerでなければ取得
       viewController = presentedViewController
    }
}
```

どのUIViewControllerが一番上に来ているのかは「is」を使用してクラス名と型比較をすればわかります。

```swift
var message: String = ""
        
if viewController is FirstViewController {
   message = "1番目のViewControllerです"
}
        
if viewController is SecondViewController {
   message = "2番目のViewControllerです"
}
        
if viewController is ThirdViewController {
   message = "3番目のViewControllerです"
}
```