Help us understand the problem. What is going on with this article?

【iOS】iOSアプリ開発入門~ 画面遷移編3~

はじめに

前回はiOSアプリにおける画面遷移の種類についてお話しました。
画面遷移編2:https://qiita.com/euJcIKfcqwnzDui/items/6d37aaf00c0bc7ce26ca

今回はその具体的な実装方法について説明していきます。
以前モーダル遷移の実装をしたプロジェクトをベースとして説明していきます。

前準備

その前に、今までプロジェクト作成されたときに生成されたViewController.swiftMain.storyboardをそのまま使ってきましたが、画面が多くなるとわかりにくくなるので変更しておきましょう。
それぞれFirstViewController.swiftFirstViewController.storyboardにリネームします。

まずはファイル名をリネームします。
ファイルを選択しもう一度ファイル名をクリックするとリネームできます。
スクリーンショット 2020-05-30 17.23.30.png

次はクラス名を変更します。

FirstViewController.swift
import UIKit

class FirstViewController: UIViewController { // ViewControllerから変更
    /// テキストフィールド
    @IBOutlet weak var textField: UITextField!

    /// 画面遷移ボタンタップ処理
    /// - Parameter sender: ボタン
    @IBAction func didTapTransitionButton(_ sender: Any) {
...
...

クラス名を変更すると今までそのクラスを使用していた場所も変更してあげないといけません。
今の状態だとFirstViewController.storyboardViewControllerオブジェクトに設定されています。
FirstViewController.storyboardを開きViewControllerオブジェクトを選択します。
その状態で[Identity Inspector]を選択し、[class]の項目をFirstViewControllerと入力します。
スクリーンショット 2020-05-30 17.30.10.png
ここで一度シミュレータで実行してみましょう。
すると、起動直後アプリがクラッシュし以下のエラーが表示されます。

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を選択してください。
スクリーンショット 2020-05-30 17.39.52.png
さらにXcode11からInfo.plistというファイルにも「Main」が設定されています。
その中の[Storyboard Name]の項目を「FirstViewController」と書き換えてください。
スクリーンショット 2020-05-30 17.51.01.png

これでファイル名の変更は完了です。
シミュレータで実行し問題ないか確認してください。

モーダル

これは以前説明したので省きます。
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]を選択していってください。
スクリーンショット 2020-05-29 23.31.34.png
FirstViewControllerにくっつくような形でNavigation Controllerが追加されました。
スクリーンショット 2020-05-29 23.33.39.png
Navigation Controllerは画面のような見た目をしていますが、これはあくまでStoryboard上の表示での話であって実際はユーザから見られる画面というわけではありません。

UINavigationControllerControllerとあるようにコントロールするためのクラスです。
Navigationは画面遷移と言い換えてもらえればいいかと思います。
つまりUINavigationControllerは画面遷移を制御するためのクラスであって画面そのものではありません。
今のNavigation Controllerを追加した操作はUINavigationControllerという概念のようなオブジェクトをFirstViewControllerに追加するというイメージです。

もう少し補足するとUIViewControllerもControllerとあるように、これ自体が画面というわけではありません。
これも画面をコントロールするオブジェクトで、実際に表示されている画面はFirstViewControllerの中にあるViewというUIです。
コントローラは画面の表示制御やユーザからのイベントを扱うという役割です。

少し細かい話をしてしまいました。
実装を進めます。

FirstViewControllerにあるsegueを選択してください。
その状態で[Attributes Inspector]を選択すると[Kind]という項目があるので選択します。
スクリーンショット 2020-05-30 0.06.08.png
ポップアップメニューが表示されるので[Show(e.g. Push)]を選択します。
これでsegueがプッシュ遷移用に変更されました。
スクリーンショット 2020-05-30 0.10.48.png
Storyboardの設定はこれだけです。

あとはソースコードから処理実行するだけです。
ですがこれはモーダル遷移の場合と同じでperformSegueを呼び出してあげるだけです。
segueのidentifierを変えていないので以前モーダルを実装したままのコードですでにプッシュ遷移ができます。
該当のコードは以下です。

FirstViewController.swift
self.performSegue(withIdentifier: "SecondViewController", sender: nil)

実装は以上です。
シミュレータで実行し動作を確認してみてください。

またパラメータもモーダルと同様の方法で渡すことができます。

FirstViewController.swift
let text = textField.text
self.performSegue(withIdentifier: "SecondViewController", sender: text)

UINavigationControllerについて

UINavigationControllerでは一連のプッシュ遷移における画面を管理します。
UINavigationControllerrootViewControllerというプロパティを持ち、プッシュ遷移の一番最初の画面を保持します。
Storyboardから追加した今回の場合だと、UINavigationControllerを追加したFirstViewControllerrootViewControllerになります。

またUINavigationControllerviewControllers: [UIViewController]というプロパティを持っており、遷移した画面を配列で管理しています。
今回の例では画面遷移が完了した状態ではviewControllers[0]FirstViewControllerviewControllers[1]SecondViewControllerが格納されています。

プッシュ遷移先のSecondViewControllerに[戻る]ボタンが表示されていたかと思います。
[戻る]ボタンをタップすると一画面前に戻ります。
画面を戻るとSecondViewControllerUINavigationControllerの管理対象からはずれ解放されます。
つまりviewControllers[1]は存在しなくなります。
図で表すと以下のようになります。
プッシュ画面遷移前.png
プッシュ画面遷移後.png
プッシュ戻るボタンタップ後.png

SecondViewControllerからさらに遷移する

当然SecondViewControllerからThirdViewControllerという画面にさらにプッシュ遷移した場合管理下に追加されviewControllers[2]に格納されます。

実装してみます。
これは特に難しい話ではなく同様にSecondViewControllerからThirdViewControllerへのsegueを作ってあげてperformSegueを呼び出してあげるだけです。

SecondViewControllerを追加した手順と同様にThirdViewController.swiftThirdViewController.storyboardを追加以下のように編集してください。

ThirdViewController.swift
import UIKit

class ThirdViewController: UIViewController {

}

スクリーンショット 2020-05-30 14.16.10.png

さらにSecondViewController.storyboardからThirdViewControllerへのsegueをプッシュで作成してください。
またSecondViewController.storyboardにボタンを追加し、ボタンタップでThirdViewControllerに遷移するようにします。
シミュレータで実行すると3画面遷移できるようになります。
スクリーンショット 2020-05-30 14.21.33.png

SecondViewController
/// 画面遷移ボタンタップ処理
/// - Parameter sender: ボタン
@IBAction func didTapTransitionButton(_ sender: Any) {
    // 画面遷移
    self.performSegue(withIdentifier: "ThirdViewController", sender: nil)
}

ここで注意してほしいことはSecondViewController.storyboardUINavigationControllerは追加しないということです。
UINavigationControllerはrootとなる画面にのみ追加します。
今rootはFirstViewControllerとしているためSecondViewControllerはrootではありません。
SecondViewControllerからプッシュ遷移する場合、rootに設定したUINavigationControllerが参照されます。

画面遷移後は以下のような状態になります。
プッシュ遷移3画面.png

予想がつくかと思いますがThirdViewControllerで[戻る]ボタンを押すと以下のような構成になります。
Third戻る.png
このようにUINavigationControllerでは最後に追加されたものから消えていきます。
この管理方法をスタック(stack)と呼びます。
余談ですがスタックは画面管理の方法ではなく配列の管理方法の1つです。
スタックは基本情報試験にも出てくるIT技術の基礎知識なので抑えておきましょう。
別の配列管理方法ではキュー(queue)があります。一度調べておいてください。

UINavigationControllerの参照のされ方

ここまでの画面遷移処理で少し違和感を覚える方もいるかもしれません。

rootであるFirstViewControllerはStoryboardでUINavigationControllerのオブジェクトを追加したので画面遷移にUINavigationControllerを使えるのはなんとなくわかるかと思います。

ですがSecondViewControllerにはUINavigationControllerは設定されていないはずなのにどうして使えるのでしょうか?

実はUIViewControllerにはnavigationControllerというように現在自分が管理されているUINavigationControllerをプロパティとして持っています。
画面遷移の具体的な処理はUIKitが勝手にやってくれているので詳細は不明なのですが、おそらくプッシュ遷移のタイミングで自分のnavigationControllerを次の画面に一緒にセットしています。

従って1つのUINavigationControllerのオブジェクトが次へ次へと共有されているのです。
図にすると以下のようなイメージになります。
オブジェクト関係図.png
このような形でroot画面からプッシュ遷移した画面の全てを管理しています。

タブ

segueを使った実装

タブ遷移の実装方法について説明します。
タブに関してもsegueを使った実装をします。

タブの遷移にはUITabBarControllerというクラスを使用します。
UITabBarControllerを扱うために、まずTabViewController.storyboardというファイルを追加しましょう。
スクリーンショット 2020-05-30 18.15.50.png
TabViewController.storyboardを開き、[+]ボタンからUITabBarControllerを探しStoryboardに追加します。
スクリーンショット 2020-05-30 18.16.47.png
Tab Bar Controllerというオブジェクトと一緒にitem 1 Sceneitem 2 Sceneというオブジェクトが追加されました。
これはタブTab Bar Controllerがデフォルトで表示する画面テンプレートです。
今回は不要なので削除します。
それぞれ選択し削除しておいてください。
スクリーンショット 2020-05-30 18.18.23.png

Tab Bar Controllerも画面のような形ですが、これも「Controller」とあるように画面そのものではなくタブ遷移において画面を管理するオブジェクトです。
階層1つしたにあるTab Barというオブジェクトは画面一番下に表示されるタブ自体です。これは表示されるUIでUITabBarというクラスです。

このTabBarController.storyboardで最初に参照するオブジェクトはTabBarControllerなので[Is Initial View Controller]のチェックを付けます。
スクリーンショット 2020-05-30 18.24.11.png
TabBarControllerが画面を追加します。
FirstViewController.storyboardSecondViewController.storyboardThirdViewController.storyboardへの参照を追加します。
他の画面遷移と同様Storyboard Referenceを画面数だけ配置します。
配置したStoryboard ReferenceにそれぞれStoryboardを設定します。
スクリーンショット 2020-05-30 18.41.38.png
TabBarControllerからそれぞれのStoryboard Referenceまでsegueを作成します。
segueの種類は[Relationship Segue]の[view controllers]を選択してください。
スクリーンショット 2020-05-30 18.43.40.png
以下のようになればタブ遷移の準備は完了です。
スクリーンショット 2020-05-30 18.47.36.png
次はアプリ起動直後に表示するStoryboardを変更します。
先述したMain.storyboardをリネームする際、説明したようにプロジェクトの[Main Interface]とInfo.plistの[Storyboard Name]を「TabBarController」に変更してください。
スクリーンショット 2020-05-30 18.52.42.png
スクリーンショット 2020-05-30 18.53.10.png
シミュレータで実行するとタブバーが表示されます。
タブバーには何も表示されていませんが、タブボタン自体は配置されています。
適当な場所をタップするとタブ遷移されます。
スクリーンショット 2020-05-30 19.02.38.png
ですがこれではユーザからすると何のUIかわからないので画像と名前を設定してあげましょう。

SecondViewController.storyboardを開いてください。
[+]ボタンからUITabBarItemを探し、SecondViewControllerに追加してください。
UITabBarItemはタブボタンのオブジェクトです。
ここにアイテム名や画像を設定してあげるとそれがタブに表示されます。
UITabBarItemを選択し、[Attributes Inspector]の[BarItem]に[Title]と[Image]を設定してください。
※画像はまだ追加していないので標準でXcodeに入っているものが出ます。自分で画像を追加していくこともできます。
スクリーンショット 2020-05-30 19.12.11.png
ThirdViewController.storyboardにも同様の手順で追加します。

FirstViewController.storyboardについても本来同様の手順でよかったのですが、今回UINavigationControllerが追加されています。
そのためFirstViewControllerオブジェクトに追加しても表示されません。
この場合はNavigationControllerオブジェクトに追加してあげてください。
[Is Initial ViewController]のチェックを入れたオブジェクトに追加する必要があると覚えましょう。
スクリーンショット 2020-05-30 19.23.02.png
シミュレータで実行するとタブアイテムが表示されます。
スクリーンショット 2020-05-30 19.26.01.png

UITabBarControllerについて

UITabBarControllerUINavigationControllerと同様に画面を管理するクラスです。
ですがあくまで並列な画面遷移のためrootというものは存在しません。
表示される画面は同様にviewControllersで管理されています。
タブ遷移ではプッシュ遷移と違い画面を消すということが基本的にはないためviewControllersはスタック管理されているというわけではありません。
ただ配列として画面を持っているというだけです。

またUIViewControllertabBarControllerを持っており、それぞれのViewControllerが同一のUITabBarControllerを参照しています。

図にすると以下のようなイメージです。
タブ関係図.png

プッシュ遷移とタブ遷移の組み合わせ

実際のアプリではプッシュ遷移とタブ遷移両方を組み合わせて画面遷移を実装していきます。
また、今回のFirstViewControllerではすでに両方の遷移をしています。
FirstViewControllerにはNavigationControllerオブジェクトが追加されているからですね。

今回の画面の構成を正確に書くと以下のようになります。
全体関係図.png
若干複雑な関係となります。

図を見るとNavigationControllertabBarControllerというプロパティを持っています。
これはUINavigationControllerUIViewControllerを継承したクラスのためです。
同様にUITabBarControllerUIViewControllerを継承しているためnavigationControllerというプロパティを持ちます。
そのためさらにTabBarControllerNavigationControllerを持たせることもできます。

この図をいますぐ理解できる必要はありませんが、実際にアプリを作る際には自分がどんな構成の画面遷移を実装しているのか図に起こせるように理解できるようにしましょう。

最後に

今回でiOSの画面遷移編は終了です。
モーダル、プッシュ、タブそれぞれの遷移に意味があり実装方法が多少異なります。
積極的に使って自分のものにしていきましょう。

最後の画面遷移の組み合わせはiOSを学ぶ上で躓く部分かもしれません。
これは画面遷移を実装→図に起こす→画面遷移を実装...と繰り返して感覚的に身につけていってください。

次回はライフサイクルという画面表示非表示になる際にコールされるイベントについて説明します。
https://qiita.com/euJcIKfcqwnzDui/items/d50dc4ec1ccc798fc03e

本連載ではプログラミング未経験からiOSアプリ開発が行えるようになることを目的としています。
今までの投稿をまとめていますのでこちらもご覧ください。
http://naoyalog.com/

euJcIKfcqwnzDui
フリーランスエンジニア。主にiOSアプリ開発を行う。 現在はReactNativeでiOS/Androidアプリを開発中。 Swift/Objective-Cネイティブ開発 VIPER、MVP、Redux ご連絡はTwitterのDMからお願いします。 本ブログに記述された見解は私個人の見解であり、所属する会社&組織の見解を反映したものではありませんのでご了承ください。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away