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

## はじめに
前回は条件分岐など少しプログラミング的な処理を入れてUIを更新しする方法ついて投稿しました。
SwiftとUIの接続編３：https://qiita.com/euJcIKfcqwnzDui/items/93f010b989a4d333f0b9

基本的なアプリには画面がいくつかあります。
今回は画面を追加し、アプリを作る上で必須となる「画面遷移」について説明します。

少しプログラミングの話も出てくるかと思うので一度[こちら](https://qiita.com/euJcIKfcqwnzDui/items/f6e41f6b8f068c620ac6)の記事に目を通しておくといいかもしれません。

それではプロジェクトを開きます。
前回までで処理やUIが少し増えたので今回は新しいプロジェクトを作って進めます。
もちろん前回までのプロジェクトを使って頂いても構いません。

## 画面を追加する
### 遷移先画面用のファイルを追加する
まず画面遷移するためには遷移先の画面が必要です。
画面を作るためにはSwiftファイルとStoryboardのファイルが必要になります。
遷移先の画面は`SecondViewController`という名前にしてファイルを作っていきましょう。

まずXcodeから追加先のフォルダを右クリックします。
メニューが表示されるので[New File...]を選択します。
<img width="464" alt="スクリーンショット 2020-05-26 23.12.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/2421df67-fd74-6d03-4122-c0c81632eb2c.png">
まずSwiftファイルを作ります。
表示されたポップアップから[Swift File]を選択し[Next]をクリックします。
<img width="735" alt="スクリーンショット 2020-05-26 23.17.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/ad7be559-b7f5-7538-b450-953cbf48b4c8.png">
[Save As:]に作りたいファイル名を拡張子なしで入力し[Create]をクリックします。
<img width="735" alt="スクリーンショット 2020-05-26 23.19.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/d6d548f5-04f3-59d4-18eb-2de827cf17bb.png">
`SecondViewController.swift`が作成されました。
同じ手順でStoryboardファイルも作成します。
<img width="729" alt="スクリーンショット 2020-05-26 23.25.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/6e857716-e98c-cd8f-7b6d-dff47aed4670.png">
<img width="739" alt="スクリーンショット 2020-05-26 23.26.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/5f6b1213-5580-c7d9-31f5-ecb674ac0781.png">
`SecondViewController.storyboard`が作成されました。

これでファイルの準備は完了です。
<img width="276" alt="スクリーンショット 2020-05-26 23.28.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/7627aea5-685a-992b-5391-6df9b69e43c7.png">

### 画面クラスを作成する
画面を表示するためにはまず画面用のクラスが必要です。
クラス名を`SecondViewController`として以下のように`SecondViewController.swift`に作成します。
ちなみにファイル名とその中に定義するクラス名は必ずしも一致している必要はありません。
ですがファイルを見ればどのクラスが書かれたファイルなのかすぐわかるように一致させることをおすすめします。

```swift:SecondViewController.swift
import UIKit

class SecondViewController: UIViewController {

}
```
これで画面のクラスは作成できました。
このコードについて簡単に説明します。

まず`class SecondViewController`は定義するクラス名を表します。
このあたりは[別記事](https://qiita.com/euJcIKfcqwnzDui/items/f6e41f6b8f068c620ac6#%E3%82%AF%E3%83%A9%E3%82%B9)の説明を参照してください。

クラス名に`:`を付けて`UIViewController`と書かれています。
これは**継承**といい、オブジェクト指向の考え方の１つです。
継承とはあるクラスをベースとして新しいクラスを作成する方法です。
継承元の特性を引き継がせるようなイメージです。拡張しているといってもいいかもしれません。
この段階では`SecondViewController`にはプロパティもメソッドも持っていないので`UIViewController`とほぼ同じ特性を持つクラスです。

`UIViewController`は全ての画面のベースとなるクラスです。
表示する画面となるViewや画面遷移のメソッドなどを持っています。
プロジェクト作成時に自動生成された`ViewController`クラスを確認するとこれを継承していることがわかります。

では`UIViewController`はどこで定義されたクラスなのかというと`UIKit`という**フレームワーク**、**ライブラリ**とも言ったりしますが、そこで定義されています。
ライブラリはある機能を実現するための処理をまとめたものです。
`UIKit`はUIを構成するためのクラスを提供してくれています。
`UILabel`や`UIButton`など頭に「UI」がついているものは全てUIKitが提供しているクラスです。

ライブラリが提供するクラスや機能を使う場合はファイルの頭で`import ライブラリ`と宣言してあげる必要があります。
今回は`UIKit`を使うので頭で`import UIKit`としています。

これで遷移先のクラスは作成できました。
遷移するための処理やその他基本的な処理は`UIViewController`がやってくれるので`SecondViewController`の中身はとりあえず空で問題ありません。

### 遷移先のStoryboardを作成する
次はStoryboardを編集します。
`SecondViewController.storyboard`を開いてください。

作成したばかりなので空の状態です。
まずは土台となる`UIViewController`を配置します。
`UIViewController`もボタンなどの配置と同じです。
[+]をクリックし`UIViewController`を適当な位置に配置してください。
検索から「ViewController」と打てば早く見つけられるかと思います。
<img width="1440" alt="スクリーンショット 2020-05-27 1.12.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/ac7c898f-2e71-83fd-fa62-18f0a0895f35.png">

ViewControllerを選択した状態で[Attribute Inspector]を選択してください。
Inspector内に[Is Initial View Controller]にチェックをいれます。
するとViewControllerの左側に矢印が出ます。
<img width="1171" alt="スクリーンショット 2020-05-27 1.15.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/fde20c85-2079-814f-8ad0-4f5e89a066a5.png">
ViewControllerは[+]から何個でも配置することができます。
そのため複数配置さえていた場合、このファイルのどのViewControllerを最初に表示すればいいのかInterface Builderがわからなくなります。
[Is Initial View Controller]は「このファイルで最初に表示するViewControllerです」というフラグです。

次はViewControllerに`SecondViewController`クラスを設定してあげます。
ViewControllerを選択し、[Identity Inspector]をクリックします。
[Class]という項目があるので`SecondViewController`を設定します。
<img width="1185" alt="スクリーンショット 2020-05-27 1.25.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/f9cfdae8-0c54-e061-4a89-3de82473d1f6.png">
ViewControllerの場合[Class]には`UIViewController`を継承した全てのクラスのうち１つを選択することができます。
ここに設定すると先程作成した`SecondViewController`とStoryboardの中のViewControllerがひも付きます。
【補足】ここに設定する`SecondViewController`はファイル名ではなく**クラス名**です。

真っ白な画面では遷移したとき少しわかりにくいかもしれないので適当なラベルを配置しておきましょう。
(ラベルの文言はStoryboard上でラベルをダブルクリックすると編集できます)
<img width="451" alt="スクリーンショット 2020-05-27 1.33.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/4131544f-eeb7-914a-6002-3a40df0db45e.png">

これで遷移先の画面は完成です。

## 画面遷移する
それでは画面遷移させます。
iOSアプリの画面遷移の方式は何パターンかあります。
今回はその中で最も単純な**モーダル**という方式を使って画面遷移させます。
モーダルの詳細と他の方式は次回紹介します。

### セグエを設定する
画面遷移の実装方法は２通りあります。
ソースコードから直接画面オブジェクトを生成する方法と**segue**を使う方法です。
segueも`UIKit`で提供されている機能の１つで画面と画面を繋ぐオブジェクトです。
矢印のようなものですが実際に見た方が早いです。

今回はこのsegueを使って画面遷移させてみます。

`Main.storyboard`を開いてください。

まずは**Storyboard Reference**というUIを配置します。
いつも通り[+]ボタンをクリックし、Storyboard Referenceを探します。
これをViewControllerの外側に配置してください。
<img width="1183" alt="スクリーンショット 2020-05-27 1.50.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/17ea97bc-296c-4d78-1c53-c0dcaf87036f.png">
配置するとViewControllerの外側とUIの一覧にStoryboard Referenceが現れます。
<img width="1176" alt="スクリーンショット 2020-05-27 1.54.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/940260ab-0f6f-7d46-ea11-e2383561ecd0.png">
補足ですが、Storyboard ReferenceはViewControllerの外側に配置されるのでViewControllerの構成には影響しません。
UIの一覧を見ると[View Controller Scene]と同じ階層にあることがわかります。

Storyboard Referenceはその名の通りStoryboardへの参照です。
先程別ファイルとして遷移先として`SecondViewController.storyboard`というStoryboardファイルを作成しました。
`Main.storyboard`と`SecondViewController.storyboard`は全くの別ファイルなので`Main.storyboard`には遷移先の情報が必要となります。
その別ファイル情報を設定するためのUIがStoryboard Referenceです。

ではStoryboard Referenceに`SecondViewController.storyboard`の情報を設定してあげます。
Storyboard Referenceを選択してください。
選択すると[Attributes Inspector]にStoryboardを設定できる項目があります。
ここに`SecondViewController.storyboard`を設定してあげましょう。
<img width="1169" alt="スクリーンショット 2020-05-27 2.06.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/7430e7f9-15ea-4c3e-ebe9-bacd0ff89add.png">
【補足】ここで設定しているのはファイル情報です。そのため[Storyboard]の項目に設定しているのはクラス名ではなく**ファイル名**です。

次はsegueでViewControllerとStoryboard Referenceを繋げてあげます。
ViewControllerの上にあるバーをクリックします。
すると３つのアイコンが表示されるのでその一番左のアイコンをクリックします。
これはViewControllerのオブジェクトを表すアイコンです。
controlキーを押しながらこのアイコンをクリックし、Storyboard Referenceまで青い線を引っ張っていってください。
<img width="840" alt="スクリーンショット 2020-05-27 2.11.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/c880cf9c-aa03-3fbd-2c9b-ebdd5033f6b8.png">
するとStoryboard Referenceの近くに以下のようなポップアップが表示されます。
[Present Modally]を選択します。
これはモーダルで表示するという設定になります。
<img width="220" alt="スクリーンショット 2020-05-27 2.15.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/9538d5a2-aa7c-69a1-0ade-d0b91e6d6658.png">
設定するとViewControllerとStoryboard Referenceが矢印で繋がれます。
この矢印が**segue**です。
<img width="405" alt="スクリーンショット 2020-05-27 2.19.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/6a6f4810-bf81-0ae8-b8dd-35d3597b7e0a.png">
segueを選択し、[Attributs Inspector]をクリックしてください。
[Identifier]という項目があるのでsegueのIDを設定します。
このIDはファイル内のsegueの中で一意なものであれば何でも構いません。
ですがわかりやすくするために遷移先の名前を設定するのが一般的です。
<img width="1173" alt="スクリーンショット 2020-05-27 2.22.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/8e097346-7d72-7d27-3977-4fb3bacf6b2f.png">

これでsegueの設定ができ、遷移元のStoryboardの設定は完了です。

### 画面遷移する
ここまでが画面遷移をするための準備となります。

それでは画面遷移させてみましょう。
ボタンタップのイベントで画面を遷移させたいので`Main.storyboard`にボタンを配置し、`ViewController`クラスにタップイベントを紐付けてください。

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    /// 画面遷移ボタンタップ処理
    /// - Parameter sender: ボタン
    @IBAction func didTapTransitionButton(_ sender: Any) {
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}
```
処理自体は非常に簡単で`performSegue`というメソッドを使用するだけです。
以下を実装して実行すると`SecondViewController`画面に遷移します。

```swift:ViewController.swift
@IBAction func didTapTransitionButton(_ sender: Any) {
    self.performSegue(withIdentifier: "SecondViewController", sender: nil)
}
```
`performSegue`というメソッドは`UIViewController`で定義されているメソッドで、segueを使って画面遷移するためのメソッドです。
第一引数の`withIdentifier`は先程Storyboardでsegueに設定したIDです。
設定したID以外のものを設定するとアプリがクラッシュするので気をつけてください。
第二引数の`sender`は画面遷移の際、次の画面に受け渡したいパラメータを設定します。
今回`nil`を設定していますがこれは何も設定しないという意味です。他の言語では`null`と呼ばれています。

メソッドの先頭に`self`というものを付けていますがこれは自分自身のオブジェクトという意味です。
今回でいうと`ViewController`自身です。
実際には`performSegue`は`UIViewController`のメソッドですが、クラスを継承した場合継承元クラスのメソッドを自分自身のメソッドとして呼び出すことができます。

これで`モーダル`という方式の画面遷移をsegueで実現することができました。
モーダルは画面の上に画面が乗るような遷移方式です。

## パラメータを受け渡す
`performSegue`の第二引数`sender`でパラメータを次の画面に渡せるといいました。
アプリを作っているとどうしても一画面では処理を完結できず、次の画面に情報を渡してあげないと処理できないようなことがあります。
そういった場面では`sender`にパラメータを設定し、次の画面で値を保持してあげます。

実際にやってみましょう。
今回の例では１画面目でテキストをキーボードから入力し、遷移先の画面で入力した文字を表示します。

キーボードから文字を入力するにはUITextFieldを使用します。
`Main.storyboard`に配置し`ViewController.swift`にアウトレット接続してください。
(以下の例ではその他に「入力してください」というラベルも配置しています。)
<img width="727" alt="スクリーンショット 2020-05-27 23.30.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/6905d95e-a8c3-853d-8691-9d522200b02f.png">
<img width="695" alt="スクリーンショット 2020-05-27 23.33.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/0a54352d-cad1-65ec-1c22-8380722f2bf3.png">

```swift:ViewController.swift
class ViewController: UIViewController {
    /// テキストフィールド
    @IBOutlet weak var textField: UITextField!
    
    /// 画面遷移ボタンタップ処理
    /// - Parameter sender: ボタン
    @IBAction func didTapTransitionButton(_ sender: Any) {
        self.performSegue(withIdentifier: "SecondViewController", sender: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}
```
次に遷移先の`SecondViewController`に渡された文字列を表示するためのUILabelを配置してください。
配置したUILabelを`SecondViewController.swift`にアウトレット接続します。
<img width="469" alt="スクリーンショット 2020-05-27 23.41.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326173/3457dce8-b264-96c7-aadd-cf4a9f0d4930.png">

```SecondViewController.swift
class SecondViewController: UIViewController {
    /// 入力された文字列を表示するラベル
    @IBOutlet weak var inputtedTextLabel: UILabel!
    
}
```
さらに`SecondViewController.swift`に入力された文字列を格納しておく`inputtedText`というプロパティを宣言しておきます。

```SecondViewController.swift
class SecondViewController: UIViewController {
    /// 入力された文字列を表示するラベル
    @IBOutlet weak var inputtedTextLabel: UILabel!
    /// 入力された文字列を格納しておくプロパティ
    var inputtedText: String?
}
```
`inputtedText`の型が`String?`となっていますが`?`は**オプショナル型**を表します。
オプショナル型はSwift特有の考え方なのですが、値がnilの可能性があるという意味で`String?`とするとnilになり得る`String`型という意味になります。
`UITextField`のテキストの初期値はnilで`SecondViewController`に引き渡される文字列がnilになる可能性があるためオプショナル型で宣言しています。
今回はそういうものがあるのかくらいの認識で問題ありません。

さてこれで準備が完了しました。
実際にパラメータを引き渡すコードを書いていきましょう。

パラメータを引き渡すにはまず画面遷移のイベントを**フック**してあげる必要があります。
イベントをフックするとは、イベントが発生したときに処理を割り込ませるような仕組みになります。
例えばボタンタップのメソッドもイベントが発生したときに処理を割り込ませているのでフックの一種になるかと思います。

`UIViewController`にはsegueでの画面遷移イベントをフックするメソッドが用意されています。
`prepare(for segue: UIStoryboardSegue, sender: Any?)`というメソッドです。
これを利用してパラメータを引き渡してあげます。

画面遷移を実行するのは遷移元である`ViewController`の処理なので`ViewController.swift`に処理を追加します。

確認のため`prepare`に`print()`入れて実行してみましょう。

```swift:ViewController.swift
class ViewController: UIViewController {
    /// テキストフィールド
    @IBOutlet weak var textField: UITextField!
    
    /// 画面遷移ボタンタップ処理
    /// - Parameter sender: ボタン
    @IBAction func didTapTransitionButton(_ sender: Any) {
        self.performSegue(withIdentifier: "SecondViewController", sender: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    /// 画面遷移イベントをフックする
    /// - Parameters:
    ///   - segue: segue
    ///   - sender: パラメータ
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        print("画面遷移前の処理！")
    }
}
```
`func prepare`の前に`override`とあります。
これは継承元となったクラスに定義されているメソッドを上書きするという意味になります。
`UIViewController`では`performSegue`を呼んだ後、segueに紐付けられた画面オブジェクトを作成し、`prepare`を呼び出すというような処理を行っています。
ここで呼ばれる`prepare`の処理を上書いてあげることで画面遷移前に好きな処理を記述できるようになります。

`prepare`の引数について。
第一引数はsegueには画面遷移に使用したsegueが渡されてきます。
今回の場合だと`SecondViewController`へのsegueです。
segueの中には遷移先の情報があり、遷移先の画面オブジェクトも含まれます。
このsegueから取り出した画面オブジェクトに対し好きな処理をすることができます。

第二引数の`sender`には`performSegue`の第二引数に設定した値が入ります。

それでは実際にパラメータを引き渡す処理を実装します。
以下のように実装してください。

```swift:ViewController.swift
class ViewController: UIViewController {
    /// テキストフィールド
    @IBOutlet weak var textField: UITextField!
    
    /// 画面遷移ボタンタップ処理
    /// - Parameter sender: ボタン
    @IBAction func didTapTransitionButton(_ sender: Any) {
        // textFieldから入力されているテキストを取得する
        let text = textField.text
        // 画面遷移のパラメータとしてtextを設定する
        self.performSegue(withIdentifier: "SecondViewController", sender: text)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    /// 画面遷移イベントをフックする
    /// - Parameters:
    ///   - segue: segue
    ///   - sender: パラメータ
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // segueからSecondViewControllerを取得する
        if let secondViewController = segue.destination as? SecondViewController {
            // senderをStringにキャストする
            let paramater = sender as? String
            // SecondViewControllerのinputtedTextにparamaterを設定する
            secondViewController.inputtedText = paramater
        }
    }
}
```
まずは`didTapTransitionButton`での処理から。
`UITextField`の`text`プロパティにはユーザがキーボードから入力した文字列が入っています。
`UILabel`と似たような感じですね。
ここで取得した値を`performSegue`の第二引数に画面遷移のパラメータとして引き渡しています。

`prepare`では実際に遷移先にパラメータを設定しています。
`UIStoryboardSegue`の`destination`には遷移先の画面オブジェクトが設定されています。
`as`は**キャスト**と呼ばれる処理で型を変換しています。
定義を見るとわかりますが`destination`は`UIViewController`型です。
今回は`SecondViewController`の`inputtedText`にパラメータを設定してあげたいのですが、`UIViewController`には`inputtedText`は定義されていません。
そのため一度キャストで型変換してあげる必要があります。

`if let ~~`はSwift特有の書き方なので詳細については別の機会に説明します。
簡単にいうと`destination`が`SecondViewController`なら`secondViewController`という変数を宣言するというような処理です。

`let paramater = sender as? String`
これもキャストしています。
`sender`は`Any`型ですが`inputtedText`は`String`型なので型が合いません。
そのため一度キャストした値を`paramater`に格納しています。

`secondViewController.inputtedText = paramater`はそのままで、`inputtedText`に`paramater`をセットしています。

これで`SecondViewController`にパラメータを引き渡すことができました。
パラメータを引き渡した後は`SecondViewController`は自由に`inputtedText`を使うことができます。

この後は`SecondViewController`の`viewDidLoad`などで`UILabel`の表示を`inputtedText`で更新してあげると表示が変更されます。
以下のように実装してから実行すると入力したテキストの値が遷移先に表示されます。

```swift:SecondViewController.swift
class SecondViewController: UIViewController {
    /// 入力された文字列を表示するラベル
    @IBOutlet weak var inputtedTextLabel: UILabel!
    /// 入力された文字列を格納しておくプロパティ
    var inputtedText: String?
    
    /// 画面読み込み完了後処理
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // inputtedTextLabelの表示をinputtedTextの値にする
        self.inputtedTextLabel.text = self.inputtedText
    }
}
```
もしキーボードで画面遷移ボタンが押せない場合は`ViewController.swift`に以下の処理を追加してください。
画面のどこかをタップするとキーボードを閉じるという処理です。

```swift:ViewController.swift
/// 画面タップでテキストフィールドを閉じる
/// - Parameters:
///   - touches: touches
///   - event: event
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    self.view.endEditing(true)
}
```

## 最後に
今回は画面遷移の方法とパラメータの引き渡し方について説明しました。
だんだんとSwiftプログラミングの要素が入ってきたので難しくなってきたかと思います。
ですが画面遷移はアプリで必ずといっていいほど入っている処理なので習得してください。

今回モーダル遷移を取り扱ったので次回は別の方式について説明します。
画面遷移編２：https://qiita.com/euJcIKfcqwnzDui/items/6d37aaf00c0bc7ce26ca

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