【SwiftUI】基本的な Property Wrapper の使い方

SwiftUIでよく利用するアノテーション（Property Wrapper）を簡単にまとめました

## テーマ
- 各PropertyWrapperの関係が ひと目でわかる 
- 1つ1つについて簡潔にまとまっている 
- チートシートとしてアプリ作成時に使える

## 経緯
SwiftUIアプリ開発をする中でよく出てきたのが Property Wrapper でした
しかし、初学者には それぞれどうやって使うのかが分かりにくかったため、チームメンバーと一緒に勉強し、その内容をまとめることにしました
この投稿では、SwiftUIを学ぶときに、最初に欲しかった SwiftUI Property Wrapperについての説明をします

## Property Wrapper： プロパティ ラッパー
まず Property Wrapper って何？
プロパティに付加的な機能を与えるためのSwiftUIの言語機能です
監視対象のデータが更新されるとViewを更新してくれます

この記事で解説する各 Property Wrapper の関係を図で表したものが以下になります
![SwiftUI_PropertyWrapper関係図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3881350/197d9cf2-b0b1-9f96-c81e-f624d435c11d.png)


## State 
値の更新に応じてViewが自動更新されるようになります
プリミティブ型プロパティ（IntやStringなど）にのみ付加できます
```swift
struct ItemResisterView: View {
    @State var newItem: String = "" // @Stateを付加
    var body: some View {
        VStack {
            TextField("新規アイテム名", text: $newItem) // 入力による操作
            Text(newItem) // 入力したものがリアルタイムで画面に表示される
```

## Binding
親Viewが持っている`@State`プロパティと状態を共有し、双方向で更新ができるようになります

- **親View側**
    - 子Viewを呼び出す際に`@State`プロパティを渡します
    - $を付けることで子Viewの持つ`@Binding`プロパティに紐づきます
- **子View側**
    - `@Binding`プロパティを用意します
    - 親View側のプロパティを参照するので 初期値は不要です
```swift
// 親View
struct TabBarView: View {
    @State var listName: String = "買うものリスト" // @Stateプロパティ(対象)
    var body: some View {
        ShoppingListView(listName: $listName) // 子Viewの呼び出し(@Stateプロパティを乗せる)
// 子View
struct ShoppingListView: View {
    @Binding var listName: String // @Bindingを付加(親ViewのlistNameと状態を共有する)
    var body: some View {
        VStack {
            TextField("リスト名", text: $listName) // 入力内容が親Viewにも反映される
```

## Environment
環境値を利用することで、親Viewから孫Viewへ（経由される子Viewを意識せず）状態の共有ができるようになります

- **読み取りの形式**
    - `@Environment(\.keyPath)`
- **書き込みの形式**
    - `.environment(Environment用のKeyPath, 書き込む値)`
```swift
// CoreDataで必要なNSManagedObjectContextの状態を環境値に保存して利用する場合
@main // 起動時
struct ZeroApp: App {
    let persistenceController = PersistenceController.shared
    var body: some Scene {
        // 書き込み
        .environment(\.managedObjectContext, persistenceController.container.viewContext)

// 値を利用したいView
struct ShoppingListView: View {
    // 読み取り
    @Environment(\.managedObjectContext) var viewContext
```

## ObservedObject
オブジェクト型プロパティの変更を監視し、値の更新に応じてViewが自動更新されるようになります
- **`:ObservableObject`**
    - 監視対象のクラスは`ObservableObject`プロトコルに準拠する必要があります
- **`@Published`**
    - 監視対象のクラスの変更を検知するプロパティに付加します
- **`@StateObject`**
    - 監視対象のインスタンスを保持するView側で、インスタンス定義時に付加します
    - ライフサイクルは「自身のViewが表示されてから非表示になるまで」です
    - データの発生源は自身のViewであるため 監視対象のオブジェクトをインスタンス化して自身で保持します
- **`@ObservedObject`**
    - 親Viewから参照している監視対象のインスタンスに対して付加します
    - ライフサイクルは「親ビューが表示されてから非表示になるまで」です
    - 親Viewから渡されるデータを参照する際に用いるため 子View自身でインスタンス化して保持するのはNGです
    - 親Viewと同じ監視対象のオブジェクトを参照するため 状態は共有されます
```swift
// データオブジェクトの準備
class User: ObservableObject { // ObservedObjectに準拠したクラスを用意する
    @Published var name = "" // 監視対象のプロパティに@Publishedを付加する
}
// 親View
struct TabBarView: View {
    @StateObject var user = User() // 状態オブジェクトとしてインスタンス化する
    var body: some View {
        Text(user.name)
        ShoppingListView(user: user) // そのインスタンスを監視対象オブジェクトとして子ビューに渡す
// 子View
struct ShoppingListView: View {
    @ObservedObject var user: User // @ObservedObject属性を付加(親Viewのuserを参照する)
    var body: some View {
        TextField("名前", text: $user.name) // 入力による操作
        Text("\(user.name)さんのリストです") // 入力したものがリアルタイムで画面に表示される
```

## FetchRequest
CoreDataに保存しているデータベースからソート条件を指定してデータを取得することができます

例えば、CoreDataに以下のようなEntityが存在する場合
![zeroEntity例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3881350/16a3d978-9eea-5e7b-1a04-e3db7a55b1f0.png)

`@FetchRequest`を利用してデータを取得する実装は以下となります
- `sortDescriptors`：　取得した結果のソート順を定義します
- `predicate`：　取得された結果をフィルタリングします
- `animation`：　変更アニメーションを指定します
```swift
@FetchRequest (
    // nameの昇順でソートする
    sortDescriptors: [NSSortDescriptor(keyPath: \ZeroEntity.name, ascending: true)],
    // stateがtrueの場合に絞る
    predicate: NSPredicate(format: "state == \(true)") ,
    animation: .default
)
private var shoppingList: FetchedResults<ZeroEntity> // ソートされたデータが入る
```

## まとめ
各 Property Wrapper の使い時をざっくりまとめると以下になります
| Property Wrapper | いつ使うか |
|---|---|
| State | プリミティブ型の値の変更をViewに自動反映させたいとき |
| Binding | 親Viewと値を共有したいとき |
| Environment | 親Viewから孫Viewへ値を反映させたいとき |
| ObservedObject／StateObject／Published | オブジェクト型インスタンスの変更を監視したいとき  |
| FetchRequest | CoreDataからデータを取得したいとき |