TextFieldから数字の場合だけを取り出して処理を行いたい時があると思います。
BMIと適正体重の計算を例に、その処理の書き方をいくつかまとめてみました。
目次
- 前提確認
- TextFieldから数字を取り出してみる
- 処理を切り出してみる
- 全体のコード
- まとめ
前提確認
- MacOS Catalina 10.15.4
- Xcode 12.1
- Swift version 5
今回計算するBMIと適正体重の求め方は次の通りです。
計算結果は小数点第3位で四捨五入し、Double型で表示することにします。
BMI = 体重kg * (身長m)^2
適正体重 = (身長m)^2/22
TextFieldから数字を取り出してみる
TextFieldの値(=TextField.text)を数字として取り出すには3つのステップがあります。
①TextField.textがnilかどうかチェック、nilなら空文字を返す
②①の値をDouble型で取り出す
③②のnilチェック(もしTextField.textが空文字もしくは文字が入っていた場合、②はnilとなるため)
こうして取り出した値は初めて数字として計算することができます。
では、③をguardを使いつつ、コードを書いてみましょう。
import UIKit
class ViewController: UIViewController {
@IBOutlet weak private var textField1: UITextField! //身長cmを記入
@IBOutlet weak private var textField2: UITextField! //体重kgを記入
@IBOutlet weak private var bmiOutputLabel: UILabel! //bmiを出力するLabel
@IBOutlet weak private var standardWeightOutputLabel: UILabel! //適正体重を出力するLabel
@IBAction func calculate(_ sender: Any) {
let height = Double(textField1.text ?? "") //①textFeild.textがnilかチェックし、②Double型として身長を取り出す
let weight = Double(textField2.text ?? "") //①textFeild.textがnilかチェックし、②Double型として体重を取り出す
var bmi = Double()
var standardWeight = Double()
//③nilチェック、アンラップする
guard let _height = height, let _weight = weight else {
return //nilならここで処理を止める
}
//身長をcmからmに直す
let meterHegiht = _height/100
//それぞれ小数点第3位で四捨五入する
bmi = round((_weight/(meterHegiht*meterHegiht))*100)/100
standardWeight = round(((meterHegiht*meterHegiht)*22)*100)/100
//Labelに表示
bmiOutputLabel.text = "\(bmi)"
standardWeightOutputLabel.text = "\(standardWeight)"
}
}
TextFieldから数字を取り出す処理としては、こんな感じです。慣れないうちは面倒だなと思うかもしれません。
もちろんこれでも良いのですが、あとあと保守しやすいよう、この計算を更に別のクラスに切り出してみましょう。
処理を切り出してみる
別クラスにこの処理を書いてみましょう。
イメージとして次のように書いてみたいです。
import UIKit
class ViewController: UIViewController {
@IBOutlet weak private var textField1: UITextField!
@IBOutlet weak private var textField2: UITextField!
@IBOutlet weak private var bmiOutputLabel: UILabel!
@IBOutlet weak private var standardWeightOutputLabel: UILabel!
private var calculate = Calculate()
@IBAction func calculate(_ sender: Any) {
calulate.calculate(inputHeight:textField1.text, inputWeight:textField2.text)
}
//計算の処理を別クラスに切り分ける
class Calculate{
func calculate(inputHeight:String?, inputWeight:String?){
let height = Double(input.height ?? "")
let weight = Double(input.weight ?? "")
guard let _height = height, let _weight = weight else {
return
}
let meterHegiht = _height/100
let bmi = round((_weight/(meterHegiht*meterHegiht))*100)/100
let standardHeight = round((meterHegiht*meterHegiht)*22)*100/100
}
}
ただ、これにはいくつか問題が含まれています。
一つ一つ見ていきましょう。
①返したい値が2つ
Calulateクラスのcalculateメソッドで返したい値はBMIと適正体重の2つです。
2つのDouble型を返のにはどうすれば良いでしょうか。
func calculate(inputHeight:String?, inputWeight:String?) -> //2つのDouble型?
そこで2つ以上の値を管理するのに便利なstructが使えます。
struct Output {
let bmi : String
let standardWeight :String
}
今回、出力する値も2つなら、入力する値も2つ(身長と体重)なので、こちらもstructを使ってみましょう。
ただ、こちらはTextFieldからの値が入り、nilになる可能性もあるため、オプショナル型とします。
struct Input {
let height : String?
let weight : String?
}
さて、上二つを使うと次のようにまとめられそうです。
import UIKit
class ViewController: UIViewController {
(略)
}
struct Input {
let height : String?
let weight : String?
}
struct Output {
let bmi : String
let standardWeight :String
}
class Calculate{
func calculate(input: Input) -> Output{
let height = Double(input.height ?? "")
let weight = Double(input.weight ?? "")
guard let _height = height, let _weight = weight else {
return
}
let meterHegiht = _height/100
let bmi = round((_weight/(meterHegiht*meterHegiht))*100)/100
let standardHeight = round((meterHegiht*meterHegiht)*22)*100/100
return Output(bmi: "\(bmi)", standardWeight:"\( standardHeight)")
}
}
structを使用して入力値と出力値をまとめられて、スッキリしましたね。
ただ、実はこれだとguardの中身でエラーが発生します。
というのも、返り値としてOutputを設定しているのに、returnだけだと何も返さないので怒られます。
次のようにすることもできますが、せっかくならもう少し汎用性のある方法で書いてみましょう。
guard let _height = height, let _weight = weight else { //heightまたはweightがnilであれば次の値を返す
return Output(bmi: "誤入力", standardWeight: "誤入力") //今回たまたまOutputの中身がStringなのでこういう書き方ができる
}
②ErrorTypeを定義
ということで、guardの条件をクリアしなかった場合はそれをErrorとして補足しましょう。
例のErrorTypeプロトコルを適合させたenumでエラーパターンを定義します。この辺理解曖昧な方はこちらの記事をご参照。
enum Validation: Error {
case 正しく入力されていない(result: String)
}
guardをクリアしなかった場合、このエラーバターンをthrowすることにします。
struct Input {
let height : String?
let weight : String?
}
struct Output {
let bmi : String
let standardWeight :String
}
class Calculate{
enum Validation: Error {
case 正しく入力されていない(result: String)
}
func calculate(input: Input) throws -> Output{
let height = Double(input.height ?? "")
let weight = Double(input.weight ?? "")
guard let _height = height, let _weight = weight else {
throw Validation.正しく入力されていない(result: "正しく入力されていません")
}
let meterHegiht = _height/100
let bmi = round((_weight/(meterHegiht*meterHegiht))*100)/100
let standardHeight = round((meterHegiht*meterHegiht)*22)*100/100
return Output(bmi: "\(bmi)", standardWeight:"\( standardHeight)")
}
}
これで切り分けた方の処理を完了です。
最後にViewController内の処理を書きましょう。
③do構文で仕上げ
今回エラーパターンを含む処理を書くので、例のdo構文を使います。
と言っても非常にシンプルです。
import UIKit
class ViewController: UIViewController {
@IBOutlet weak private var textField1: UITextField!
@IBOutlet weak private var textField2: UITextField!
@IBOutlet weak private var bmiOutputLabel: UILabel!
@IBOutlet weak private var standardWeightOutputLabel: UILabel!
private var calculate = Calculate()
@IBAction func calculate(_ sender: Any) {
do {
let Output = try calculate.calculate(input: Input(height: textField1.text, weight: textField2.text))
bmiOutputLabel.text = "\(Output.bmi)"
standardWeightOutputLabel.text = "\(Output.standardWeight)"
}catch let Calculate.Validation.正しく入力されていない(result: msg){
print(msg)
}catch{
}
}
}
全体のコード
まとめるとこうなります。
import UIKit
class ViewController: UIViewController {
@IBOutlet weak private var textField1: UITextField!
@IBOutlet weak private var textField2: UITextField!
@IBOutlet weak private var bmiOutputLabel: UILabel!
@IBOutlet weak private var standardWeightOutputLabel: UILabel!
private var calculate = Calculate()
@IBAction func calculate(_ sender: Any) {
do {
let Output = try calculate.calculate(input: Input(height: textField1.text, weight: textField2.text))
bmiOutputLabel.text = "\(Output.bmi)"
standardWeightOutputLabel.text = "\(Output.standardWeight)"
}catch let Calculate.Validation.正しく入力されていない(result: msg){
print(msg)
}catch{
}
}
}
struct Input {
let height : String?
let weight : String?
}
struct Output {
let bmi : String
let standardWeight :String
}
class Calculate{
enum Validation: Error {
case 正しく入力されていない(result: String)
}
func calculate(input: Input) throws -> Output{
let height = Double(input.height ?? "")
let weight = Double(input.weight ?? "")
guard let _height = height, let _weight = weight else {
throw Validation.正しく入力されていない(result: "正しく入力されていません")
}
let meterHegiht = _height/100
let bmi = round((_weight/(meterHegiht*meterHegiht))*100)/100
let standardHeight = round((meterHegiht*meterHegiht)*22)*100/100
return Output(bmi: "\(bmi)", standardWeight:"\( standardHeight)")
}
}
まとめ
いかがでしたでしょうか。
最初のコードより少し長くなってしまいましたが、処理を切り分けられたの保守しやすくなったのではないでしょうか。
また、structで値も一括管理しているので、可読性も良くなったと思います。
自分がこの処理を書くまでにで辿った思考を一つ一つ言語していきましたが、何かの参考になれば幸いです。
ご指摘等ございましたら頂けますと幸いです。