【Swift】画面遷移時の値・処理の受け渡し方法（俯瞰用）

先日投稿した画面遷移で書ききれなかったところについて、続きを書き加えます。
先日の記事と同様に備忘録の意味合いが強めです。

先日の記事はこちら：[【Swift】画面遷移の方法（俯瞰用）](https://qiita.com/ichikawa7ss/items/9f89621ce27341af9054 "【Swift】画面遷移の方法（俯瞰用）")

## 動作環境
- macOS : Mojave 10.14.6
- Swift : 5.0.1
- iOS   : 13.1.3
- Xcode : 11.0


## 前回のポイント - 代表的な画面遷移の方法
画面遷移の実装方法は主に以下の三つ。
**A. InterfaceBuilder（ボタンと画面をSegueで接続）**
**B. InterfaceBuilder（Segue Identifier） + コーディング**
**C. InterfaceBuilder（Storyboard ID） + コーディング**


## 今回書く内容
- 遷移先画面への値渡しの方法
- 遷移先の結果を遷移元に反映させる方法

## 遷移先画面への値渡しの方法

### 基本的な考え方

基本的に遷移先への値の渡し方は以下の手順
（A_ViewController -> B_ViewController に遷移することを考えます）

**遷移先のB_ViewControllerの実装**
1. 遷移先に渡したい値を格納する変数を用意する

**遷移元のA_ViewControllerの実装**
2. 遷移先のViewControllerを取得
3. １で用意した遷移先の変数に値を渡す
4. 画面遷移実行

上記はSegueにIDをつけてperformSegueメソッドを使用する場合でも、StoryboardIDをつけpresentメソッドを呼び出すような場合でも同様です。

### Bパターン【InterfaceBuilder（Segue Identifier） + コーディング】での実装

画像のように遷移元A_ViewControllerでテキストフィールドに値を入力して、
遷移先のB_ViewControllerで表示するような場合を考えます。

まず画像右上の赤枠部分のStoryboard Segue -> IdentifierにsegueのIDを付与します。
今回はtoBViewControllerとしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287671/0306a89b-1e32-2246-0589-ce0319a80c2f.png)

**遷移先のB_ViewControllerの実装**

```swift:B_ViewController
import UIKit

class B_ViewController: UIViewController {
    
    @IBOutlet weak var outputLabel: UILabel!
    
    // 1. 遷移先に渡したい値を格納する変数を用意する
    var outputValue : String?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        outputLabel.text = outputValue
    }
}
```

**遷移元のA_ViewControllerの実装**

```swift:A_ViewController
import UIKit

class A_ViewController: UIViewController {

    @IBOutlet weak var inputField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    // segueが動作することをViewControllerに通知するメソッド
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        // segueのIDを確認して特定のsegueのときのみ動作させる
        if segue.identifier == "toBViewController" {
            // 2. 遷移先のViewControllerを取得
            let next = segue.destination as? B_ViewController
            // 3. １で用意した遷移先の変数に値を渡す
            next?.outputValue = self.inputField.text
        }
    }

    @IBAction func tapTransitionButton(_ sender: Any) {
        // 4. 画面遷移実行
        performSegue(withIdentifier: "toBViewController", sender: nil)
    }
    
}
```

遷移元のA_ViewControllerではsegueのidentifierをみて、処理を行うか決めています。
prepareメソッドはsegueが動作するたびに呼ばれるので、
（今回は遷移先は一つなので必要ありませんが）遷移先が増えた時に適切な処理をするために必要です。


### C. InterfaceBuilder（Storyboard ID） + コーディング

このパターンでは、segueはつながずviewControllerにIDを付与することで遷移するパターンですが、
基本パターンの1-4の手順は同一です。

まず画像のように遷移先であるB_ViewControllerにIDを付与します。

