swiftで画面遷移先に値を渡す＆画面遷移元に値を渡す

こんにちは。今回は値の受け渡しを伴う、特定のタイミングでの画面遷移についてまとめてみようと思います。

画面遷移先に値を渡す処理、あるいは画面遷移元に値を渡す処理についてはたくさんの既存記事がありました。しかしそれを同時に行っているものはあまりたくさんなかった気がしたので、一本にまとめて今回のテーマとしました。

今回はデリゲートやプロトコルを使います。私自身もデリゲートとかプロトコルとかよくわからないヨ状態で書いていますので、わからないなりに頑張って書いていきましょう。もし認識違い等あればご指摘いただけると幸いです！

今回扱う項目は以下です。

* 遷移先画面に値を渡す処理を書く。
* 遷移先画面から遷移元画面に値を渡す処理を書く。

前提条件

* Xcode：Version 12.4(12D4e)
* swift：Version 5.3.2
* MacOS：Version 11.1




## 1.UIやボタンの処理を整えよう

まずは今回作る簡単なアプリの完成形を見てみましょう。

plusボタンを押すとカウントが一つずつ増えて、5になると次の画面に遷移します。遷移後の画面でminusボタンを押すとカウントが一つずつ減って、-5になると元の画面に戻ります。画面遷移にあたって、カウントの値を遷移先画面に受け渡す処理が行われます。


![1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1676583/826469a6-299b-00aa-c048-f371a101ac39.gif)


今回作るアプリは二画面構成で、それぞれLabelとButtonが一つずつ置かれている構成となっています。便宜上、遷移前の画面を「青い画面」、遷移後の画面を「黄色い画面」と呼んで説明していきます。

ラベルの表示はコードで操作するので、ストーリーボード上ではひとまずLabel, Label2としておきます。黄色い画面は「SecondViewController」というクラスを設定してあります。

今回はコードを用いての画面遷移となりますので、青い画面の上の白い四角から、黄色い画面に向かって、Controlを押しながらドラッグしましょう。画面間にセグエが現れます。遷移方法はPresent ModallyのFull Screenにしました。セグエには「next」というIDを振っておきます。


![Screen Shot 2021-07-14 at 18.40.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1676583/83c8c288-72c0-1872-0486-f42e31dcf6fe.png)


UIが整ったらLabelとButtonを変数化します。

ラベルはそれぞれ「blueLabel」、「yellowLabel」と名付けて変数化しました。
また、それぞれの画面でラベルに表示させる値を格納する変数も「counter」、「passedCounter」という名前で用意します。

```ViewController.swift
@IBOutlet weak var blueLabel: UILabel!
var counter = 0
```

```SecondViewController.swift
@IBOutlet weak var yellowLabel: UILabel!
var passedCounter = 0
```

Buttonもそれぞれ「plus」、「minus」と名付けました。ボタンを押したときの処理として、カウントが1増えてラベルに表示する処理をそれぞれ書き足します。
改めて、今回のアプリでは、カウントが増えていって５になったら黄色い画面に遷移し、カウントが減っていって−５になったら青い画面に戻る処理を想定しています。そのためにif文とその中の画面遷移処理も用意しておきましょう。


```ViewController.swift
//plusボタン押したときの処理
@IBAction func plus(_ sender: Any) {
        
    counter = counter + 1
        
    blueLabel.text = String(counter)
        
    //counterが５以上になったときの処理↓
    if counter >= 5{
            
        //セグエをたどって次の画面に遷移
        performSegue(withIdentifier: "next",sender: nil)
            
    }
        
}
```

```SecondViewController.swift
//minusボタン押したときの処理
@IBAction func minus(_ sender: Any) {
        
    passedCounter = passedCounter - 1
        
    yellowLabel.text = String(passedCounter)
        
    //passedCounterが-５以下になったときの処理↓
    if passedCounter <= -5{
            
        //元の画面に戻る処理
        dismiss(animated: true, completion: nil)
            
    }
        
}
```
最後にラベルの初期表示をそれぞれのカウントの値にするために、以下の記述をそれぞれのviewDidLoadに書きます。

