SwiftUIのProperty Wrappersまとめ


## Property Wrappers
SwiftUIのpropertyWrapperの前に、そもそもpropertyWrapperとは何かを説明させてください。
変数をラッピングして、アクセス（get/set）を制御するための仕組みです。
簡単な例を見てみましょう。

```swift
@propertyWrapper
struct HelloWorld {
    private var text: String
    init() {
        text = ""
    }
    var wrappedValue: String {
        get { return text }
        set {
            if newValue == "世界" {
                text = "こんにちは, \(newValue)!"
                return
            }
            text = "Hello, \(newValue)!"
        }
    }
}
```

クラスや構造体、enumに`@peopertyWrapper`をつけることで定義できます。
定義したら、それを以下のように変数に適用することができます。

```swift
@HelloWorld var name: String
```

`name`に何らかの値をセットすると、"Hello, \(name)!"という値に変換してセットされるようになります。日本語で"世界"とセットしたときだけ、"こんにちは, 世界!"となるようにしてみました。
### wrappedValue
propertyWrapperはラッピングした値を返す変数である`wrappedValue`を実装する必要があります。この値のget/setで、ラッピングした値を変更したりします。
![SwiftUI PropertyWrappers1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247791/a8d2c379-a95c-ad29-b9a6-5514e03629c1.png)
### projectedValue
追加でprojectedValueという変数を実装することもできます。ラッピングした値の状態を示したり、`wrappedValue`に関するなんらかの情報を返すことができます。外からこの値にアクセスするには`$`を頭につけなければいけません。例えば、

```swift

@propertyWrapper
struct HelloWorld {
    var projectedValue: Bool
    init() {
        projectedValue = false
    }
    var wrappedValue {
        // ... get ..
        set {
            if newValue == "世界" {
                text = "こんにちは, \(newValue)!"
                projectedValue = true
                return
            }
            text = "Hello, \(newValue)!"
            projectedValue = false
        }
    }
}
```

とすると、`$name`で、Bool値が返ります。

```swift
class Hello {

    @HelloWorld(text: "Hello, World!") var str: String
    
    func sayHello() {
        print(str)        // Hello, World!
        print("\($str)")  // false
        str = "世界"
        print(str)        // こんにちは, 世界!
        print("\($str)")  // true
    }
}
```
「世界」という値をセットしたときだけ、`projectedValue`が`true`になるようにしています。
このように`projectedValue`を使って`wrappedValue`の追加情報(投影値?)を返すことができます。
## SwiftUI の propertyWrapper
以降はSwiftUIで定義されたpropertyWrapperについてみていこうと思います。
### State
SwiftUIのviewは構造体（値）のため、変更不可です。ですが、変数に`@State`をつけると、その変数に変更が入ったら自動でviewを再構築してくれます。なのでviewで変更する必要があるものは`@State`をつけます。

```swift
struct GreetingView: View {
    @State var message: String = ""
    
    var body: some View {
        VStack {
            Text(message)
        }
    }
}
```

`State`は`projecteValue`として、後述する`Binding`を返します。上記の例だと、 `$message` で、`String`を`Binding`でラップした値を返してくれます。
![SwiftUI PropertyWrappers2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247791/77d854b8-4dd9-9b8f-9d8b-765233fff4f5.png)
### Binding
双方向データバインディングというやつです。`Binding`の値を変更すると、その情報源（変数）に、値の変更を反映してくれます。逆に情報源を更新すれば、`Binding`から取得できる値も更新されています。

`State`の`projectedValue`（$）は、自身の値と結びつく`Binding`を返してくれるので、自身の値を更新してほしいときに渡します。

```swift
struct GreetingView: View {
    @State var message: String = "ボタンは押さないでね★"
    
    var body: some View {
        VStack {
            Text(message)
            // Bindingする例
            GreetingButton(message: $message)
            // Bindingしない例
            StateGreetingButton(message: message)
        }
    }
}

struct GreetingButton: View {
    @Binding var message: String
    
    var body: some View {
        Button(action: {
            self.message = "ボタン...押しましたね..."
        }) {
            Text("絶対に押すな！")
        }
    }
}

struct StateGreetingButton: View {
    @State var messageState: String
    
    var body: some View {
        Button(action: {
            self.messageState = "Bindingではないです"
        }) {
            Text("押してもいいですよ")
        }
    }
}
```

`GreetingButton`で、`Binding`である`message`を更新すると、情報源である`GreetingView`の`message`に変更が反映されます。
![SwiftUI PropertyWrappers3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247791/c34caeaf-72e7-c92b-8db6-15cc58b19397.png)
しかし、`StateGreetingButton`のボタンを押して、`messageState`を変更しても、情報源である`GreetingView.message`には反映されません。

もう一つの例をあげると、`Toggle`の引数`isOn`に`Binding`を渡しますよね。これは、`Toggle`自身が`isOn`に渡した`Binding`の情報源を変更するためです。

```swift
@State var onOff: Bool = true

var body: some View {
    Toggle(isOn: $onOff) {
        Text("情報源(var onOff)の値を変えますよ: \(onOff.description)")
    }
}
```
### ObservedObject

`ObservedObject`は`ObservableObject`を実装したクラスを`wrappedValue`として持ちます。

