【Swift】コードでViewControllerを生成して画面遷移と値渡しをする方法

# はじめに
Storyboard は画面レイアウトの作成にだけ使用して、
ViewController の初期化と画面遷移はコードでやってしまいたい人向けです。
自分はよくこんな感じにやってます。

# 前準備
## ファイルの構成について
自分は Storyboard と ViewController は1対1になるようにしています。
なので基本的に Storyboard と ViewController の名前は同じにします。

こんな感じの構成です。
**FirstViewController** が遷移元の画面で、**SecondViewController** が遷移先の画面だと思ってください。

| フォルダの構成 |
| :---------: |
| <img width="246" alt="スクリーンショット 2020-05-06 20.46.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202306/5afeb82d-13ff-810a-a13b-2dfa44c9201e.png"> |

このとき Storyboard 側で **Custom Class** の設定と、
**Is Initial View Controller** の設定をすることを忘れないでください。

| Custom Class | Is Initial View Controller |
| :----------: | :------------------------: |
| <img width="259" alt="スクリーンショット 2020-05-06 20.49.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202306/e679dffc-7011-b29c-908c-6f075115d6a2.png"> | <img width="260" alt="スクリーンショット 2020-05-06 20.49.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202306/49f56b56-5895-9560-2cd9-3ca18c8f508c.png"> |

## ViewController について
ViewController はそれぞれこんな感じです。
例として SecondViewController の `number` に数字を渡したいとします。

```Swift
// FirstViewController

final class FirstViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}
```

```Swift
// SecondViewController

final class SecondViewController: UIViewController {
    
    // ここに値渡しで数字を渡したい
    var number: Int!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print(number)
    }
}
```

# 画面遷移
では本題に入ります。

画面遷移は Storyboard から ViewController を生成して、
ViewController の `present()` もしくは NavigationController の `pushViewController()` のどちらかで遷移します。

```Swift
// Storyboard から遷移先の ViewController を生成
// 簡易的なコードなので強制キャストしています。
let vc = UIStoryboard(name: "SecondViewController", bundle: nil).instantiateInitialViewController() as! SecondViewController

// `present()` で遷移する場合
present(vc, animated: true, completion: nil)

// NavigationController の `pushViewController()` で遷移する場合
navigationController?.pushViewController(vc, animated: true)
```

# 値渡し
画面遷移の段階で遷移先の ViewController を生成しているので、
そこに渡してしまうだけです。

```Swift
// Storyboard から遷移先の ViewController を生成
// 簡易的なコードなので強制キャストしています。
let vc = UIStoryboard(name: "SecondViewController", bundle: nil).instantiateInitialViewController() as! SecondViewController

// 遷移先の `number` に値を渡す
vc.number = 10

// ...
```

# おまけ: ViewController の初期化を簡単にする
Storyboard から ViewController を生成するコードは冗長で、
引数に Storyboard の名前を文字列として取るのでタイプミスも発生しやすいです。

なので Storyboard の初期化、初期化した Storyboard から ViewController の生成をまとめてやってしまう Static な関数を UIViewController の Extension として追加して、より簡単に初期化できるよう拡張します。

ただし、以下の Extension は **Storyboard と ViewController の名前が同じであること**を前提としています。

```Swift
extension UIViewController {
    static func instantiate() -> Self {
        guard let vc = UIStoryboard(name: String(describing: self), bundle: nil).instantiateInitialViewController() as? Self else {
            fatalError("Storyboard named \(String(describing: self)) was not found.")
        }
        return vc
    }
}
```

使用するときはこんな感じです。

```Swift
let secondVC = SecondViewController.instantiate()
```