SwiftUI Tutorialsに登場したProperty Wrappersまとめ

# はじめに
自分が[Apple公式のSwiftUI Tutorials](SwiftUI Tutorials | Apple Developer Documentation)を通して学んだ時に登場したProperty Wrappersがそれぞれどういう役割かとかを忘れちゃいそうなので備忘のために書き残すことにしました。

watchOSやmacOSのAppを作る予定は現状なかったので、Chapter3までに登場したProperty Wrappersの紹介になります。
これから同じくSwiftUIを始めて勉強する方々の参考になればと思います。

# Property Wrappersとは
Swift5.1で実装された機能です。
プロパティのget/setに関わる制御を共通化するような仕組みです。

この後紹介する`@State`や`EnvironmentObject`はProperty Wrappersですし、自分で新たなものを定義することが可能です。

# SwiftUI Tutorialsに登場したProperty Wrappers
## @&#65520;State
[State | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/state)

通常SwiftUIのViewはstructで定義していくためプロパティの更新ができないが、`@State`をつけて宣言することでその**プロパティの値の読み書き**ができる。

`@State`で宣言したプロパティはViewのbodyもしくはViewから呼び出されるメソッドからのみアクセスする必要があるとAppleから推奨されているので、基本的には`private`をつけて宣言するのが一般的のようだ。

Viewにプロパティの値を渡したい時は`$`を変数名につけることで実現できる。

```swift
struct ToggleView: View {
    /// トグルのスイッチの状態（初期値: false）
    @State private var isOn = false

    var body: some View {
        // isOnの値を監視（$をつける！）
        Toggle(isOn: $isOn) {
            Text("スイッチを切り替える")
        }
    }
}
```
これで下記画像のようにスイッチの状態を切り替えることができるようになる。
<img width='400' alt='switchOff.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550560/719b6702-0003-499e-d6c9-3a1597775b97.png'>
<img width='400' alt='switchOn.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550560/7b269f3e-c6ab-92d4-e708-825a61bfaf3e.png'>

