【SwiftUI】Stateプロパティラッパーとバインディングの基本

# この記事は何
プログラミング初心者が、SwiftUIフレームワークを使ったアプリ開発で最初につまづきやすいのが`@State`と`@Binding`の理解。

`@State`と`@Binding`を理解するために、実践的なコードを例に挙げて解説する。

![全体](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70476/d4011550-da2a-33da-6cd5-982ad2fc9f8d.png)

> Swiftを基礎から学ぶには
自著、工学社より発売中の「[まるごと分かるSwiftプログラミング](https://amzn.asia/d/7YPWJwl)」をお勧めします。変数、関数、フロー制御構文、データ構造はもちろん、構造体からクロージャ、エクステンション、プロトコル、クロージャまでを基礎からわかりやすく解説しています。

# Stateプロパティラッパー

以下に定義する`MusicPlayer`ビューは、テキストとボタンからなる単純な画面。
ボタンをタップするたびに、画面（UI）は「一時停止」と「再生」の状態を交互に変化する。

```swift
import SwiftUI

struct MusicPlayer: View {
    @State var isPlaying = false
    
    var body: some View {
        VStack {
            Text("Awesome music")
                .font(.title)
                .foregroundStyle(isPlaying ? .primary : .secondary)
            Button {
                isPlaying.toggle()
            } label: {
                Image(systemName: isPlaying ? "pause.circle" : "play.circle")
                    .font(.largeTitle)
                    .foregroundColor(.blue)
            } 
            .padding()
        }        
    }
}
```

上のコードでは、`isPlaying`変数に`@State`がマークされている。
`@State`がマークされた変数は「ビューのステート（状態）」であり、SwiftUIによって「その値の状態」が追跡される。
`@State`変数の値が変更されると、SwiftUIはそのビューを更新して、画面の最新状態を表示する。

![‎State and Binding Basics.‎001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70476/a0a4cf47-84b9-1eb8-4266-bba5e057dbf1.png)


この`isPlaying`変数については、`body`プロパティの外側で宣言されている点にも注目。
これは、「ビューとステート（状態）の分離」を意味している。

# Bindingプロパティラッパー

上記の例における`MusicPlayer`型は、定義するUI自体は単純な割にコードが複雑に見える。
そのような場合、一部のビューを抽象化することで、保守性や可読性を改善できる。

以下のコードは、ボタン部分だけを抽象化した`PlayButton`ビューを定義する。

```swift:PlayButtonビュー
struct PlayButton: View {
    @Binding var isPlay: Bool
    
    var body: some View {
        Button {
            isPlay.toggle()
        } label: {
            Image(systemName: isPlay ? "pause.circle" : "play.circle")
                .font(.largeTitle)
                .foregroundColor(.blue)
        } 
        .padding()
    }
}
```

上のコードでは、`isPlay`変数にマークされた`@Binding`に注目。
この`isPlay`変数は`Bool`型だが、既定値がない。
したがって、`PlayButton`ビューの初期化の際には既定値を設定することになる。
`@Binding`な変数を初期化するために設定できる既定値は通常の値ではなく、「SwiftUIが追跡する`@State`変数への参照」を設定する。
例えば、`PlayButton()`イニシャライザの`isPlay`パラメータに設定するのは通常の`Bool`値ではなく、`Binding<Bool>`型の**バインド値**を渡す。

# ビューにバインドを渡す

`@Binding`な変数を初期化するために「SwiftUIが追跡する`@State`変数への参照」を設定するには、`@State`な変数の先頭に`$`記号を記述する。

以下のコードでは、`PlayButton`ビューの`@Binding`変数を初期化する際に「SwiftUIが追跡する`@State`変数への参照」を設定している。

```swift:ボタンを抽象化したコード
struct MusicPlayer: View {
    @State var isPlaying = false
    
    var body: some View {
        VStack {
            Text("Awesome music")
                .font(.title)
                .foregroundStyle(isPlaying ? .primary : .secondary)
            PlayButton(isPlay: $isPlaying)
        }        
    }
}
```

上のコードにおいて、`$isPlaying`は「SwiftUIが`PlayButton`ビューを経由して、`isPlaying`変数を追跡する」ことを意味する。
つまり、`PlayButton`ビューに「`isPlaying`変数のバインドを渡した（あるいは、`isPlaying変数はバインドされた`）」ことになる。

![‎State and Binding Basics.‎002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70476/94d0e7ef-dc35-977b-33bf-7fcb602c7271.png)


`MusicPlayer`ビューのコードは保守性と可読性が改善されて、実際のUI画面と同じくらい単純に出来た。