0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

はじめに

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.textviewModel.$textを両方使うサンプルコードを書いてみました。

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

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にします

(参考)

func onReceive<P>(
    _ publisher: P,
    perform action: @escaping (P.Output) -> Void
) -> some View where P : Publisher, P.Failure == Never

つまり

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

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

ということでした

おまけ

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

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の形で使うかもなと思いましたので、別の機会に調べてみようと思います!

参考

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?