## @&#65520;Publishedと@&#65520;EnvironmentObject
[Published | Apple Developer Documentation](https://developer.apple.com/documentation/combine/published)
[EnvironmentObject | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/environmentobject)

チュートリアルでは、`ObservableObject`を準拠しているクラスのプロパティに`@Published`を付けることで監視側がデータの変更を取得できるようにしている。
`@EnvironmentObject`をつけてプロパティを宣言することで、複数のViewに共通のインスタンスを渡して値を監視させられます。

下記コードでは親の`ContentView`内で`.environmentObject(User())`でインスタンスを渡して子ビューである`TextView`と`ButtonsView`で値を参照しています。

```swift
final class User: ObservableObject {
    @Published var name = "Taro"
    @Published var age = 18
}

struct ContentView: View {

    var body: some View {
        VStack {
            TextView()
            ButtonsView()
        }
        .environmentObject(User())
    }
}

struct TextView: View {
    @EnvironmentObject var user: User

    var body: some View {
        Text("I'm \(user.name).")
        Text("I'm \(user.age) years old.")
    }
}

struct ButtonsView: View {
    
    @EnvironmentObject var user: User

    var body: some View {
        HStack {
            Button(action: {
                self.user.age += 1
            }) {
                Text("歳をとる")
            }
            Button(action: {
                self.user.age -= 1
            }) {
                Text("若返る")
            }
        }
    }
}
```
<br>
実行したアプリでは、ボタンを押したときのユーザの年齢が動的に変更される。
<img width='300' alt='environmentObjectSample.gif' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550560/6519396d-3fbf-70ac-bd36-f5e700aec1da.gif'>

これで一応動くのですが、environmentObjectはアプリ全体で共通使用するデータのやりとりをするので、正しくはContentViewから`.environmentObject(User())`を削除し、代わりに`XXXApp.swift`内に下記のようにして記述するのが適切なようです。

そうでないと、**print文で確認すると更新はされているがアプリの見た目は変わらない**というような、正常に動作しないことがありました。


```swift:XXXApp.swift
struct SampleSwiftUIApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(User()) //ここで共通する
        }
    }
}
```

## @&#65520;StateObject
[StateObject | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/stateobject)

一度初期化すると、フレームワーク側で`@stateObject`がついたプロパティの値が保持され続けるのでビューの再描画が発生しても値が初期化されなくなります。
`@ObservedObject`を使用していると初期化されてしまうので、再描画されても変わってほしくない場合は`@stateObject`を使うと良さそうです。

チュートリアルを見る感じだと、こちらも初期化してそのままプロパティを保持して欲しいことを考えると起動直後に宣言して`.environmentObject`で渡すのが適しているという感じでしょうか。

```swift:XXXApp.swift
struct SampleSwiftUIApp: App {

    @StateObject var hogeData = HogeData() // 任意の保持され続けて欲しいデータ

    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(hogeData) // そのデータを渡す
        }
    }
}
```

## @&#65520;Binding

[Binding | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/binding)

データを格納するプロパティと、そのデータを変更/更新するビューを双方向に接続します。
ということは、`@Binding`で設定したプロパティが更新されれば、それと接続している他のビューも更新されるということになるようです。

```swift
final class ToggleState: ObservableObject {
    @Published var isOn = false
}

struct ContentView: View {
    @EnvironmentObject var toggle: ToggleState

    var body: some View {
        VStack {
            ToggleAView(isOn: $user.isOn)
            ToggleBView(isOn: $user.isOn)
        }
    }
}

struct ToggleAView:View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("スイッチA", isOn: $isOn)
    }
}

struct ToggleBView:View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("スイッチB", isOn: $isOn)
    }
}
```

上記のコードは同じプロパティをBindingしているので、片方のトグルを切り替えると、その状態がもう片方の状態にも同期されて切り替わるようになります。
<img width='300' alt='bindingSample.gif' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550560/c830a69c-2390-29db-241d-18c52c54333d.gif'>

## @&#65520;Environment
[Environment | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/environment)

`EnvironmentValues`に定義されているビューの環境の設定値を取得/更新したいときに使います。
下記コードは取得の例です。

```swift
struct ContentView: View {
    @Environment(\.timeZone) var timeZone
    @Environment(\.calendar) var calendar
    @Environment(\.locale) var locale

    var body: some View {
        VStack {
            Button(action: {
                print(timeZone) // Asia/Tokyo (current)
                print(calendar) // gregorian (current)
                print(locale)   //en (current)
            }) {
                Text("button")
            }
        }
    }
}
```

`EnvironmentValues`は上記のようにtimeZoneやcalendar等の値意外にも豊富に定義されています。
また、自分で`EnvironmentValues`の値を新たに定義することもできるそうです。

#おわりに
ひとまず、SwiftUI Tutorialsに登場するProperty Wrappersをそれぞれ調べてみて、それぞれがどういうものなのかはある程度把握できました。
他の記事を見る感じだと、今回紹介したものがやはりよく紹介されているのを見かけるので頻繁に活用していくのだと思われます。

これから実際にSwiftUIを使ったコーディングをしていくと思うので、これらを駆使しながら実装していきます。


Property Wrappersを複数種類組み合わせたときの挙動とかのケーススタディができていないのと、`@ObservedObject`や`@AppStorage`等の他のProperty Wrappersのまとめができていなかったりするので、こういうときどれを使うのが良いかがまだ不明な状態ですが、また色々調べながら進めて行けたらと思います。

# 参照　
1. [【Swift 5.1】Property Wrappersとは？ | 2速で歩くヒト](https://www.2nd-walker.com/2020/02/12/swift-property-wrappers/)
1. [SwiftUIの機能 @State, @ObservedObject, @EnvironmentObjectの違いとは](https://www.isoroot.jp/blog/2381/) 
1. [SwiftUIのProperty Wrappersとデータへのアクセス方法](https://qiita.com/shiz/items/6eaf87fa79499623306a)
1. [EnvironmentValuesを制するものはSwiftUIを制する](https://qiita.com/1amageek/items/59c6bb32a6627b4fb712)

※チュートリアルと各Property Wrappersのリンクは既に上記内で共有しているので省略させていただいています。