Open8

SwiftUIの @State, @Binding, @Published, $, _ などのなんとなく使ってる文法をちゃんと理解する

まったまった

@Published

@PublishedはObservableObjectプロトコルに準拠したクラス内のプロパティを監視し、変化があった際にViewに対して通知を行う
@State に近い

class SignUpViewModel: ObservableObject {
    @Published var firstName: String = ""
    @Published var lastName: String = ""
}
struct LoginView: View {
    @StateObject var viewModel = SignUpVeiwModel()

    var body: some View {
        VStack {
            Text("フルネーム:\(viewModel.lastName) \(viewModel.firstName)") // 表示は $ 不要
            
            TextField("姓", text: $viewModel.lastName) // ここで変更も行うので先頭に $
            TextField("名", text: $viewModel.firstName) // ここで変更も行うので先頭に $
        }
    }
}

https://shuhey-hashimoto.com/swiftui/publishedってなに?/

@StateObject と @ObservedObject の違い

上記で @ObservedObject var viewModel と書くこともできるが、@ObservedObject だとライフサイクルとして、例えば親ビューの何かの値の更新による再描画で、自身も再描画された際に、@ObservedObject で定義した viewModel も破棄されてしまうらしい(つまり上記の例では firstName, lastName が親ビューの何かの値の更新の度に空文字に戻ってしまうらしい。

@ObservedObject というのは viewModel のようなものに対してでなく、データオブジェクト(データの入れ物の箱としてのオブジェクト?)に対して使うそうだが、いまいちユースケースがピンとこない。
https://qiita.com/0102_0102johnny/items/fba13b2e7cfa61d437e9

まったまった

@State と @Binding

データバインディング、すなわち異なるViewやViewModelで同じデータを扱う(バインドする)ための仕組みか。

struct ParentView: View {
    @State var isCheck: Bool = false

    var body: some View {
            Image(systemName: isCheck ? "bell.fill" : "bell.slash")
            ChildView(isCheck: $isCheck) // この時に $を付けて参照渡し
    }
}
struct ChildView: View {
    @Binding var isCheck: Bool // 親Viewのプロパティ参照を受け取る

    var body: some View {
        Button("画像切替") {
            isCheck.toggle() // 参照共有しているプロパティを変更する
        }
    }
}

https://blog.code-candy.com/swiftui_binding/

ただこの場合は親ビュー→子ビューという関係だけど、View と ViewModel という関係で @Binding を使っていいのか疑問。

まったまった

@State vs ただの var

@State を付ける付けないでどうかわるのか、var は変数の変更を受け付けるんだから @State 付けなくても動きそうだけど.. →動かないというかコンパイルエラーになる。

ポイント

  • View というのは struct である
  • struct では var で宣言してても変数を変更できない(なんでや..)
  • @State を付けると状態変数になるので、変更できるし、変更があると View が再描画される

まぁまさに React で言う useState() だけど、ViewModel (Class) で @State で宣言した変数はどういった挙動をするんだろう

https://qiita.com/REON/items/63bda579c53873abd628

まったまった

そもそもの @~ のような修飾子?のことを PropertyWrappers というらしい。
https://qiita.com/nkmrh/items/96f08b1915b85367e7ff

https://qiita.com/crea/items/0b59722ab21e8c6cbb30

https://zenn.dev/kalupas226/articles/a307608497651b

名前のとおりプロパティをラップして wrappedValueprojectedValue という2つの新たなプロパティへのアクセスを用意し、それらの getter, setter でごにょごにょしたい時に使えるらしい。
wrappedValue は(名前の通り?)通常は定義した値そのものを返すだけだが、何らかの処理を挟んで返すことも可能っぽい。

@State isPlaying: Bool

var body: some View {
    Text(isPlaying ? "再生中" : "停止中")
    PlayButtion(isPlaying: $isPlaying) // $isPlaying == isPlaying.projectedValue
}
まったまった

じゃあこの書き方は何なのか?

struct EditProfileView: View {
    @StateObject var viewModel: EditProfileViewModel
    
    init(user: User) {
        self._viewModel = StateObject(wrappedValue: EditProfileViewModel(user: user))
    }

これは _viewModel が、viewModel 宣言時に内部で暗黙的に宣言された StateObject<EditprofileViewModel> 型の値であるから、それに直接代入している、ということ。ここで _viewModel.wrappedValue == viewModel か?

下記を参考にすると少し理解が深まる気がする

次の1行の宣言文は

@State var like = true

Swiftコンパイラ内部で次のような宣言に変換されてコンパイルされます。

private var _like: State<Bool> = State(wrappedValue: true)
var like: Bool {
    get {
        return _like.wrappedValue
    }
    set {
        _like.wrappedValue = newValue
    }
}
var $like: Binding<Bool> {
    get {
        return _like.projectedValue
    }
}

https://ja.stackoverflow.com/questions/62443/swiftuiのtoggleのisonに指定するbool値になぜドルマークを付けなければいけないのかがわからない

まったまった

@State vs @StateObject

https://medium.com/@dheerajrathore3002/state-vs-stateobject-c1effdbe5c74

基本的に @State は Bool, String, Int などのシンプルな変数の状態管理に使うが、 @StateObject は ObservableObject の変数宣言時に使う。

じゃあ ObservableObject って何かって言うと、全てのプロパティが State のようなオブジェクト?、、、なのか、、?
ただ ObservableObject のプロパティは基本 @Published で宣言するっぽく、これらのプロパティに変更があった時に View は再描画を行う。

まったまった

じゃあ無理やりこうかくとどうなるのか?

struct EditProfileView: View {
    @Environment(\.dismiss) var dismiss
    @State var viewModel: EditProfileViewModel
    
    init(user: User) {
        self.viewModel = EditProfileViewModel(user: user)
    }

@StateObject で宣言すると get-only property やでと怒られるが、@State で宣言すれば文法エラーにはならない。このとき user に変更があったときどうなるのか?

  • 親から渡されてきた user に変更があったとき
  • viewModel 内で @Publish されている user を変更したとき