自己紹介
はじめまして、ペンギン村で一番やかましい住人のナガクラ(@nagakuta)です!
Slackだけでなく、ブログもやかましく更新していきます!!(宣言)
TL;DR
RootViewController
をAppDelegate.window.rootViewController
に指定してから画面遷移するようにすると色々ラクだよ!Wireframe
も一緒に使うとテスト書くときラクだよ!!
RootViewController
による画面遷移
RootViewController
#とは
自分がRootViewController
について知ったのは、ペンギン村に貼られた以下の記事でした。
その記事のリンクにRootViewController
についての詳細記事がありました。
その記事中にて説明されているRootViewController
について簡潔にザザッと説明すると、
- 概念として、
Container ViewController
の延長である AppDelegate.window.rootViewController
に設定するViewControllerである- このViewControllerを起点に画面遷移を行うようにする
となります。なにこれ便利そう😲😲😲
そして、RootViewController
を実装するメリットをサクッと説明すると、
- 1つのナビゲーションスタックだけで新しいViewControllerを表示したり、インタフェースなしで戻したりすることができる
- なので、どんなにViewが重なっていようと、最下層のViewControllerをカンタンに取得、あるいは差し替えることができる
- 実は、
AppDelegate.window.rootViewController
を直接差し替えても、差し替え前のViewControllerがメモリ解放されないらしく、差し替えるたびにメモリ領域を圧迫する😨 AppDelegate.window.rootViewController
をRootViewController
に固定することで、メモリ領域の不必要な圧迫を防ぐ
の3点でしょうか。なにそれすごく便利そう😂😂😂
RootViewController
を実装してみる
では、実際にRootViewController
を実装してみます( ´∀`)(一部端折っている箇所があります🙇)
RootViewController.swift
final internal class RootViewController: UIViewController { // MARK: - Life Cycle Methods init() { super.init(nibName: nil, bundle: nil) } override viewDidLoad() super.viewDidLoad() // 起動直後に遷移させる画面を宣言 let launchViewController: UIViewController = LaunchViewController() // 子ViewのViewControllerを指定 self.addChildViewContrlller(launchViewController) // 子ViewをSubViewとして追加 launchViewController.view.frame = UIScreen.main.bounds self.view.addSubView(launchViewController.view) // 子Viewの所有権を譲渡 launchViewController.didMove(toParentViewController: self) } /// NextViewControllerに遷移 func transitToNextViewController() { let nextViewController: UIViewController // 子ビューのChildViewControllerを削除 let childViewController: UIViewController = self.childViewControllers.first! childViewController.willMove(toParentViewController: nil) childViewController.view.removeFromSuperView() childViewController.removeFromParentViewController() // 新しいViewControllerを子ビューとして追加 self.addChildViewController(nextViewController) nextViewController.view.frame = UIScreen.main.bounds self.view.addSubView(nextViewController.view) nextViewController.didMove(toParentViewController: self) } }
LaunchViewController.swift
final internal class LaunchViewController: UIViewController { // MARK: - Life Cycle Methods (中略) override viewDidAppear(_ animated: Bool) { let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate let rootViewController: UIViewController = appDelegate.window!.rootViewController as! RootViewController // NextViewControllerに遷移 rootViewController.transitToNextViewController() } (以下略) }
AppDelegate.swift
@UIApplicationMain internal class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // 起動直後に遷移する画面をRootViewControllerに指定する self.window = UIWindow(frame: UIScreen.main.bounds) self.window.rootViewController = RootViewController() self.window.makeKeyAndVisible() return true } (以下略) }
これで、起動 → RootViewControllerに遷移 → (RootViewControllerの子ビューをLaunchViewControllerに) → LaunchViewControllerに遷移 → (RootViewControllerの子ビューをNextViewControllerに置き換え) → NextViewControllerに遷移が実現できました( ´∀`)bグッ!
ここからNextViewControllerをログイン画面とするなり、メインコンテンツを表示させるなりするのが一般的なiOSアプリケーションの起動後の動きになると思います。
Wireframe
で画面遷移
Wireframe
#とは
Wireframe
というのをざっくりと説明すると、「画面遷移をViewControllerの代わりに行うための仕組み」です。
通常、画面遷移を実装する際はUIViewControllerのメソッドを利用して行うと思います。(先程のRootViewController
でも、Viewの上に重ねるという形で画面遷移を実装しています)
しかし、画面遷移をUIViewControllerクラスで行うと、少しViewControllerが膨らんでしまいますし、ViewControllerごとに同様の処理を何回も何回も実装するのはアホくs…スマートじゃないですよね😅
そんな時に使ってみたいのがWireframe
です。Wireframe
で画面遷移用のメソッドを実装し、ViewControllerクラスからそのメソッドを呼び出すようにすれば、いちいち画面遷移についてあーだこーだと考えずにViewControllerは画面表示のことに専念できます👍
また、遷移先の画面を指定できるようになれば、Unitテストの際にモックのViewControllerを指定することができるようになるため、テストを書くのが格段に楽チンになります😆😆😆
Wireframe
による画面遷移を実装してみる
ではでは、Wireframe
を実装してみましょう(`・ω・´)ガンバル
RootWireframe.swift
internal protocol Wireframe { /// 指定した画面に遷移 func transition(to viewController: UIViewController) } internal struct RootViewWireframe: Wireframe { func transition(to viewController: UIViewController) { let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate let rootViewController: RootViewController = appDelegate.window!.rootViewController as! RootViewController // RootViewControllerの子ビューを削除 if !rootViewController.childViewControllers.isEmpty { rootViewController.childViewControllers.forEach { (childViewController: UIViewController) in childViewController.willMove(toParentViewController: nil) childViewController.view.removeFromSuperView() childViewController.removeFromParentViewController() } // 以下はRootViewController.viewDidLoadメソッドの内容をそのまま移植 rootViewController.addChildViewController(viewController) viewController.view.frame = UIScreen.main.bounds rootViewController.view.addSubView(viewController.view) viewController.didMove(toParentViewController: rootView) } }
これでWireframe
の実装ができました👍
では、RootViewControllerの画面遷移をWireframe
に移譲します(`・ω・´)シャキーン
RootViewController.swift
final internal class RootViewController: UIViewController { let wireframe: RootViewWireframe = RootViewWireframe() (中略) override viewDidLoad() { super.viewDidLoad() // Wireframeによる画面遷移 let childViewController: UIViewController = UIViewController() self.wireframe.transition(to childViewController) } (以下略) }
LaunchViewController.swift
final internal class LaunchViewController: UIViewController { let wireframe: RootViewWireframe = RootViewWireframe() (中略) override func viewDidAppear(_ animated: Bool) { let nextViewController: UIViewController = UIViewController() self.wireframe.transition(to nextViewController) } (以下略) }
これで、Wireframe
による画面遷移が実装できました!(∩´∀`)∩ワーイ
これで、AppDelegate.window.rootViewController
の差し替えも容易になり、重なりまくった子ビューから最下層のViewを取得するのもRootViewController.childViewControllers.first
で取得できるようになります💪💪💪
あとがき
「RootViewController
+ Wireframe
による画面遷移」、いかがだったでしょうか😃😃😃
これからも、「楽に、最小効率で」を目的に勉強したTipsをこのブログで公開していきたいなと思います(`・ω・´)
次回更新もお楽しみに!!!