3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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

完成形

Textfieldとbuttonを配置したシンプルな計算機
Textfieldに数字を打ち込み計算記号をタップすると次のモーダルで計算式と計算結果を表示します。
戻るボタンを押すことで前画面に戻りつつ、計算結果を返します。
計算機.gif

新規プロジェクト ~ ViewControllerの追加

single View Appで新規プロジェクト立ち上げ
ViewControllerを追加して、UIViewControllerを継承した新しいファイル(ここではResultViewController.swift)を作成し、追加したViewControllerのidentity Inspectorでカスタムクラスに設定する
スクリーンショット 2019-10-07 12.02.15.png

UIの作成

※勉強不足のためレイアウトの細かい設定しておりません。悪しからず。
スクリーンショット 2019-10-07 14.40.52.png

1つ目の画面

数字を打ち込むためのtextField -> 2つ
計算するためのbutton -> 4つ
固定ラベルと計算結果を表示するlabel -> 3つ

2つ目の画面

前の画面に戻るbutton -> 1つ
固定ラベルと計算結果を表示するlabel -> 2つ

オブジェクトとcontrollerの紐付け

各controllerに対してlabelbuttonControl+ドラッグで紐付ける
計算時の記号buttonに関しては、+calcButtonとして紐づけた後、-, ×, ÷を同じ部分に紐付けてタグで分けるようにした。(コードは後述)
タグは+ -> 0, - -> 1, × -> 2, ÷ -> 3にしている
スクリーンショット 2019-10-07 15.34.15.png

スクリーンショット 2019-10-07 14.42.02.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とし、presentationfull screenにした。
identifierは"next"とする。

スクリーンショット 2019-10-07 14.39.51.png

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

ResultViewController.swift
    var firstNum = Int()        // textFieldの値1
    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の時にする処理を書いていないからエラーしちゃうのか?

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?