画面遷移について網羅的に整理した (Swift5)

畑田です。   
開発ペース早すぎるので画面遷移についてまとめて時間を潰しています。   
全てコードで書いています。   

## 環境
- Swift version 5.2.4   
- Xcode version 11.6   

## UINavigationControllerで管理する
まず前提として、コードだけで実装する際の画面遷移は`UINavigationController`で管理することによって実現されます。   
`SceneDelegate`クラスの`scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)`メソッドの中で初めに表示されるview controllerを設定します。   
このときに、初めのview controllerを`UINavigationController`のインスタンスとしてしまい、その`rootViewController`(0番目のview controller)プロパティに実際に表示させたいview controllerを渡してあげると、それ以降のview controllerがnavigation controllerで管理されます。   
以下ソースコードです。

```swift:SceneDelegate.swift
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 windowScene = (scene as? UIWindowScene) else { return }
        
        let window = UIWindow(windowScene: windowScene)
        
        let initialViewController = ViewController()
        let navigationViewController = UINavigationController(rootViewController: initialViewController)
        window.rootViewController = navigationViewController
        self.window = window
        self.window?.makeKeyAndVisible()
    }

    // skip details

}
```

## いざ画面遷移
次にそれぞれのview controllerで、ボタンが押されたときに呼ばれるメソッドの中において画面遷移を記述していきます。   
navigation controllerはスタック空間を持っていて、新しく作ったview controllerインスタンスをそのスタック空間にpushすることで画面遷移しています。   
以下ソースコードです。

```swift
@objc private func nextButtonDidTapped() {
    let nextViewController = SecondViewController()
    self.navigationController?.pushViewController(nextViewController, animated: true)
}
```

## 一つ前の画面に戻る画面遷移
そもそも一つ前の画面に戻る挙動は、navigation controllerにデフォルトでついているnavigation barのbackボタンや、画面の左縁から右方へのスワイプで実現できます。   
これはnavigation controllerの仕様です。   
しかしながら、navigation barを取り去ったり、スワイプでの画面遷移を無効にしたりしているときに戻る動作を行いたい場合もあると思います。   
ということで、改めて一つ前の画面に戻るためのコードを書いていきます。   
具体的には戻るボタンを配置し、そのボタンが押されたときに呼ばれるメソッドの中に今の画面をnavigation controllerから放り出すコードを記述します。   
やっていることとしてはnavigation controllerのスタック空間にpushしたview controllerをpopさせているに過ぎません。

```swift
@objc private func backButtonDidTapped() {
    // back to the previous view controller
    self.navigationController?.popViewController(animated: true)
}
```

## 二つ以上前に画面に戻る画面遷移
二つ以上前の画面に戻る動作はnavigation controllerのデフォルトの機能として備わっていないので、独自に記述してあげる必要があります。   
以下では、navigation controllerのスタック空間に存在するview controllerの数から現在のindexを取得し、その二つ前のview controllerに戻っています。   
`popViewController(animated:)`メソッドの代わりに`popToViewController(_:animated:)`を呼んであげると最初の画面に戻すことができます。

```swift
@objc private func backButtonDidTapped() {
    guard let _ = self.navigationController else { return }
    let currentIndex = self.navigationController!.viewControllers.count - 1
    self.navigationController!.popToViewController(self.navigationController!.viewControllers[currentIndex - 2], animated: true)
}
```

## 情報を次のview controllerに直接渡す
次のview controllerに何らかの情報を受け渡したいとき、URLを使ったり、`UserDefaults`を利用したりすることで実現できますが、最も簡単なのが直接値や参照を渡してしまうことです。   
以下のように次のview controllerのインスタンスに情報を渡してから画面遷移します。

```swift
@objc private func nextButtonDidTapped() {
    let nextViewController = SecondViewController()
    nextViewController.info = self.information
    self.navigationController?.pushViewController(nextViewController, animated: true)
}
```

## modalな画面遷移
modal viewというのは画面下方からヌッと出てきて、下にスワイプすると帰っていくようなviewです。   
これは`present(_:animated:completion:)`メソッドを使うと簡単に実現できます。   
completionは任意でつけてみてください。

```swift
@objc private func nextButtonDidTapped() {
    let nextViewController = SecondViewController()
    self.present(nextViewController, animated: true, completion: nil)
}
```

## modal viewから戻るコード
modal viewは基本的には下にスワイプすると消えますが、何らかの処理が走ったときに、完了の動作として自動で消えて欲しいときもあります。   
これは`dismiss(animated:completion:)`で非常に簡単に実現できます。   
以下では、ボタンを押したときに呼ばれるメソッドの中に書いていますが、処理の完了時に呼ばれるように書いても走ります。

```swift
@objc private func backButtonDidTapped() {
    self.dismiss(animated: true, completion: nil)
}
```

## `show`メソッドっていうのがある、、、
`show(_:sender:)`というメソッドを使っても次の画面への遷移を実装できます。   
これはnavigation controllerが存在すれば、`pushViewController(_:animated:)`と同様な挙動を示し、存在しなければ、`present(_:animated:completion:)`と同様な挙動を示します。   
明示的にメソッドを呼ばないことにメリットを感じないので、あまり使った経験はありません。