画面遷移の理解を深めるため、簡単な計算機を作りました。
Segueによる画面遷移と値の受け渡しを実装しました。
計算結果を元の画面に受け渡すため、protocolを利用しました。
完成形
Textfieldとbuttonを配置したシンプルな計算機
Textfieldに数字を打ち込み計算記号をタップすると次のモーダルで計算式と計算結果を表示します。
戻るボタンを押すことで前画面に戻りつつ、計算結果を返します。
新規プロジェクト ~ ViewControllerの追加
single View Appで新規プロジェクト立ち上げ
ViewControllerを追加して、UIViewControllerを継承した新しいファイル(ここではResultViewController.swift)を作成し、追加したViewControllerのidentity Inspectorでカスタムクラスに設定する
UIの作成
※勉強不足のためレイアウトの細かい設定しておりません。悪しからず。
1つ目の画面
数字を打ち込むためのtextField -> 2つ
計算するためのbutton -> 4つ
固定ラベルと計算結果を表示するlabel -> 3つ
2つ目の画面
前の画面に戻るbutton -> 1つ
固定ラベルと計算結果を表示するlabel -> 2つ
オブジェクトとcontrollerの紐付け
各controllerに対してlabel
とbutton
をControl+ドラッグ
で紐付ける
計算時の記号buttonに関しては、+
をcalcButton
として紐づけた後、-, ×, ÷
を同じ部分に紐付けてタグで分けるようにした。(コードは後述)
タグは+ -> 0, - -> 1, × -> 2, ÷ -> 3
にしている
@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) {
}
@IBOutlet weak var label: UILabel!
@IBOutlet weak var resultLabel: UILabel!
@IBAction func back(_ sender: Any) {
}
変数の定義と計算するためのコードを記載
どのボタンがタップされたかをタグによって識別し、計算結果をresult
に格納する。
結果表示画面で計算式をlabel
に出力するため、記号をsymbol
に格納する
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"
とする。
ResultViewControllerで必要となる変数を定義
var firstNum = Int() // textFieldの値1
var secondNum = Int() // textFieldの値2
var symbol = String() // 計算記号
var result = Int() // 計算結果
記号ボタンをタップした際に計算して、ResultViewControllerに遷移する
@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メソッド
で遷移元に変えることができる
@IBAction func back(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
計算結果をViewControllerに返す
ResultViewControllerでprotocolを定義し、委任先のViewControllerで実行する
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
}
}
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)
}
}
完成コード
お粗末ですが、記載したコードです。
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
}
}
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の時にする処理を書いていないからエラーしちゃうのか?