```swift
struct PlayerViewView: View {
    
    @ObservedObject var user: PlayerViewModel = PlayerViewModel()
    
    var body: some View {
        VStack {
            
            Text("あなたは \(user.age) 歳です")
            
            Button(action: {
                self.user.haveBirthday()
            }) {
                Text("何歳ですか？")
            }
        }
    }
}

final class PlayerViewModel: ObservableObject {
    private(set) var age: Int = 0 {
        willSet {
            objectWillChange.send()
        }
    }

    func haveBirthday() {
        age += 1
    }
}
```
上記の例だと、ボタンを押すたびに、`PlayerView`へ`PlayerViewModel`の変更が通知され、viewが更新されます。
データフローは`@State`と変わらないのですが、`@ObservedObject`の場合は、`wrappedValue`が`ObservableObject`のオブジェクトでないといけない、という点が異なります。
#### ObservableObject
`ObservableObject`は`Combine`フレームワークに含まれるプロトコルです。オブジェクトの変更を通知できることを表します。`objectWillChange`というPublisher（変更イベントの発行元）を持っており、これを購読していれば、`ObservableObject`の変更を検知できます。
`ObservableObject`が自身に変更が入った、ということを通知するには、以下のように`objectWillChange.send()`を実行します。

```swift
class SomeObservableObject: ObservableObject {
    var hello: String {
        willSet {
            // ObservableObjectに適合するクラスは objectWillChangeを持っている
            objectWillChange.send()
        }
    }    
}
```

以下のように購読すれば、変更が入ったことを検知できます。

```swift
class Hoge {
    var cancellable: AnyCancellable?
    init() {
        cancellable = someObservalbeObject.objectWillChange.sink { _ in
            // objectWillChange.send() が呼ばれた！
        }
    }
}
```

`@ObservedObject`のpropertyWrapperを使うと、SwiftUIが`ObservableObject`を購読し、変更が入るたびにviewを更新してくれます。
![SwiftUI PropertyWrappers4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247791/acb58a32-8710-5e3d-036b-f8bbde7bdf31.png)
### Published
SwiftUIではなく、Combineで定義されたpropertyWrapperですが、`ObservableObject`でよく使われるので説明します。
変数に`@Published`を付与すると、その変数の`projectedValue`は自身の変更を通知する `Publisher` を返してくれます。

```swift
class Dog {
    @Published var name: String = "Pero"
    private var cancellable: AnyCancellable?

    init() {
        self.cancellable = $name.sink { newName in
            print("あなたの名前: \(newName)")
        }
    }
}

let dog = Dog()   // あなたの名前: Pero
dog.name = "John" // あなたの名前: John
dog.name = "Taro" // あなたの名前: Taro
```

`sink` で購読し、値の変更を監視しています。変更が入るたびにログ出力しています。

`ObservableObject`というプロトコルに適合したクラスで、`@Published`の変数を持っている場合、それに対して変更すると、`ObservableObject`の変更を自動で通知してくれます。

```swift
class Dog: ObservableObject {
    @Published var name: String = "Pero"
    private var cancellable: AnyCancellable?

    init() {
        self.cancellable = objectWillChange.sink {
            print("名前、変わりましたね...")
        }
    }
}

let dog = Dog()
dog.name = "John" // 名前、変わりましたね...
```

なので、`@Published`を使えば、自前で`willSet`にて`objectWillChange.send()`なんて書かずによいので楽です。
### EnvironmentObject
`ObservableObject`を子ビューでも使いまわしたい場合は、`@ObservedObject`ではなく、`@EnvironmentObject`を使用します。

```swift
struct ContentView: View {
    
    @EnvironmentObject var setting: PlayerSettings
    
    var body: some View {
        VStack {
            Text("あなたの通知設定: \(setting.isNotificationEnable.description)")
            SubView()
        }
    }
}

struct SubView: View {
    
    @EnvironmentObject var setting: PlayerSettings
    
    var body: some View {
        VStack {
            Button(action: {
                self.setting.isNotificationEnable.toggle()
            }) {
                Text("通知設定を変更")
            }
        }
    }
}

/// View間で共有するオブジェクト
final class PlayerSettings: ObservableObject {
    
    @Published var isNotificationEnable: Bool = false
}
```

上記の例だと、`SubView`の`setting`は親View(`ContentView`)の`setting`が自動でインジェクトされます。なので、子View側で、変更したら、親Viewにも反映されます。

ただし、最初に`@EnvironmentObject`を使用しているViewの生成時に、オブジェクトを生成して渡す必要があります。渡さないとクラッシュします。

```swift
let contentView = ContentView().environmentObject(PlayerSettings())
```

もう一つ注意する点は、`sheet`などで表示した親子関係でない別のViewには自動でインジェクトされないということです。共有したい場合は、明示的に渡す必要があります。

```swift
struct ContentView: View {
    
    @EnvironmentObject var setting: PlayerSettings
    
    @State var showModal: Bool = false
    
    var body: some View {
        VStack {
            
            Text("あなたの通知設定: \(setting.isNotificationEnable.description)")

            Button(action: {
                self.showModal = true
            }) {
                Text("べつのがめん")
            }.sheet(isPresented: $showModal) {
                // 親子関係でないViewでも共有したい場合は、明示的に渡す必要がある
                SubView().environmentObject(self.setting)
            }
        }
    }
}
```
### Environment
SwiftUIで定義されたViewの設定値を取得することができます。

```swift
struct SubView: View {
    @Environment(\.isEnabled) var enable: Bool
    
    var body: some View {
        VStack {
            Text("活性状態? \(enable.description)")
        }
    }
}
```

例えば上記画面の表示時に、以下のように`disabled(true)`とすれば、`@Environment(\.isEnabled)`は`false`になります。

```swift
SubView().disabled(true)
```