はじめに
前回はiOSアプリにおける画面遷移の種類についてお話しました。
画面遷移編2:https://qiita.com/euJcIKfcqwnzDui/items/6d37aaf00c0bc7ce26ca
今回はその具体的な実装方法について説明していきます。
以前モーダル遷移の実装をしたプロジェクトをベースとして説明していきます。
前準備
その前に、今までプロジェクト作成されたときに生成されたViewController.swiftとMain.storyboardをそのまま使ってきましたが、画面が多くなるとわかりにくくなるので変更しておきましょう。
それぞれFirstViewController.swiftとFirstViewController.storyboardにリネームします。
まずはファイル名をリネームします。
ファイルを選択しもう一度ファイル名をクリックするとリネームできます。

次はクラス名を変更します。
import UIKit
class FirstViewController: UIViewController { // ViewControllerから変更
/// テキストフィールド
@IBOutlet weak var textField: UITextField!
/// 画面遷移ボタンタップ処理
/// - Parameter sender: ボタン
@IBAction func didTapTransitionButton(_ sender: Any) {
...
...
クラス名を変更すると今までそのクラスを使用していた場所も変更してあげないといけません。
今の状態だとFirstViewController.storyboardのViewControllerオブジェクトに設定されています。
FirstViewController.storyboardを開きViewControllerオブジェクトを選択します。
その状態で[Identity Inspector]を選択し、[class]の項目をFirstViewControllerと入力します。

ここで一度シミュレータで実行してみましょう。
すると、起動直後アプリがクラッシュし以下のエラーが表示されます。
Thread 1: Exception: "Could not find a storyboard named 'Main' in bundle NSBundle ...
「Mainという名前のstoryboardが見つからない」とのこと。
Main.storyboardもリネームしているので当然です。
アプリを起動した後どのstoryboardから表示するのかXcodeに教えてあげなければいけません。
プロジェクトを作成した段階ではMain.storyboardが指定されています。
これをFirstViewController.storyboardに変更してあげます。
フォルダ構成の一番上、プロジェクト名を選択します。
ここではプロジェクトの全体的な設定をすることができます。
[TARGETS]にはいくつかありますが一番上のプロジェクト名を選択します。
さらに上のタブで[General]を選択。
[Development Info]に[Main Interface]という項目があるのでプルダウンからFirstViewController.storyboardを選択してください。

さらにXcode11からInfo.plistというファイルにも「Main」が設定されています。
その中の[Storyboard Name]の項目を「FirstViewController」と書き換えてください。

これでファイル名の変更は完了です。
シミュレータで実行し問題ないか確認してください。
モーダル
これは以前説明したので省きます。
https://qiita.com/euJcIKfcqwnzDui/items/679b1cd30694519f4916#%E7%94%BB%E9%9D%A2%E9%81%B7%E7%A7%BB%E3%81%99%E3%82%8B
プッシュ
segueを使った実装
プッシュの実装方法について説明します。
プッシュの画面遷移には以前説明したsegueを使った実装をします。
まずはMain.storyboardを開きます。
プッシュの遷移にはUINavigationControllerというクラスを使用します。
このUINavigationControllerをStoryboardに追加しましょう。
FirstViewControllerを選択します。
その状態でXcode上部のツールバーから[Edior]>[Embed In]>[Navigation Controller]を選択していってください。

FirstViewControllerにくっつくような形でNavigation Controllerが追加されました。

Navigation Controllerは画面のような見た目をしていますが、これはあくまでStoryboard上の表示での話であって実際はユーザから見られる画面というわけではありません。
UINavigationControllerはControllerとあるようにコントロールするためのクラスです。
Navigationは画面遷移と言い換えてもらえればいいかと思います。
つまりUINavigationControllerは画面遷移を制御するためのクラスであって画面そのものではありません。
今のNavigation Controllerを追加した操作はUINavigationControllerという概念のようなオブジェクトをFirstViewControllerに追加するというイメージです。
もう少し補足するとUIViewControllerもControllerとあるように、これ自体が画面というわけではありません。
これも画面をコントロールするオブジェクトで、実際に表示されている画面はFirstViewControllerの中にあるViewというUIです。
コントローラは画面の表示制御やユーザからのイベントを扱うという役割です。
少し細かい話をしてしまいました。
実装を進めます。
FirstViewControllerにあるsegueを選択してください。
その状態で[Attributes Inspector]を選択すると[Kind]という項目があるので選択します。

ポップアップメニューが表示されるので[Show(e.g. Push)]を選択します。
これでsegueがプッシュ遷移用に変更されました。

Storyboardの設定はこれだけです。
あとはソースコードから処理実行するだけです。
ですがこれはモーダル遷移の場合と同じでperformSegueを呼び出してあげるだけです。
segueのidentifierを変えていないので以前モーダルを実装したままのコードですでにプッシュ遷移ができます。
該当のコードは以下です。
self.performSegue(withIdentifier: "SecondViewController", sender: nil)
実装は以上です。
シミュレータで実行し動作を確認してみてください。
またパラメータもモーダルと同様の方法で渡すことができます。
let text = textField.text
self.performSegue(withIdentifier: "SecondViewController", sender: text)
UINavigationControllerについて
UINavigationControllerでは一連のプッシュ遷移における画面を管理します。
UINavigationControllerはrootViewControllerというプロパティを持ち、プッシュ遷移の一番最初の画面を保持します。
Storyboardから追加した今回の場合だと、UINavigationControllerを追加したFirstViewControllerがrootViewControllerになります。
またUINavigationControllerはviewControllers: [UIViewController]というプロパティを持っており、遷移した画面を配列で管理しています。
今回の例では画面遷移が完了した状態ではviewControllers[0]にFirstViewController、viewControllers[1]にSecondViewControllerが格納されています。
プッシュ遷移先のSecondViewControllerに[戻る]ボタンが表示されていたかと思います。
[戻る]ボタンをタップすると一画面前に戻ります。
画面を戻るとSecondViewControllerはUINavigationControllerの管理対象からはずれ解放されます。
つまりviewControllers[1]は存在しなくなります。
図で表すと以下のようになります。



SecondViewControllerからさらに遷移する
当然SecondViewControllerからThirdViewControllerという画面にさらにプッシュ遷移した場合管理下に追加されviewControllers[2]に格納されます。
実装してみます。
これは特に難しい話ではなく同様にSecondViewControllerからThirdViewControllerへのsegueを作ってあげてperformSegueを呼び出してあげるだけです。
SecondViewControllerを追加した手順と同様にThirdViewController.swift、ThirdViewController.storyboardを追加以下のように編集してください。
import UIKit
class ThirdViewController: UIViewController {
}
さらにSecondViewController.storyboardからThirdViewControllerへのsegueをプッシュで作成してください。
またSecondViewController.storyboardにボタンを追加し、ボタンタップでThirdViewControllerに遷移するようにします。
シミュレータで実行すると3画面遷移できるようになります。

/// 画面遷移ボタンタップ処理
/// - Parameter sender: ボタン
@IBAction func didTapTransitionButton(_ sender: Any) {
// 画面遷移
self.performSegue(withIdentifier: "ThirdViewController", sender: nil)
}
ここで注意してほしいことはSecondViewController.storyboardにUINavigationControllerは追加しないということです。
UINavigationControllerはrootとなる画面にのみ追加します。
今rootはFirstViewControllerとしているためSecondViewControllerはrootではありません。
SecondViewControllerからプッシュ遷移する場合、rootに設定したUINavigationControllerが参照されます。
予想がつくかと思いますがThirdViewControllerで[戻る]ボタンを押すと以下のような構成になります。

このようにUINavigationControllerでは最後に追加されたものから消えていきます。
この管理方法を**スタック(stack)と呼びます。
余談ですがスタックは画面管理の方法ではなく配列の管理方法の1つです。
スタックは基本情報試験にも出てくるIT技術の基礎知識なので抑えておきましょう。
別の配列管理方法ではキュー(queue)**があります。一度調べておいてください。
UINavigationControllerの参照のされ方
ここまでの画面遷移処理で少し違和感を覚える方もいるかもしれません。
rootであるFirstViewControllerはStoryboardでUINavigationControllerのオブジェクトを追加したので画面遷移にUINavigationControllerを使えるのはなんとなくわかるかと思います。
ですがSecondViewControllerにはUINavigationControllerは設定されていないはずなのにどうして使えるのでしょうか?
実はUIViewControllerにはnavigationControllerというように現在自分が管理されているUINavigationControllerをプロパティとして持っています。
画面遷移の具体的な処理はUIKitが勝手にやってくれているので詳細は不明なのですが、おそらくプッシュ遷移のタイミングで自分のnavigationControllerを次の画面に一緒にセットしています。
従って1つのUINavigationControllerのオブジェクトが次へ次へと共有されているのです。
図にすると以下のようなイメージになります。

このような形でroot画面からプッシュ遷移した画面の全てを管理しています。
タブ
segueを使った実装
タブ遷移の実装方法について説明します。
タブに関してもsegueを使った実装をします。
タブの遷移にはUITabBarControllerというクラスを使用します。
UITabBarControllerを扱うために、まずTabViewController.storyboardというファイルを追加しましょう。

TabViewController.storyboardを開き、[+]ボタンからUITabBarControllerを探しStoryboardに追加します。

Tab Bar Controllerというオブジェクトと一緒にitem 1 Sceneとitem 2 Sceneというオブジェクトが追加されました。
これはタブTab Bar Controllerがデフォルトで表示する画面テンプレートです。
今回は不要なので削除します。
それぞれ選択し削除しておいてください。

Tab Bar Controllerも画面のような形ですが、これも「Controller」とあるように画面そのものではなくタブ遷移において画面を管理するオブジェクトです。
階層1つしたにあるTab Barというオブジェクトは画面一番下に表示されるタブ自体です。これは表示されるUIでUITabBarというクラスです。
このTabBarController.storyboardで最初に参照するオブジェクトはTabBarControllerなので[Is Initial View Controller]のチェックを付けます。

TabBarControllerが画面を追加します。
FirstViewController.storyboard、SecondViewController.storyboard、ThirdViewController.storyboardへの参照を追加します。
他の画面遷移と同様Storyboard Referenceを画面数だけ配置します。
配置したStoryboard ReferenceにそれぞれStoryboardを設定します。

TabBarControllerからそれぞれのStoryboard Referenceまでsegueを作成します。
segueの種類は[Relationship Segue]の[view controllers]を選択してください。

以下のようになればタブ遷移の準備は完了です。

次はアプリ起動直後に表示するStoryboardを変更します。
先述したMain.storyboardをリネームする際、説明したようにプロジェクトの[Main Interface]とInfo.plistの[Storyboard Name]を「TabBarController」に変更してください。


シミュレータで実行するとタブバーが表示されます。
タブバーには何も表示されていませんが、タブボタン自体は配置されています。
適当な場所をタップするとタブ遷移されます。

ですがこれではユーザからすると何のUIかわからないので画像と名前を設定してあげましょう。
SecondViewController.storyboardを開いてください。
[+]ボタンからUITabBarItemを探し、SecondViewControllerに追加してください。
UITabBarItemはタブボタンのオブジェクトです。
ここにアイテム名や画像を設定してあげるとそれがタブに表示されます。
UITabBarItemを選択し、[Attributes Inspector]の[BarItem]に[Title]と[Image]を設定してください。
※画像はまだ追加していないので標準でXcodeに入っているものが出ます。自分で画像を追加していくこともできます。

ThirdViewController.storyboardにも同様の手順で追加します。
FirstViewController.storyboardについても本来同様の手順でよかったのですが、今回UINavigationControllerが追加されています。
そのためFirstViewControllerオブジェクトに追加しても表示されません。
この場合はNavigationControllerオブジェクトに追加してあげてください。
[Is Initial ViewController]のチェックを入れたオブジェクトに追加する必要があると覚えましょう。

シミュレータで実行するとタブアイテムが表示されます。

UITabBarControllerについて
UITabBarControllerもUINavigationControllerと同様に画面を管理するクラスです。
ですがあくまで並列な画面遷移のためrootというものは存在しません。
表示される画面は同様にviewControllersで管理されています。
タブ遷移ではプッシュ遷移と違い画面を消すということが基本的にはないためviewControllersはスタック管理されているというわけではありません。
ただ配列として画面を持っているというだけです。
またUIViewControllerはtabBarControllerを持っており、それぞれのViewControllerが同一のUITabBarControllerを参照しています。
プッシュ遷移とタブ遷移の組み合わせ
実際のアプリではプッシュ遷移とタブ遷移両方を組み合わせて画面遷移を実装していきます。
また、今回のFirstViewControllerではすでに両方の遷移をしています。
FirstViewControllerにはNavigationControllerオブジェクトが追加されているからですね。
今回の画面の構成を正確に書くと以下のようになります。

若干複雑な関係となります。
図を見るとNavigationControllerもtabBarControllerというプロパティを持っています。
これはUINavigationControllerがUIViewControllerを継承したクラスのためです。
同様にUITabBarControllerもUIViewControllerを継承しているためnavigationControllerというプロパティを持ちます。
そのためさらにTabBarControllerにNavigationControllerを持たせることもできます。
この図をいますぐ理解できる必要はありませんが、実際にアプリを作る際には自分がどんな構成の画面遷移を実装しているのか図に起こせるように理解できるようにしましょう。
最後に
今回でiOSの画面遷移編は終了です。
モーダル、プッシュ、タブそれぞれの遷移に意味があり実装方法が多少異なります。
積極的に使って自分のものにしていきましょう。
最後の画面遷移の組み合わせはiOSを学ぶ上で躓く部分かもしれません。
これは画面遷移を実装→図に起こす→画面遷移を実装...と繰り返して感覚的に身につけていってください。
次回はライフサイクルという画面表示非表示になる際にコールされるイベントについて説明します。
https://qiita.com/euJcIKfcqwnzDui/items/d50dc4ec1ccc798fc03e
本連載ではプログラミング未経験からiOSアプリ開発が行えるようになることを目的としています。
今までの投稿をまとめていますのでこちらもご覧ください。
http://naoyalog.com/

