[Swift 5]RootViewControllerを適用する備忘録

例えば、初回起動時の利用規約確認ページの表示やウォークスルーの表示、またそれらの画面からメイン画面への移動について

AppDelegateを介して`window`の`rootViewController`を切り替えたりしていたのですが、
メモリの関係であまり推奨されないらしい。

今回は専用のRootViewControllerを用意し、そのchildを挿げ替えるという方法があると知り、実践しました。
あわせて、起動時にはSplashViewControllerを用意して何らかのデータ取得や表示先画面の分岐を行っています。
今後Splash画面をアニメーションさせたくなったときにも入れやすそうです。

##前提情報
個人的な好みですが、随所で`ViewController`を`VC`と略しています。

##まずは肝心のRootViewControllerの作成
```RootVC.swift
import UIKit

final class RootVC: UIViewController {
    // 現在表示しているViewControllerを示します
    private var current: UIViewController
    
    init() {
        // 起動時最初の画面はSplashViewControllerを設定します
        current = SplashVC()
        super.init(nibName: nil, bundle: nil)
    }
    // init()を実装したことによる必須実装
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // ViewControllerをRootVCの子VCとして追加
        addChild(current)
        current.view.frame = view.bounds
        view.addSubview(current.view)
        current.didMove(toParent: self)
    }
    
    /// RootVCの子VCを入れ替える＝ルートの画面を切り替える
    func transition(to vc: UIViewController) {
        // 新しい子VCを追加
        addChild(vc)
        vc.view.frame = view.bounds
        view.addSubview(vc.view)
        vc.didMove(toParent: self)
        // 現在のVCを削除する準備
        current.willMove(toParent: nil)
        // Superviewから現在のViewを削除
        current.view.removeFromSuperview()
        // RootVCから現在のVCを削除
        current.removeFromParent()
        // 現在のVCを更新
        current = vc
    }
    // 移動したいViewControllerごとに用意しておくと簡単に使用できる
    func transitionToMain() {
        // 切り替えたい先のViewControllerを用意
        let vc = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()!
        transition(to: vc)
    }
}
```

##AppDelegateで、最初に表示する画面をRootVCに設定する
```AppDelegate.swift

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 以下を追加
        // 最初に表示させるViewControllerにRootVCを指定する
        window = UIWindow(frame: UIScreen.main.bounds)
        window!.rootViewController = RootVC()
        window!.makeKeyAndVisible()

        return true
    }

    // その他メソッド省略…
}

extension AppDelegate {
    
    /// AppDelegateのシングルトン
    static var shared: AppDelegate {
        return UIApplication.shared.delegate as! AppDelegate
    }
    /// rootViewControllerは常にRootVC
    var rootVC: RootVC {
        return window!.rootViewController as! RootVC
    }
}
```

##SplashVC
```SplashVC.swift
import UIKit
/// 起動時、LauchScreenの後に表示される最初の画面。起動処理や遷移先画面の分岐を行う
final class SplashVC: UIViewController {
    /// 処理中を示すインジケーター
    private lazy var activityIndicator: UIActivityIndicatorView = {
        let indicator = UIActivityIndicatorView(style: .whiteLarge)
        indicator.frame = view.bounds
        indicator.backgroundColor = UIColor(white: 0, alpha: 0.4)
        return indicator
    }()
    
    /// 処理中に表示させておくスプラッシュ画像。LaunchScreenでも同じ画像を指定しておく
    private lazy var splashImage: UIImageView = {
        let imageView = UIImageView(
            image: UIImage(named: "Japanese01")
        )
        imageView.contentMode = .scaleAspectFill
        imageView.frame = view.frame
        return imageView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(splashImage)
        view.addSubview(activityIndicator)
        // 起動時に行いたい処理を実行する
        // UsecaseなりPresenterなり・・
        activityIndicator.startAnimating()

        // 中略

        // メイン画面へ移動
        AppDelegate.shared.rootVC.transitionToMain()
    }
}
```

###参考
https://medium.com/@stasost/ios-root-controller-navigation-3625eedbbff