🍎

@Stateとボタンを活用してカウントアプリを作る

2024/12/27に公開

このChapterのゴール

習慣化したいことの回数をカウントするアプリを作る

学べること

  • Buttonの使い方
  • @Stateで状態を管理する方法
  • Viewの再描画の仕組み

達成までのステップ

  • カウントするためのButtonを設置する
  • @Stateを使って状態を管理する
  • Buttonをタップした時のロジックを記述する
  • 仕上げ

カウントするためのButtonを設置する

Buttonは以下のコードで設置します。

Button(
    action: { /* タップされた時の処理 */ },
    label: { Text("カウント") }
)

action引数は、ボタンがタップされたときの処理を記述する部分です。
主に、計算処理や状態の更新などの関数をここに書きます。

label引数は、ボタンの見た目を決める部分です。
TextやImageなどのビューを記述して、ボタンの外観をカスタマイズします。

今回は、ボタンの見た目を簡単に設定するために.buttonStyleというモディファイアを使用します。

設定例は以下の通りです。

Button(
    action: { /* タップされた時の処理 */ },
    label: { Text("カウント") }
)
.buttonStyle(.bordered)

ここまでの全体コード
struct ContentView: View {
    var body: some View {
        Button(
            action: { /* タップされた時の処理 */ },
            label: { Text("カウント") }
        )
        .buttonStyle(.bordered)
    }
}

@Stateを使って状態を管理する

Buttonが表示できたので、次にカウントした数字を画面に表示してみましょう。
そのために、カウントの値を保持するプロパティ(変数)を以下のコードで追加します。

@State var count: Int = 0

@Stateとは?

@Stateは、アプリの中で値が変わる可能性があるデータを管理するための仕組みです。

なぜ@Stateが必要なのかというと、通常、プログラムではデータが変わったときにその変化を画面に反映する仕組みを自分で書く必要があります。これをSwiftUIでは@Stateを使うだけで簡単に実現できます。

Buttonをタップした時のロジックを記述する

現在の状態では、ボタンを押しても何も変化がありません。そこで、定義したcountプロパティを使って、カウントを画面に表示してみましょう。

先ほど作成したButtonのaction引数に、countを1増やす処理を追加しましょう。

- Button(
-    action: { /* タップされた時の処理 */ },
-    label: { Text("カウント") }
-)
👇 変更
+ Button(
+    action: { count += 1 }, // count = count + 1と同じ意味
+    label: { Text("カウント") }
+)

次にcountプロパティの内容を画面に表示させるため、以下のVStackとTextを追加しましょう。

VStack(spacing: 16) {
  Text("筋トレ回数:\(count)") // 👈 ボタンの上の行にText追加

  Button() // 略
}

以下の挙動になればOKです。

ここまでの全体のコード
struct ContentView: View {
    @State var count: Int = 0

    var body: some View {
        VStack(spacing: 16) {
            Text("筋トレ回数:\(count)")
            
            Button(
                action: { count += 1 },
                label: { Text("カウント") }
            )
            .buttonStyle(.bordered)
        }
    }
}

再描画とは?

@Stateをつけたプロパティが変化した時、Viewは「再描画」されます。
「再描画」というのは、画面の見た目(UI)を新しいデータに基づいてもう一度Viewを作り直すことです。

Buttonをタップしてaction引数内の処理が実行された時、以下の順番の順番で実行し直します。

struct ContentView: View {
    @State var count: Int = 0 // ②
    
    var body: some View {
        VStack(spacing: 16) {
            Text("筋トレ回数:\(count)") // ③
            
            Button( // ④
                action: {
                    count += 1  // ① Buttonをタップした時
                },
                label: { Text("カウント") }
            )
            .buttonStyle(.bordered)
        }
    }
}

仕上げ

ゴールまで後もう少しです。
最後に見た目を良くするために仕上げをしていきましょう。

Textの見た目を変える

今のままだと文字のサイズが小さいので、モディファイアを使って見やすくしましょう。

Text("筋トレ回数: \(count)")
    .font(.largeTitle)
    .fontWeight(.bold)
    .foregroundColor(.white)
    .padding(16)
    .background(Color.gray)
    .cornerRadius(10)

Imageを使ってButtonの見た目を変える

次にボタンの見た目を変えていきましょう。
TextをImageに変えます。

 Button(
    action: { count += 1 },
-    label: { Text("カウント") }
-)
-.buttonStyle(.bordered)

👇変更

Button(
    action: { count += 1 },
    label: {
+        Image(systemName: "plus")
+            .bold()
+            .font(.title2)
+            .frame(width: 50, height: 50)
+            .background(Color.red)
+            .foregroundColor(.white)
+            .cornerRadius(25)
+            .shadow(radius: 2)
+    }
)

マイナスボタンを追加する

最後にマイナスボタンを追加して、カウントを減らす機能を追加しましょう。
HStackを使って横並びにButtonを追加していきます。

Button(
    action: {
        count = max(0, count - 1)
    },
    label: {
        Image(systemName: "minus")
            .bold()
            .font(.title2)
            .frame(width: 50, height: 50)
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(25)
            .shadow(radius: 2)
    }
)

プラスボタンと同じように、新たにButtonを追加します。
カウントを増やす処理と同じように、カウントを減らす処理をaction引数に追加します。

action: {
    count = max(0, count - 1)
},

カウントを増やす場合は通常、count += 1 のように書きます。同じ考え方で減らす場合は count -= 1 となりますが、この方法だとカウントが0以下になったとき、-1や-2のように負の値になってしまいます。

そこで、0以下にならないようにするために、max()メソッドを使います。

max(引数1, 引数2)メソッドは、「引数1と引数2のうち、大きい方の値を返す」という意味です。
つまり、count - 1 がマイナスになりそうな場合、代わりに0を返してくれます。

これを使うことで、カウントが0以下に減らないように制御できます。

以下のように表示できるようになると、今回のChapterのゴール達成です🚩

全体のコード
struct ContentView: View {
    @State var count: Int = 0
    
    var body: some View {
        ZStack {
            Color.gray.opacity(0.1).ignoresSafeArea()

            VStack(spacing: 30) {
                Text("筋トレ回数: \(count)")
                    .font(.largeTitle)
                    .fontWeight(.bold)
                    .foregroundColor(.white)
                    .padding(16)
                    .background(Color.gray)
                    .cornerRadius(10)
                
                HStack(spacing: 16) {
                    Button(
                        action: {
                            count = max(0, count - 1)
                        },
                        label: {
                            Image(systemName: "minus")
                                .bold()
                                .font(.title2)
                                .frame(width: 50, height: 50)
                                .background(Color.blue)
                                .foregroundColor(.white)
                                .cornerRadius(25)
                                .shadow(radius: 2)
                        }
                    )
                    
                    Button(
                        action: { count += 1 },
                        label: {
                            Image(systemName: "plus")
                                .bold()
                                .font(.title2)
                                .frame(width: 50, height: 50)
                                .background(Color.red)
                                .foregroundColor(.white)
                                .cornerRadius(25)
                                .shadow(radius: 2)
                        }
                    )
                }
            }
        }
    }
}

Discussion