[SwiftUI] $viewModel.text とviewModel.$textの違いについて

# はじめに
SwiftUIのTextFieldにViewModelの値を渡す際に`Cannot convert value of type 'Published<String>.Publisher' to expected argument type 'Binding<String>'`とエラーが出ました。

つまり、View側では`Binding<String>`の値を期待していたところ、`Published<String>.Publisher`だったよということで

誤って`viewModel.$text`としていたところを`$viewModel.text`にすれば簡単に解決するのですが

気になったので`viewModel.$text`はどのような場合に使う可能性があるのか調べてみました。

# 環境
Xcode 14.3

# 内容

まずは、`$viewModel.text` と`viewModel.$text`を両方使うサンプルコードを書いてみました。

サンプルコードでは、TextFieldで文字を入力するとViewModelのtextが更新され、その値を`.onReceive`して別のTextとして表示しています
（実際には`Text(viewModel.text)`とすればそのまま値を表示できるのであえてのサンプルです）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631585/625e4b15-60ea-6a8e-56f6-af1108e2e277.png" width="200">


```swift
struct ContentView: View {
    @ObservedObject var viewModel = ContentViewModel()
    @State var onReceiveText: String = ""

    var body: some View {
        VStack {
            TextField("", text: $viewModel.text)
                .padding(10)
                .overlay(
                    RoundedRectangle(cornerRadius: 10)
                        .stroke(Color.gray, lineWidth: 1)
                )
            Text(onReceiveText)
                .padding(10)
                .overlay(
                    RoundedRectangle(cornerRadius: 10)
                        .stroke(Color.gray, lineWidth: 1)
                )
        }
        .padding(10)
        .onReceive(viewModel.$text) { text in
            onReceiveText = text
        }
    }
}

class ContentViewModel: ObservableObject {
    @Published var text: String = "TextField"
}
```

TextFieldに渡すViewModelの値は`Binding<String>`なので、`$viewModel.text`になります。

ViewModelのtextの値を`.onReceive`する際に必要な値は`Publisher`なので`viewModel.$text`にします

（参考）
```swift
func onReceive<P>(
    _ publisher: P,
    perform action: @escaping (P.Output) -> Void
) -> some View where P : Publisher, P.Failure == Never
```
https://developer.apple.com/documentation/swiftui/view/onreceive(_:perform:)

### つまり

- `$viewModel.text`はviewModelのtextプロパティへのバインディングを表し、値の読み取りと書き込みの両方ができ、値が変更されるとビューが再描画される

- `viewModel.$text`はviewModelのtextプロパティの変更を監視し、その変更を購読できる`Publisher`が提供される

ということでした


#### おまけ
`.onReceive`で値を受け取る必要があるように、サンプルコードに少し修正を加えます

```diff_swift
struct ContentView: View {
    @ObservedObject var viewModel = ContentViewModel()
    @State var onReceiveText: String = ""

    var body: some View {
        VStack {
            TextField("", text: $viewModel.text)
                .padding(10)
                .overlay(
                    RoundedRectangle(cornerRadius: 10)
                        .stroke(Color.gray, lineWidth: 1)
                )
            Text(onReceiveText)
                .padding(10)
                .overlay(
                    RoundedRectangle(cornerRadius: 10)
                        .stroke(Color.gray, lineWidth: 1)
                )
        }
        .padding(10)
-       .onReceive(viewModel.$text) { text in
+       .onReceive(viewModel.textPublisher) { text in
            onReceiveText = text
        }
    }
}

class ContentViewModel: ObservableObject {
    @Published var text: String = "TextField"

+   var textPublisher: AnyPublisher<String, Never> {
+       $text
+           .debounce(for: 1, scheduler: RunLoop.main)
+           .removeDuplicates()
+           .eraseToAnyPublisher()
+   }
}
```

TextFieldの変更を1秒間ごとの変化でText表示する形にしてみました

この形であれば実装する可能性もあるかなと考えましたが、`@Published`ではない値には$マーク不要になるためやはり`viewModel.$text`を使うケースはなかなかないのかな

# おわりに
使い分けは理解できましたが、なかなか`viewModel.$text`をView側で使うことはなさそうだと思いました。

今回は書いていませんが、`ObservableObject`の中で`ObservableObject`を使う場合に、`子ObservableObject`からPublisherとして値を受け取るケースなどでは`viewModel.$text`の形で使うかもなと思いましたので、別の機会に調べてみようと思います！

# 参考
https://stackoverflow.com/questions/63282377/cannot-convert-value-of-type-publishedbool-publisher-to-expected-argument-ty