【Swift】MVCで電卓アプリを作成してみた

#はじめに

本記事をご覧いただきありがとうございます。

私ごとですが、先日初の個人アプリをリリースしました。
アプリのアーキテクチャはMVCで構築したのですが、なかなか可読性が悪く改善しなければと思いましたので
MVCについて色々調べることにしました。

その調べた内容をもとに電卓アプリを作ったので皆さんに共有できればなと思い記事を書いております。
正しい知識かはわかりませんが使えそうなところは参考にしていただけると幸いです。

また、作成したコードはGitHubにありますのでぜひご覧ください。 -> [コチラ](https://github.com/onishi-app/calculator)

#MVCとは

MVCとはアーキテクチャの中の一つです。
アーキテクチャとは、アプリを綺麗に開発・継続的に運用していくための設計方法になり、
MVCの他にも、MVPやMVVM、Clean Architectureなどが存在します。

MVCは、Model、View、Controllerの頭文字をとった単語になります。
**・Model**
ユーザが入力した情報を下に計算などを行う

**・View**
UI（ユーザが見る画面）の設定など

**・Controller**
ユーザからの入力を検知しModelに渡す

ゲームで例えると、
ゲームコントローラーはContoller
PS4やSwitchはModel
テレビ画面やゲーミングモニタはView　という認識でいいと思います。

①ゲームプレイヤーがジャンプボタンを押す（Controller）
　・入力を検知する（押された強さや押された時のキャラクターの場所の情報を取得）
　・PS4（Model）に取得したデータを送信する

②PS4で情報の計算（Model）
　・押された強さによってジャンプの高さの変更などを行う
　・キャラクターのいる場所によってジャンプできる高さを変更する
　・計算した情報をもとにキャラクターを動かす

③テレビに表示（View）
　・ジャンプしている姿を表示する
　
今回作成予定の電卓アプリで例えると、
表示されている画面がView
ボタンを押した時の処理がController
四則演算に応じた演算結果を返すのがModel　という認識だと思います。

##MVCで電卓アプリの作成

###完成形
![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/e4a29973-b263-0995-b69c-66d25c51d384.gif)



まず、アプリを作成するにあたりどのような機能にするかを簡単に決めておきます。
要件定義書的なものですかね！

###UI設計
まずUI設計は次のような感じにしたいと思います。（まるパクリ）
![電卓.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/c821b633-f415-ed1b-3fb7-0d1ab7026973.png)

ここの情報は全てMVCのV（View）に当たるところだと思います。

###機能設計
機能定義としましては、
**・Cボタン**
クリア（入力されている情報を全て消す）
**・+/-ボタン**
入力されている値の正負を反転させる
**・%ボタン**
入力されている値を％の値に変換する（１００で割る）
**・÷、×、-、+、=、各数字、.**
そのままの意味

という感じですかね。

ボタンがタップされた時にボタンの背景が一瞬グレーになったり、
+などを押すと押されたことがわかるように背景が黄色から白になります。

これらの処理はMVCのC（Controller）に書いていこうと思います。

上記以外の計算などの処理は全てMVCのM（Model）に書いていきます。

##実装

新規プロジェクトを作成し、Model、View、Controllerの3つのディレクトリを作成します。
今回のプロジェクト名はCalculatorにしました。

ディレクトリの作成方法は、Calculatorをcontrolを押しながらクリックするとメニューが出てきますので
New Groupを選択し名前を記入します。
![スクリーンショット 2021-02-21 19.06.30.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/f5f7b842-e320-16ae-97aa-1393664dfc78.jpeg)

3つのファイルが作成できたら、
既存のファイルを各ディレクトリに格納していきます。

Viewには、Storyboardやxib、Assetsなどを入れておけばOKだと思います。
それ以外はとりあえずControllerですね！

###View
では、まずはじめにViewの定義を行います。

とりあえずこんな感じですかね・・・？
![スクリーンショット 2021-02-21 19.26.35.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/56112297-e791-b40e-5da6-801db97dbbdf.jpeg)

今回はオートレイアウトなどはなしで進めていきたいと思います。

###Controller
本来ならボタンは丸型だったりするのですが、
それらの処理はControllerに記載していきます。

面倒ですが各ボタンを全てIBOutletでつなぎ、`button.layer.cornerRadius`で丸角にしています。
丸角にした結果がこちらです。
![スクリーンショット 2021-02-21 19.41.28.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/521c610d-af42-68cb-73f9-f0785613d8d1.jpeg)

もうこの時点で少し似てきましたね・・・。

次にボタンを押した時の処理を記述していきます。
内容は下記のようになります。

**・ボタンを押した時にラベルに出力する**
　->悩んだのですが、Controller->Model->Controller->Viewという流れにします。（これが正規の流れ？）
**・数字以外のボタンを押した時の処理を行う（Modelに情報を渡す）
・数字ボタンが押された時にボタンの背景色を一瞬明るくする（Controllerで実装）
・右列のボタンが押された時は背景と文字の色を逆にする（Controllerで実装）**

背景色などの変更を記述します。
何度もしつこいですが、計算などに関しはModelで行うのでControllerではUIの変更を行います。

StoryboardからIBActionでControllerと繋ぎます。
このIBActionには複数のボタンを重ねてtagで管理しています。

数字の0〜9のボタンはそれぞれtag0〜tag9にし、.（ドット）はtag10にしています。

```swift
// 「0〜9」, 「.」 を押した時の処理
@IBAction func pushNumberButton(_ sender: Any) {
    guard let button = sender as? UIButton else {
        return
    }
    // タップした瞬間ボタンの背景を明るくする
    button.backgroundColor = .lightGray
    // 0.5秒かけてボタンを元の色に戻す
    UIView.animate(withDuration: 0.5) {
        button.backgroundColor = .darkGray
    }
}
```

数字以外のボタンも似たようにしています。（詳しくはGitHubをご覧ください。）

###Model
Modelをcontrolを押しながらクリックし、New Fileで新しいファイルを作成します。

計算などを行うModelを作成したいのでCalcというファイル名にします。
![スクリーンショット 2021-02-23 21.37.50（2）.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/180a505a-0af5-7be5-8d46-3985a88553a2.jpeg)

Calc型を作成しそこにプロパティやメソッドを追加していきます。
このCalc型でさまざまな計算処理を記述しControllerで呼び出します。

例えば下記のような感じで記述し、
Controllerで呼び出せばラベルに表示するテキストを取得することができます。

さらに押されたボタンの数字をどんどん足していき最終的な値を保管しています。

自身の値を変更する場合は`mutating`キーワードが必要になります。

今回は＋やーが押されている場合と押されていない場合で処理を分けている箇所がほとんどです。
＋やーが押されていない場合は`x + y`の`x`の方を更新していきます。
＋やーが押されている場合は`y`の方を更新していきます。

```swift
struct Calc {
    // 一つ目の値
    var firstValue: Double = 0
    // 二つ目の値
    var secondValue: Double = 0
    // ラベルに表示する文字
    var firstText: String = ""
    var secondText: String = ""
    // +, -, ×, ÷ が押されていないかチェック
    var type: CalcType?
    
    // 数字もしくはドットが押された時の処理
    mutating func pushNumber(number: Int) -> String {
        // ÷, ×, +, - が押されているかどうかチェック
        if type == nil {
            // 押されていない場合
            if number == 10 {
                // 押したボタンがドットの場合
                if firstText == "" {
                    // 最初にドットを押した場合
                    firstText = "0."
                } else {
                    firstText += "."
                }
            } else {
                self.firstText += String(number)
            }
            
            // 最後がドットで終わる場合は末尾に0を追加する
            let value = number != 10 ? Double(firstText) : Double(firstText + "0")
            firstValue = value!
            
            return firstText
            
        } else {
            if number == 10 {
                if secondText == "" {
                    secondText = "0."
                } else {
                    secondText += "."
                }
            } else {
                self.secondText += String(number)
            }
            
            let value = number != 10 ? Double(secondText) : Double(secondText + "0")
            secondValue = value!
            
            return secondText
            
        }
    }
}
```

次に、+, -, ×, ÷などを押した時の処理も記述していきます。

１ + １まで押した後に「＝」を押さずに「ー」などを押しても計算は実行されますので、
そこの部分も再現してみました。

`alreadySetType(type: )`というのが、すでに＋やーが押されていた場合に呼び出すメソッドです。

```swift
// 数字以外のボタン押された時の処理
mutating func pushOther(buttonNumber number: Int) -> String {
    switch number {
    case 0:
        // Cが押された時の処理 -> 全ての情報を初期化する
        firstValue = 0
        secondValue = 0
        firstText = ""
        secondText = ""
        type = nil
        return "0"
    case 1:
        // +/-が押された時の処理
        if type == nil {
            // +や-が押されていない場合
            firstValue *= -1
            firstText = String(firstValue)
            return firstText
        } else {
            // すでに+や-が押されていた場合
            secondValue *= -1
            secondText = String(secondValue)
            return secondText
        }
    case 2:
        if type == nil {
            firstValue /= 100
            firstText = String(firstValue)
            return firstText
        } else {
            secondValue /= 100
            secondText = String(secondValue)
            return secondText
        }
    case 3:
        if let brforeType = type {
            let result = alreadySetType(type: brforeType)
            type = .divide
            return result
        } else {
            type = .divide
            return firstText
        }
    case 4:
        if let brforeType = type {
            let result = alreadySetType(type: brforeType)
            type = .multiply
            return result
        } else {
            type = .multiply
            return firstText
        }
    case 5:
        if let brforeType = type {
            let result = alreadySetType(type: brforeType)
            type = .minus
            return result
        } else {
            type = .minus
            return firstText
        }
    case 6:
        if let brforeType = type {
            let result = alreadySetType(type: brforeType)
            type = .plus
            return result
        } else {
            type = .plus
            return firstText
        }
    case 7:
        if type == nil {
            return firstText
        } else {
            var value: Double
            switch type {
            case .divide:
                value = firstValue / secondValue
            case .multiply:
                value = firstValue * secondValue
            case .minus:
                value = firstValue - secondValue
            case .plus:
                value = firstValue + secondValue
            case .none:
                return ""
            }
            firstValue = value
            firstText = String(firstValue)
            secondText = ""
            return String(value)
        }
    default:
        return ""
    }
}

mutating func alreadySetType(type: CalcType) -> String {
    var value: Double
    switch type {
    case .divide:
        value = firstValue / secondValue
    case .multiply:
        value = firstValue * secondValue
    case .minus:
        value = firstValue - secondValue
    case .plus:
        value = firstValue + secondValue
    }
    firstValue = value
    firstText = String(firstValue)
    secondText = ""
    return String(value)
}
```

#さいごに
個人的にはMVCは結構書きやすかったです！

**・ViewはUIの設計を記述
・Controllerはユーザの操作によって変化するUIの設定を記述
・Modelはそれ以外を記述**

この分け方があっているのかは分かりませんが、
このように分けているとどこに何が記載してあるかが分かりやすくなります。

また、さまざまなControllerで同じ処理を使用する際に
Modelを参照するだけなので可読性、メンテナンス性も向上するのかな・・・？

ただ、Controllerに全て記載する方が簡単な場合もあるかもしれないので、
そういう時にもわざわざModelを作るのが少し面倒ですね。

もう少し経験を積んでみて書きやすく理解しやすいコードの書き方を身につけたら
再度共有させていただきます。

最後までご覧いただきありがとうございました。