![image](https://user-images.githubusercontent.com/42941654/69838794-7118c880-1298-11ea-853c-bf2fa424d2a6.png)

**遷移先のB_ViewControllerの実装**

（Bパターンと同一のため割愛）

**遷移元のA_ViewControllerの実装**

```swift:A_ViewController
import UIKit

class A_ViewController: UIViewController {

    @IBOutlet weak var inputField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func tapTransitionButton(_ sender: Any) {
        let storyboard = self.storyboard!
        // 2. 遷移先のViewControllerを取得
        let next = storyboard.instantiateViewController(withIdentifier: "B_ViewController") as! B_ViewController
        //  3. １で用意した遷移先の変数に値を渡す
        next.outputValue = self.inputField.text
        // 4. 画面遷移実行
        self.present(next, animated: true)
    }
    
}
```
**結果**

遷移先に入力したテキスト「test」が反映されています。

![practiceSegue](https://user-images.githubusercontent.com/42941654/69838753-4890ce80-1298-11ea-87d1-30c44b113b33.gif)

## 遷移先の結果を遷移元に反映させる方法

今度は遷移先の処理結果を遷移元に戻った時に反映させる処理をします。
delegateパターンやAppDelegateパターンなどがあるとのことですが、
今回は「prepareメソッドでクロージャで処理ごと渡す」パターンで実装をしてみます。

### 画面イメージ

①ボタンをタップして遷移先へ
②値を入力後、ボタンをタップして遷移元へ戻る
③ラベルの値が変わっているはず

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287671/12672cfa-1b0e-4de0-ec5f-59643cf6f06b.png)


### 実装手順

上記画面のように遷移のパターンとしてはBパターンのsegueにIDを付与し、prepareメソッド呼び出し時に値を渡す処理とします。
この時、遷移先に「値」を渡すのではなく、「遷移先の値を使って遷移元の値を更新してね」という処理ごとクロージャで渡します。

**遷移元のA_ViewControllerの実装**


```swift:A_ViewController
import UIKit

class A_ViewController: UIViewController {

    @IBOutlet weak var outputLabel: UILabel!
            
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func tapTrasitionButton(_ sender: Any) {
        // segueを使って画面遷移
        performSegue(withIdentifier:  "toBViewController", sender: nil)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // segueのIDを確認して特定のsegueのときのみ動作させる
        if segue.identifier == "toBViewController" {
            // 遷移先のViewControllerを取得
            let next = segue.destination as? B_ViewController
            // 遷移先のプロパティに処理ごと渡す
            next?.resultHandler = { text in
                // 引数を使ってoutputLabelの値を更新する処理
                self.outputLabel.text = text
            }
        }
    }
}
```

**遷移先のB_ViewControllerの実装**

```swift:B_ViewController
class B_ViewController: UIViewController {

    @IBOutlet weak var inputField: UITextField!
    
    // 遷移元から処理を受け取るクロージャのプロパティを用意
    var resultHandler: ((String) -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func tapBackButton(_ sender: Any) {
        // nilチェック
        guard let text = self.inputField.text else { return }
        
        // 用意したクロージャに関数がセットされているか確認する
        if let handler = self.resultHandler {
            // 入力値を引数として渡された処理の実行
            handler(text)
        }
        
        // 遷移元へ戻る
        self.dismiss(animated: true, completion: nil)
    }
}
```

通常、遷移先の画面から、遷移元の画面の要素にアクセスしたい時はdelegateパターンを使う場合が多いようですが、簡単な処理ならこちらで書くのも良いかなと思いました。

**結果**

元々のテキスト「Label」から遷移先で入力したテキスト「test」が反映されています。
（少し分かりづらいですが）

![PracticeSegue2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287671/1197f5f9-ba0c-a1a8-95b0-841ef6ac21ad.gif)

## まとめ

- 遷移先画面への値渡しの方法する場合の考え方は以下
 1. 遷移先に渡したい値を格納する変数を用意する
 2. 遷移先のViewControllerを取得
 3. １で用意した遷移先の変数に値を渡す
 4. 画面遷移実行
 
- 遷移先の結果を遷移元に反映させる方法
    - クロージャで処理ごと反映する処理ごと渡す
    - 今回の方法以外にDelegateパターンや、AppDelegateを経由するパターンもあり

## 参考
- [【Swift/iOS】画面遷移時の値渡し](https://capibara1969.com/1060/#toc18)
- [Storyboardで遷移先画面で処理を終えて元の画面に戻る際に値を受け取る](https://qiita.com/kenichi_odo/items/062ae2dbc9f7120257ed)