iOSのdidBecomeActive/willResignActiveを視覚的に把握する

# モチベーション
ライフサイクルメソッドの説明は、解説付きでケースを列挙されることが多いですが、いまいちイメージがしにくいと感じることが多く感じます。特に、ActiveとInactiveのタイミングは分かりにくいことが多いのではないでしょうか。

この記事では、Sceneベースの各種ライフサイクルメソッドにprintを貼って、active/inactiveとはどのようなステートなのかを視覚的に把握します。(※タイミングの列挙はしません。1つ2つ取り上げるだけです)

# 解説
## 1. アプリの起動
![スクリーンショット 2020-09-12 15.00.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114709/bbc2d28b-f733-71b9-9629-53ddc0383697.png)

## ライフサイクル
シーンベースのライフサイクルでも、共通系は呼ばれます。なので`didFinishLauching`がまず始めに呼ばれます。
次に、`SceneDelegate`の`willConnect`が呼ばれます。このメソッドは、システム・ユーザー起点にかかわらず、新しいシーンがアプリケーションに接続される直前に呼ばれます。そしてその後は順当に`willEnterForeground`, `didBecomeActive`と呼ばれていきます。

```
didFinishLaunching
willConnect
willEnterForeground
didBecomeActive
```


## 2. SplitViewの割合変化
もっとも分かりやすい状態遷移です。視覚的な状態と実際の状態が一致しており、直感的に理解できます。

### 中間状態
![スクリーンショット 2020-09-12 15.41.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114709/c02bcc10-d481-e0f6-a448-1f55953d3c20.png)

### ライフサイクル
中間状態では、システムによるSplitViewの操作中で、アプリケーション自体の操作はできないため、画面は前面に出ていますが`Inactive`な状態です。

```
didFinishLaunching
willConnect
willEnterForeground
didBecomeActive
willResignActive
```

### 完了
![スクリーンショット 2020-09-12 15.41.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114709/80513b4a-b5f2-ec36-ae27-d95998c84722.png)

### ライフサイクル
割合がユーザーによって決められ、操作が終了すると`didBecomeActive`が呼ばれていることが分かります。

注目すべきは、どちらの状態でも`foreground`であることは変わらず、`active/inactive`だけの変化であるということです。

```
didFinishLaunching
willConnect
willEnterForeground
didBecomeActive
willResignActive
didBecomeActive
```

## 3. SplitViewから閉じる
![スクリーンショット 2020-09-12 15.31.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114709/7eed827d-5217-11dc-fcc1-8d4667467b73.png)

### ライフサイクルメソッド
先程の中間状態からの復帰では`didBecomeActive`になりましたが、こちらはそのまま閉じてしまった(右側の比率を100%にした)ので、`didEnterBackground`が呼ばれました。

```
didFinishLaunching
willConnect
willEnterForeground
didBecomeActive
willResignActive
didBecomeActive
willResignActive
didEnterBackground
```

# まとめ
「ForegroundなのにInactiveな状態ってどのようなものなのだろう？」という疑問に対して、これまで「システム的にはそういう状態が存在する」と受け入れて理解していた人も、上記のSplitViewの例を見れば「前面に出ているのに、アプリケーションにアクセスできない瞬間が存在する」というのを視覚的に把握できたかと思います。

他には、アプリ一覧を開いた時に`Inactive`になったりしますが、詳細は[別の方の記事](https://qiita.com/itoz/items/cac51cc75cee5b35b0f0)をご参照ください。

# 実装

```AppDelegate.swift
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        print("didFinishLauching")
        return true
    }

    // MARK: UISceneSession Lifecycle

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        print("configurationForConnecting")
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

```

```SceneDelegate.swift
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let _ = (scene as? UIWindowScene) else { return }
        print("willConnect")

    }

    func sceneDidDisconnect(_ scene: UIScene) {
        // Called as the scene is being released by the system.
        // This occurs shortly after the scene enters the background, or when its session is discarded.
        // Release any resources associated with this scene that can be re-created the next time the scene connects.
        // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
        print("didDisconnect")
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state.
        // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
        print("didBecomeActive")

    }

    func sceneWillResignActive(_ scene: UIScene) {
        // Called when the scene will move from an active state to an inactive state.
        // This may occur due to temporary interruptions (ex. an incoming phone call).
        print("wiiResignActive")

    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // Called as the scene transitions from the background to the foreground.
        // Use this method to undo the changes made on entering the background.
        print("willEnterForeground")

    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // Called as the scene transitions from the foreground to the background.
        // Use this method to save data, release shared resources, and store enough scene-specific state information
        // to restore the scene back to its current state.

        // Save changes in the application's managed object context when the application transitions to the background.
        (UIApplication.shared.delegate as? AppDelegate)?.saveContext()
        print("didEnterBackground")

    }


}



```