久々にiOSアプリを作りたくなってSwiftUIを試してみた (ボタン動作編)

## これはなに
最近、叔父とApple Storeに行く機会があったのですが、そこで大量のiPhoneを目の当たりにした時に、ふとiOSアプリを作りたくなったので、その過程をまとめました。

第一弾のレイアウト編はこちらです。
https://qiita.com/inukai-masanori/items/84ade7257c132c81ac0d


## リスト内の要素をボタンに変更
現状のリストの中身は、画像とテキストが表示されているだけなので、ボタンに変更して行きます。

```swift
Button(action: {
    // 押された時の動作
}) {
    // 表示する内容
}
```
※一つ一つの書き方は覚えていないので、パーツ名 + swiftUI などをWebブラウザで調べてます。

Xcodeのライブラリからパーツを調べてそのまま、ソースコード内にドラッグ&ドロップすることも可能です。

![スクリーンショット 2023-12-05 15.42.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2322481/b5695c9e-1f29-99d0-b94a-7913cdcd935d.png)

実際に書き換えると、下記みたいになると思います。

![スクリーンショット 2023-12-05 16.19.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2322481/28ce07e3-68c9-d7e2-1928-694eb29afa4b.png)

しかしながらボタンに変更したことによって、全体的に青文字になってしまったので、黒にします。
レイアウト編では細かく説明していませんでしたが、ナビゲーションにタイトルを設定した時のような感じで設定します。

```swift
Button(action: {
    // 押された時の動作
}) {
    HStack {
        Image(systemName: "circle")
        Text("タスク1")
    }
}
.foregroundColor(.black)
```

`.foregroundColor()` を **modifier(モディファイア)** と呼びます。

>TextやVStackなどのView構造体の属性を変更するための修飾子
View構造体に対して「.モディファイア名」の形式で指定でき、引数などを渡すことで様々な変化をさせることが可能

他にも色々な modifier があるので興味がある方は試してみてください。


## ボタンの挙動を作る

ボタンの枠組みができたら、ボタン押下後にチェックマークが完了になるようにしていきます。
処理を作るにあたり、それぞれのタスクの状態を管理できるように、プロパティを作ります。
※プロパティや型などの説明は省きます。

```swift
struct ContentView: View {
    
    var data = [(title: "タスク1", checked: false),
                (title: "タスク2", checked: false),
                (title: "タスク3", checked: false)]
    
    var body: some View {
```

プロパティを作ったので、リスト内の要素もこのプロパティを使い、作成できるようにしていきます。

List に関しては、引数を加えることで繰り返しの構文にも対応しています。

```swift
List(0..<data.count, id: \.self) {index in 
    // TODO
}
```

このあたりも詳しい説明は省きますが、やっていることとしては、0から数えて配列の個数に到達するまで `{}`の中の処理を繰り返しています。またその際にindexには0から数えて今何回目の繰り返しをしているかの数字が入っています。

`id: \.self` については説明すると長くなってしまうので、今回はおまじない程度に思っていただき、興味がある方はぜひ調べてみてください。



繰り返しを利用して、Listをスッキリさせてみましょう。

```swift
List(0..<data.count, id: \.self) {index in
    Button(action: {
        // 押された時の動作
    }) {
        HStack {
            Image(systemName: "circle")
            Text(data[index].title)
        }
    }
    .foregroundColor(.black)
}
```

だいぶスッキリしましたね。
下準備ができたところで、ここから完了・未完了の動作を作ります。

まずは 変数のcheckedの値に応じて、画像を変化させます。

```swift
if data[index].checked {
    Image(systemName: "checkmark.circle.fill")
} else {
    Image(systemName: "circle")
}
```
![スクリーンショット 2023-12-05 17.47.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2322481/f2b8778b-892c-61eb-ff78-fe3313b9a723.png)

次に、実際にタップした際に状態を切り替えるようにします。
ボタンの action: ブロック内に処理を追加します。

```swift
Button(action: {
    data[index].checked.toggle()
}) {
    HStack {
        Text(data[index].title)
    }
}
```

ボタンが押された時に、値を反転させています。
`toggle()` は Bool型 に使える値を反転させるメソッドです。

しかしながらこれだけではエラーになってしまいます。

![スクリーンショット 2023-12-05 18.13.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2322481/26c89aeb-be2d-4e18-f162-27a38b9313f6.png)


これは、SwiftUIのViewはstruct(構造体)によって構成されており、struct内のプロパティは
基本的に保持する値の変更ができないためになります。

これはプロパティに @State を付与することで解決できます。
- @State とは ?
    - プロパティの値の更新が可能になる
    - プロパティの値の更新を監視し、通知が来たら自動でViewを再描画する

```swift
@State var data = [(title: "タスク1", checked: false), ....]
```

このあたりもやや難しいとは思いますが、Viewの中でプロパティの値を変更する際には基本必要というくらいの感じで思っておいていただければ大丈夫です。
興味があれば調べてみてください。


最終的なプログラムは以下です。

```swift
import SwiftUI

struct ContentView: View {
    
    @State var data = [(title: "タスク1", checked: true),
                       (title: "タスク2", checked: false),
                       (title: "タスク3", checked: false)]
    
    var body: some View {
        NavigationView {
            List(0..<data.count, id: \.self) {index in
                Button(action: {
                    data[index].checked.toggle()
                }) {
                    HStack {
                        if data[index].checked {
                            Image(systemName: "checkmark.circle.fill")
                        } else {
                            Image(systemName: "circle")
                        }
                        Text(data[index].title)
                    }
                }
                .foregroundColor(.black)
            }
            .navigationTitle("タスクアプリ")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

![スクリーンショット 2023-12-05 18.40.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2322481/4ce6512a-ff1e-f8f8-4109-376d440cbb7c.png)



## まとめ(感想)
レイアウトに続き、動きの実装についてもすごく簡単にできることがわかったと思います。
(ただ少しだけレベルが上がって、HTMLを触ったことがあるだけでなく、ある程度のプログラム知識も必要な気はしてます)

本当のタスク管理アプリではデータの保存 (アプリを終了しても状態がリセットされない) についての処理や タスクの追加・削除 もできないといけないですが、少し長くなりそうだったのでここまでにしました。

データの保存については、またタイミングをみて記事にしようと思います。

タスクの追加・削除については、今回までの解説でレイアウトや動作は作れると思うので、興味がある方はぜひ挑戦してみてくだい。