```ViewController.swift
override func viewDidLoad() {
    super.viewDidLoad()
        
    //blueLabelの初期表示を変更
    blueLabel.text = String(counter)
        
}
```

```SecondViewController.swift
override func viewDidLoad() {
    super.viewDidLoad()
        
    //yellowLabelの初期表示をpassedCounterの値にする
    yellowLabel.text = String(passedCounter)
        
}
```

これで基本的な部分の準備が整いました。次節から値の受け渡し処理を書いていきます。
## 2.遷移先に値を渡す処理

青い画面のcounterの値を、黄色い画面のpassedCounterに渡します。書き足す記述は以下です。


```ViewController.swift
//次の画面に値を渡す処理
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
    //次の画面を変数化する
    let secondVC = segue.destination as! SecondViewController
        
    //渡したい変数を書く
    secondVC.passedCounter = counter
        
}
```
ここの解釈は[こちら](https://qiita.com/fromage-blanc/items/3ea2dfe97d4c2d6f0646)の記事を参考にしました。カウントが５以上になっていざ画面遷移するぞ！という直前に呼ばれる処理のようです。黄色い画面をsecondVCという変数として宣言し、passedCounterに青い画面のcounterの値を代入しています。

この記述によって、以下のような動きになります。


![2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1676583/fbf85a21-575e-5428-f4d5-6a98d66c923a.gif)


お分かりでしょうか。青い画面から黄色い画面への値の受け渡しはうまくいっているようですね。しかし黄色い画面から青い画面へ、−5の値をまだ渡せていません。そのため戻ってきた画面ではcountの値が５のまま残っているのです。
次節では、黄色い画面から青い画面に値を受け渡す処理を書いていきます。


## 3.遷移前の画面に値を渡す処理

それではまず、黄色い画面でプロトコルを作成し、変数として扱えるように変数化します。こちらの手順は[この記事](https://qiita.com/Furuta03/items/b297680e150eac5a0986)の内容を大いに参考にさせていただきました。

プロトコルにはcatchDataというメソッドを記述しますが、これは後で青い画面で使うことになります。


```SecondViewController.swift
//protocolを作成する
protocol CatchProtocol {
    
    func catchData(count: Int)
    
}
 
class SecondViewController: UIViewController {
    
    //プロトコルを変数化する
    var delegate:CatchProtocol?
```
それでは青い画面に戻って、プロトコルのメソッドを実行する処理を書きます。

まず、黄色い画面で作成したCatchProtocolを青い画面で使う宣言をします。ソースコード一番上のclassのところに、CatchProtocolを追記してください。

```ViewController.swift
//CatchProtocolを追加
class ViewController: UIViewController, CatchProtocol{
```

すると以下のアラートが出てくると思います。
![Screen Shot 2021-07-14 at 20.03.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1676583/f8fbb924-5a25-2016-b0d9-e72e1ab7afa7.png)
このアラートは「プロトコルを使うと言ったのに、その中のメソッドを何も使ってないじゃないですか」とXcodeさんがブチギレているものだと解釈しています。Fixを押すと、確かに先ほど黄色い画面で定義したcatchDataメソッドが出てきますね。
出てきたcatchDataの中を以下のように記述しましょう。

```ViewController.swift
//遷移先画面でpassedCounterが-5になったらここが呼ばれる
func catchData(count: Int){
        
    counter = count
        
    blueLabel.text = String(counter)
        
}
```
この記述では、青い画面のcounterにcountの値を代入し、それをラベルのテキストとして表示させています。countという変数が初めて出てきました。これは黄色い画面で定義するものなのですが、今から書きます。

黄色い画面の、passedCountの値が−５以下になるif文内にdelegateの記述を追記します。

```SecondViewController.swift
//passedCounterが-５以下になったときの処理↓
if passedCounter <= -5{
            
    //元の画面に書いたcatchDataメソッドが呼ばれる、passedCounterの中身を受け渡す
    delegate?.catchData(count: passedCounter)
            
    //元の画面に戻る処理
    dismiss(animated: true, completion: nil)
            
}
```
これはdelegate先、つまり青い画面に書いたcatchDataというメソッドを呼び出して、passedCounterの値をcountに込めてお渡しするよ〜という意味です。これで先ほどの青い画面の記述とのつながりが見えたでしょうか。

さて、これでほぼ完成となりましたが、最後に青い画面の「override func prepare」内に「secondVC.delegate = self」の記述を追記してください。

```ViewController.swift
//次の画面に値を渡す処理
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
    //次の画面を変数化する
    let secondVC = segue.destination as! SecondViewController
        
    //渡したい変数を書く
    secondVC.passedCounter = counter
        
    //遷移先から値を受け取る宣言のdelegate
    secondVC.delegate = self
        
}
```

これは、黄色い画面（secondVC）からdelegateされた値を青い画面（self）で使うよ〜という宣言です。便宜上このタイミングで記述しましたが、本節最初のプロトコル使用の宣言とセットで記述する癖をつけておいた方が良いと思います。

ここまで記述すると、本記事のゴールとなる動きをしてくれます。正しく値が受け渡しされていますね。

![1 (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1676583/8cba25d0-6a89-7941-bfb7-e54041003922.gif)


## 4.最終的なソースコード

ここまで正しく記述できていれば、ソースコードはそれぞれ以下のようになります。参考としてご覧ください。

```ViewController.swift
import UIKit
 
//CatchProtocolを追加
class ViewController: UIViewController, CatchProtocol{
    
    //変数宣言
    @IBOutlet weak var blueLabel: UILabel!
    var counter = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //blueLabelの初期表示を変更
        blueLabel.text = String(counter)
        
    }
    
    //plusボタン押したときの処理
    @IBAction func plus(_ sender: Any) {
        
        counter = counter + 1
        
        blueLabel.text = String(counter)
        
        //counterが５以上になったときの処理↓
        if counter >= 5{
            
            //セグエをたどって次の画面に遷移
            performSegue(withIdentifier: "next",sender: nil)
            
        }
        
    }
    
    //次の画面に値を渡す処理
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        //次の画面を変数化する
        let secondVC = segue.destination as! SecondViewController
        
        //渡したい変数を書く
        secondVC.passedCounter = counter
        
        //遷移先から値を受け取る宣言のdelegate
        secondVC.delegate = self
        
    }
    
    //遷移先画面でpassedCounterが-5になったらここが呼ばれる
    func catchData(count: Int){
        
        counter = count
        
        blueLabel.text = String(counter)
        
    }
    
}
```

```SecondViewController.swift
import UIKit
 
//protocolを作成する
protocol CatchProtocol {
    
    func catchData(count: Int)
    
}
 
class SecondViewController: UIViewController {
    
    //プロトコルを変数化する
    var delegate:CatchProtocol?
    
    //変数宣言
    @IBOutlet weak var yellowLabel: UILabel!
    var passedCounter = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //yellowLabelの初期表示をpassedCounterの値にする
        yellowLabel.text = String(passedCounter)
        
    }
    
    //minusボタン押したときの処理
    @IBAction func minus(_ sender: Any) {
        
        passedCounter = passedCounter - 1
        
        yellowLabel.text = String(passedCounter)
        
        //passedCounterが-５以下になったときの処理↓
        if passedCounter <= -5{
            
            //元の画面に書いたcatchDataメソッドが呼ばれる、passedCounterの中身を受け渡す
            delegate?.catchData(count: passedCounter)
            
            //元の画面に戻る処理
            dismiss(animated: true, completion: nil)
            
        }
        
    }
    
}
```
## 5.おわりに

今回は画面遷移に伴う値の受け渡しについてまとめました。値の受け渡しについてハンズオン教材で指示されるままに記述していくと、ごちゃごちゃ色々なことを書いている気がして混乱しまいます。しかし改めて整理してみると、必要な記述は案外シンプルでした。何度も書いて覚えていきたいですね。


参考にした記事：

* [Swift iOSアプリ １つ前の画面に値を渡す方法](https://qiita.com/Furuta03/items/b297680e150eac5a0986)
* [【Swift4】次の画面にデータを引き継ぐ５つの方法【Xcode】](https://qiita.com/fromage-blanc/items/3ea2dfe97d4c2d6f0646)