【Swift】簡易電卓で学ぶ画面遷移時の値の受け渡し方(segue,delegate)

画面遷移の理解を深めるため、簡単な計算機を作りました。
Segueによる画面遷移と値の受け渡しを実装しました。
計算結果を元の画面に受け渡すため、protocolを利用しました。

# 完成形
Textfieldとbuttonを配置したシンプルな計算機
Textfieldに数字を打ち込み計算記号をタップすると次のモーダルで計算式と計算結果を表示します。
戻るボタンを押すことで前画面に戻りつつ、計算結果を返します。
![計算機.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475485/891aff34-17e0-3053-4c2e-892cf28606cc.gif)


# 新規プロジェクト ~ ViewControllerの追加
single View Appで新規プロジェクト立ち上げ
ViewControllerを追加して、UIViewControllerを継承した新しいファイル(ここではResultViewController.swift)を作成し、追加したViewControllerのidentity Inspectorでカスタムクラスに設定する
![スクリーンショット 2019-10-07 12.02.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475485/e048d1f3-024f-4a2a-4e54-6dacd0d71539.png)

# UIの作成
※勉強不足のためレイアウトの細かい設定しておりません。悪しからず。
![スクリーンショット 2019-10-07 14.40.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475485/7e5e4b3b-31c7-bafe-71db-891a3390b166.png)


### １つ目の画面
数字を打ち込むためのtextField -> 2つ
計算するためのbutton -> 4つ
固定ラベルと計算結果を表示するlabel -> 3つ
### 2つ目の画面
前の画面に戻るbutton -> 1つ
固定ラベルと計算結果を表示するlabel -> 2つ

# オブジェクトとcontrollerの紐付け
各controllerに対して`label`と`button`を`Control+ドラッグ`で紐付ける
計算時の記号buttonに関しては、`+`を`calcButton`として紐づけた後、`-, ×, ÷`を同じ部分に紐付けてタグで分けるようにした。(コードは後述)
タグは`+ -> 0, - -> 1, × -> 2, ÷ -> 3`にしている
![スクリーンショット 2019-10-07 15.34.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475485/1f4d3fcf-e2f2-3a7c-1e9c-ec3606acb91b.png)

![スクリーンショット 2019-10-07 14.42.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475485/82e34f07-e3fa-73a8-240d-f256b13f7aed.png)


```ViewController.swift
    @IBOutlet weak var firstNumTextField: UITextField!
    @IBOutlet weak var secondNumTextField: UITextField!
    @IBOutlet weak var beforeLabel: UILabel!
    @IBOutlet weak var beforeResultLabel: UILabel!

    @IBAction func calcButton(_ sender: Any) {
    }
```

```ResultViewController.swift
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var resultLabel: UILabel!

    @IBAction func back(_ sender: Any) {
    }

```

# 変数の定義と計算するためのコードを記載
どのボタンがタップされたかをタグによって識別し、計算結果を`result`に格納する。
結果表示画面で計算式を`label`に出力するため、記号を`symbol`に格納する

```ViewController.swift
    var firstNum = Int()
    var secondNum = Int()
    var result = 0
    var symbol = String()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    
    @IBAction func calcButton(_ sender: Any) {
        
        if let num1 = firstNumTextField.text, let num2 = secondNumTextField.text {
            firstNum = Int(num1)!
            secondNum = Int(num2)!
            
            if (sender as AnyObject).tag == 0 {
                result = firstNum + secondNum
                symbol = "+"
                
            } else if (sender as AnyObject).tag == 1 {
                result = firstNum - secondNum
                symbol = "-"
                
            } else if (sender as AnyObject).tag == 2 {
                result = firstNum * secondNum
                symbol = "×"
                
            } else if (sender as AnyObject).tag == 3 {
                result = firstNum / secondNum
                symbol = "÷"
            } else {
            }
        }
    }

```

#ViewControllerからResultViewControllerへ値を受け渡すsegueを記載
受け渡すために必要となるのが、
 1.segueの`identifier`
 2.遷移先(ResultViewController)の変数
 3.受け渡すための`prepareメソッド`とある条件下で遷移する`performSegueメソッド`

## storyboard上の設定
ViewControllerから`Control+ドラッグ`して、`segue`を作成
遷移方法は`present modal`とし、`presentation`は `full screen`にした。
identifierは`"next"`とする。

![スクリーンショット 2019-10-07 14.39.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475485/beeb321e-3430-7f2b-a002-edad13f34e14.png)

## ResultViewControllerで必要となる変数を定義

```ResultViewController.swift
    var firstNum = Int()        // textFieldの値１
    var secondNum = Int()       // textFieldの値2
    var symbol = String()       // 計算記号
    var result = Int()          // 計算結果
```
記号ボタンをタップした際に計算して、ResultViewControllerに遷移する

```ViewController.swift
    @IBAction func calcButton(_ sender: Any) {
        /*  重複のためコメントアウト
        if let num1 = firstNumTextField.text, let num2 = secondNumTextField.text {
            firstNum = Int(num1)!
            secondNum = Int(num2)!
            
            if (sender as AnyObject).tag == 0 {
                result = firstNum + secondNum
                symbol = "+"
                
            } else if (sender as AnyObject).tag == 1 {
                result = firstNum - secondNum
                symbol = "-"
                
            } else if (sender as AnyObject).tag == 2 {
                result = firstNum * secondNum
                symbol = "×"
                
            } else if (sender as AnyObject).tag == 3 {
                result = firstNum / secondNum
                symbol = "÷"
            } else {
            }
        }
        */

        // segueに設定したidentifier(="next")を使って画面遷移する
        performSegue(withIdentifier: "next", sender: nil)
    }

    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        let nextVC = segue.destination as! ResultViewController
        
        // 引き継ぎたい変数たち
        nextVC.result = result
        nextVC.firstNum = firstNum
        nextVC.secondNum = secondNum
        nextVC.symbol = symbol
        
    }

```

