[SwiftUI]データバインディングを理解する

## はじめに
SwiftUIで開発をしてると、**データバインディング**という言葉をよく聞くかもしれません。
**データバインディング**は、UIとデータの同期を自動で行う強力な機能です。

**データバインディング**を学ぶと他にも
「MVVM」「状態管理」「プロパティラッパー」等、様々なワードが関係してきます。

本記事は上記の説明だけでなく、
サンプルコードも紹介しますので参考になれば嬉しいです！

## データバインディングとは？
データの状態を監視する仕組みを言います。

データとビューの間の双方向のデータフローを自動的に管理できたり、
SwiftUIではユーザ操作により画面の表示をリアルタイムで変更が可能になります！

:::note info
データが変更される
→そのデータを参照しているUI要素も自動的に更新される
※逆に、UIから入力された値をデータに反映も可能！
:::


## MVVMとデータバインディングの関係
### 1. アーキテクチャを簡単にイメージしよう
アーキテクチャの種類に**MVVM**というのを聞いたことがありますでしょうか？
データバインディングではこの**MVVM**の理解も重要です。

### 2. MVVMとは？
SwiftUIでは、 **MVVM（Model-View-ViewModel）** がよく使われます。
MVVMは、以下の3つのコンポーネントに分かれます:

:::note info
#### MVVMの詳細
・Model: データやビジネスロジックを管理 (DB等に管理されるデータ)
・View: ユーザーに表示するUIを管理(画面に表示される部分)
・ViewModel: Model と View を繋ぎ、Viewが直接Modelに依存しないようにする
:::

### 3. データバインディングとの関係は？
「View」と「ViewModel」の間で、データバインディングが重要な役割を果たします。

「View」は、「ViewModel」内のデータとバインドされます。
そしてViewModelがデータを更新するとViewも自動的に更新されます。

![Model-View-ViewModel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/945342/2e47aa1e-1cca-0297-386d-76d38b6749f0.png)


## データバインディングには、状態管理が必要
データバインディングが効果的に機能するためには、**状態管理**が必要です。
SwiftUIでは、**状態管理**に関するプロパティがいくつか提供されています。

ここで**プロパティラッパー**という単語について触れます。

### プロパティラッパーについて
プロパティに対し、なんらかの特定の動作や機能を提供するために
使える仕組みのことを言います。

いくつか例を挙げておきます。

### 具体的なプロパティラッパー
| 種類 | 説明 |
|:-----------|:------------|
|State | ローカルの状態管理に使用されます。変更されるとviewが再描画されます。|
|Binding| 親viewから受け取ったデータをバインドするために使用します。子viewがこのデータを変更でき、その変更は親viewに反映されます。|
|ObservedObject| 外部のオブジェクトの状態を監視するために使用します。通常は、ViewModelとして利用します。|
|StateObject| viewが自身で管理するViewModelなどのオブジェクトに使用され、ライフサイクルを管理します。|
|EnvironmentObject| 複数view間で共有されるオブジェクトに使用します。グローバルな状態管理が必要な場合に便利です。|


## プロパティラッパーの導入と説明
SwiftUIにおけるプロパティラッパーは、データバインディングや状態管理を簡単にするための便利な機能です。上記で述べた各プロパティラッパーについて、さらに詳しく見ていきましょう。

### State
Stateは、ビューがローカルに保持する状態を宣言します。
ローカルなデータが変更されるたびにビューが再描画されます。

```state.swift
struct ContentView: View {
    // ローカルに「counter」を保持する
    @State private var counter = 0

    var body: some View {
        VStack {
            // 「counter」値を表示
            Text("Counter: \(counter)")

            // 「counter」値を1増やす
            Button("Increment") {
                counter += 1
            }
        }
    }
}

```

### Binding
Bindingは、親viewが管理する状態を子viewに渡すときに使います。
親viewの状態と子viewの状態が同期されます。

```binding.swift
struct ContentView: View {
    @State private var isOn = false

    var body: some View {
        ToggleView(isOn: $isOn)
    }
}

struct ToggleView: View {
    @Binding var isOn: Bool

    // 状態が変わると、データが同期される
    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

```

### ObservedObject
クラスベースの外部オブジェクトの変更を監視します。
このオブジェクトは ObservableObject プロトコルを準拠しており、
状態が変更されたときにviewに通知されます。

```observedObject.swift
class CounterModel: ObservableObject {
    @Published var count = 0
}

struct ContentView: View {
    @ObservedObject var model = CounterModel()

    var body: some View {
        VStack {
            Text("Count: \(model.count)")

            // 状態が変わると、viewに通知される
            Button("Increment") {
                model.count += 1
            }
        }
    }
}
```

### サンプルコード
これまでの内容を踏まえて、
データバインディングを活用したシンプルなカウンターアプリの例を見てみましょう。

```sample.swift
class CounterModel: ObservableObject {
    @Published var count = 0
}

struct ContentView: View {
    // CounterModelを管理
    @StateObject private var model = CounterModel()

    var body: some View {
        VStack {
            // 「model」の値が記載される
            Text("Count: \(model.count)")
                .font(.largeTitle)

            // 「model」の値を1増やす
            Button("Increment") {
                model.count += 1
            }
            .padding()
        }
    }
}

```

:::note info
上記は「StateObject」を使って「CounterModel」を管理し、
カウントが増加するたびにviewが自動的に更新されます。
:::

:::note alert
「StateObject」を宣言時に付与(wrap)しないと、
**Cannot assign to property: 'self' is immutable**
というエラーが発生します。
:::

## まとめ
SwiftUIのデータバインディングで、画面とデータをリアルタイムで同期できる仕組みを
理解いただけましたでしょうか？

MVVMやプロパティラッパーといった、
状態管理に関しても理解が深まると幸いです！