はじめに
複雑な画面遷移や複数の起動経路を持つようなアプリの画面遷移に、Storyboardを使用して実装するとSegueが入り乱れ大変なことになるようです。
学習の記録も兼ねて、単純な画面遷移を実装しました。
目的
可読性が良い&改修のしやすいコードにすること
前提
- 画面の装飾や遷移のためのボタンなどはStoryboardで設置している
※実際は全てをStoryboardに任せる訳にはいかないが、今回はサンプルのコードを簡潔にするためこのような手法を取りました。
- FirstView -> SecondView -> ThirdView と単純な画面遷移のみ
- それぞれの画面につき、ViewControllerとStoryboardを一つずつ使用
- Info.plistの
Main storyboard file base name
は削除する - SceneDelegateは削除し使用しない
※SceneDelegateはiOS13から追加された機能となり、iOS12以前にも対応可能にするためには予め削除しておく必要がある。
本記事ではSceneDelegateは使用せず、AppDelegateに起動コードを記述している。
4,5についての解説は本記事では省略しますが、以下の記事が分かりやすく参考になると思いますのでリンクさせて頂きました。いつもありがとうございます。
- 【Xcode11】Storyboardを使わずコードだけで画面を生成する方法
- SceneDelegateを使用せずにiOSでアプリをビルドするようにする
- iOS 13 から導入されるSceneDelegateとは
ファイル構成&プレビュー
ファイル構成 | プレビュー |
---|---|
ソースコード
①AppDelegate
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds) // ウィンドウをインスタンス化する
self.window = window
Router.showRoot(window: window) // 初期Viewの取得はRouterクラスで行う
return true
}
}
ViewControllerやNavigationControllerを取得するといった処理を、showRoot
というメソッドとしてRouterクラスに任せています。
UIWindowのみこちらでインスタンス化し、メソッドに引数として渡します。
②FirstViewController(最初のView)
import UIKit
class FirstViewController: UIViewController {
// ボタンを押した時の処理
@IBAction func tapGoSecond(_ sender: UIButton) {
Router.showSecond(fromVC: self)
}
}
遷移先のViewControllerを取得する処理をRouterクラスに任せています。
self.ViewControllerをメソッドの引数として渡します。
③SecondViewContrller(2つ目のView)
import UIKit
class SecondViewController: UIViewController {
// ボタンを押した時の処理
@IBAction func tapGoThird(_ sender: UIButton) {
Router.showThird(fromVC: self)
}
}
同じく、遷移先のViewControllerを取得する処理をRouterクラスに任せています。
self.ViewControllerをメソッドの引数として渡します。
④Router
前出の3つのクラスから画面遷移のメソッドをそれぞれ受け取っており、それらの処理を書いています。
import UIKit
final class Router {
// アプリ起動時にrootViewを取得する処理
static func showRoot(window: UIWindow?) {
let firstStoryboard = UIStoryboard(name: "First", bundle: nil)
let firstVC = firstStoryboard.instantiateInitialViewController() as! FirstViewController
let nav = UINavigationController(rootViewController: firstVC) // ナビゲーションコントローラーを定義。引数で最下層となるViewを指定
window?.rootViewController = nav
window?.makeKeyAndVisible()
}
// 2つ目のViewへ画面遷移する処理
static func showSecond(fromVC: UIViewController) {
let secondStoryboard = UIStoryboard(name: "Second", bundle: nil)
let secondVC = secondStoryboard.instantiateInitialViewController() as! SecondViewController
show(fromVC: fromVC, nextVC: secondVC)
}
// 3つ目のViewへ画面遷移する処理
static func showThird(fromVC: UIViewController) {
let thirdStoryboard = UIStoryboard(name: "Third", bundle: nil)
let thirdVC = thirdStoryboard.instantiateInitialViewController() as! ThirdViewController
show(fromVC: fromVC, nextVC: thirdVC)
}
// 実際に画面を遷移させる処理
private static func show(fromVC: UIViewController, nextVC: UIViewController) {
if let nav = fromVC.navigationController {
nav.pushViewController(nextVC, animated: true)
} else {
fromVC.present(nextVC, animated: true, completion: nil)
}
}
}
instantiateInitialViewControllerとは
instantiateInitialViewControllerはStoryboardクラスのメソッドで、「is initial view controller」が設定されているViewControllerを取得できる
※引数にstoryboardIDを直接指定して取得することも可能
showについて
showメソッドはNavigationControllerの有無をアンラップによって判別し、結果によって画面の表示方法を変えているもの
makeKeyAndVisibleとは
UIWindowクラスのインスタンスメソッド。
AppleDeveloperを見ると、
Shows the window and makes it the key window.
ウィンドウを表示し、それをキーウィンドウにします
とあります。
~以下は自分の認識なので間違っているかもしれません~
このメソッドを実行することによってキーウィンドウの指定と表示
を行う。つまり、ウィンドウの直上に設置するrootViewを決めている
ということ。
今回のサンプルでいうと、
window?.rootViewController = UINavigationController(rootViewController: firstVC)
// windowのrootViewControllerプロパティにはfirstVCが代入されている
window?.makeKeyAndVisible()
// 現在のwindowの状態をキーウィンドウとして表示する
ということになると解釈しています。
後語り
以上がコードで書く画面遷移のやり方となります。
今回はただ画面を進めるだけのシンプルな画面遷移でしたが、複雑な画面遷移を必要とするアプリケーションの場合、このように切り離すことで、可読性が良くなり改修もしやすくなると思います。
また、UIWindowの扱いであったりrootViewControllerの差し替えなどについては勉強不足で理解しきれていませんので、今後の課題としていきます。