#戻るボタンで戻れるようにする
ここまで記載すると、計算結果を次の画面で表示することができるが、
モーダルの遷移を`Full Screen`にすると戻るボタンがないと帰ってこれないので、帰ってくるボタンのアクションを追加する。
`dismissメソッド`で遷移元に変えることができる

```ResultViewController.swift
    @IBAction func back(_ sender: Any) {

        dismiss(animated: true, completion: nil)
    }
```

# 計算結果をViewControllerに返す
ResultViewControllerでprotocolを定義し、委任先のViewControllerで実行する

```ViewController.swift
class ViewController: UIViewController, resultDelegate {

// ~~~省略~~~
    
    func carryResult(carryResult: Int) {
        beforeResultLabel.text = String(carryResult)
    }

    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        /*
        let nextVC = segue.destination as! ResultViewController
        
        // 引き継ぎたい変数たち
        nextVC.result = result
        nextVC.firstNum = firstNum
        nextVC.secondNum = secondNum
        nextVC.symbol = symbol
        */

        // 結果画面のresultはViewControllerで使う
        nextVC.delegate = self
    }
}



```


```ResultViewController.swift
import UIKit

// プロトコルを定義
protocol resultDelegate {
    func carryResult(carryResult:Int)
}

class ResultViewController: UIViewController {
    /*
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var resultLabel: UILabel!
    
    var firstNum = Int()
    var secondNum = Int()
    var symbol = String()
    var result = Int()
    */
    
    // 定義したプロトコルを実体化する
    var delegate:resultDelegate?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        label.text = "\(String(firstNum)) \(symbol) \(String(secondNum))  は..."
        resultLabel.text = String(result)
    }
    
    @IBAction func back(_ sender: Any) {
        // 画面遷移する際にdelegateの引数として、計算結果resultを連れて行く
        delegate?.carryResult(carryResult: result)

        dismiss(animated: true, completion: nil)
    }
    
}
```

# 完成コード
お粗末ですが、記載したコードです。

```ViewController.swift
import UIKit

class ViewController: UIViewController, resultDelegate {

    @IBOutlet weak var firstNumTextField: UITextField!
    @IBOutlet weak var secondNumTextField: UITextField!
    @IBOutlet weak var beforeLabel: UILabel!
    @IBOutlet weak var beforeResultLabel: UILabel!
    
    var firstNum = Int()
    var secondNum = Int()
    var result = 0
    var symbol = String()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    
    @IBAction func calcButton(_ sender: Any) {
        
        if let num1 = firstNumTextField.text, let num2 = secondNumTextField.text {
            firstNum = Int(num1)!
            secondNum = Int(num2)!
            
            if (sender as AnyObject).tag == 0 {
                result = firstNum + secondNum
                symbol = "+"
                
            } else if (sender as AnyObject).tag == 1 {
                result = firstNum - secondNum
                symbol = "-"
                
            } else if (sender as AnyObject).tag == 2 {
                result = firstNum * secondNum
                symbol = "×"
                
            } else if (sender as AnyObject).tag == 3 {
                result = firstNum / secondNum
                symbol = "÷"
            } else {
            }
        }
        
        performSegue(withIdentifier: "next", sender: nil)
    }
    
    func carryResult(carryResult: Int) {
        beforeResultLabel.text = String(carryResult)
    }

    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        let nextVC = segue.destination as! ResultViewController
        
        // 引き継ぎたい変数たち
        nextVC.result = result
        nextVC.firstNum = firstNum
        nextVC.secondNum = secondNum
        nextVC.symbol = symbol
        
        // 結果画面のresultはViewControllerで使う
        nextVC.delegate = self
    }
    

}

```

```ResultViewController.swift
import UIKit

protocol resultDelegate {
    func carryResult(carryResult:Int)
}

class ResultViewController: UIViewController {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var resultLabel: UILabel!
    
    var firstNum = Int()
    var secondNum = Int()
    var symbol = String()
    var result = Int()
    var delegate:resultDelegate?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        label.text = "\(String(firstNum)) \(symbol) \(String(secondNum))  は..."
        resultLabel.text = String(result)
    }
    
    @IBAction func back(_ sender: Any) {
        
        delegate?.carryResult(carryResult: result)
        dismiss(animated: true, completion: nil)
    }
    
}
```

# よくわかってないところ
とりあえず動くものはできたがよくわからないところも多い。
コメントいただけると泣いて喜びます。
## 戻ってくるときにsegueではなくてなぜプロトコルでやるのか
この方法で習ったのでとりあえずこれでやってみているが、ResultViewControllerからViewControllerに対してsegueを作ることも可能な気がする(やってはない)がやはりこれはNGなのだろうか？
他にも方法があるのだろうか。

##textFieldに書き込まないとき(nilの時？)クラッシュする
nilの扱い、むずい。
どう回避すれば良いかまた勉強だ。
申し訳程度にif文のところで`オプショナルバインディング`したつもりなんだけど、挙動がよくわかりません。
nilの時にする処理を書いていないからエラーしちゃうのか？