🔬

SwiftUIでView間受け渡しの @State @Binding @StateObject @ObservedObject 組合せ観測隊

2023/05/11に公開

SwiftUIでViewからViewにインスタンスを渡すときに @State @Binding @StateObject @ObservedObject を付けるが、これらをいろいろ組合せたときの挙動を調べた。

いくつか役に立つかどうかわからない裏技が得られた。興味がある人がいるかもしれないので載せておく。

やること

インスタンスを生成し、

  • 生成View
  • 受け取りView

の間で受け渡し、その挙動を観測する。受け渡すものを structclass の両方で調べる。

受け渡すインスタンスが struct or class
生成Viewでは @State or @StateObject or なし
受け取りViewでは @Binding or @ObservedObject
計12パターンを調べていく。もちろん 2*3*2 である。
意味のない組み合わせもあると思うが、そんなことは気にしないでやる。

構造

  1. 受け渡すデータを structclass で定義する
  2. 渡すViewと受け取るViewを定義する
  3. 渡すViewでインスタンスを生成し、相手Viewに渡す

受け渡すデータのソース

struct StructA {
    var value = 0
}

class ClassA: ObservableObject {
    @Published var value = 0
}

渡すViewのソース

次のソースをいろいろ切り替えて実験を行う。

struct ContentView: View {
    @State var structA = StructA()
    @StateObject var classA = ClassA()
    //@State
    //@StateObject
    
    var body: some View {
        VStack(alignment: .leading) {
            
            //このViewで取得して表示する部分
            Text("structA in ContentView \(structA.value)")
            Text("classA in ContentView \(classA.value)")
            
            //相手Viewでに渡して相手Viewを表示する部分
            //$は付けたり消したりする
            SubStructView(a: $structA)
            SubClassView(a: classA)
            
            Button("add") { 
                structA.value += 1
                classA.value += 1
            }
            .padding()
        }
    }
}

受け取り側Viewのソース

変数の宣言部で @Binding@ObservedObject を切り替えて実験する。

struct SubStructView: View {
    
    @Binding var a: StructA
    //@Binding
    //@ObservedObject
    
    var body: some View {
        Text("structA in SubStructView \(a.value)")
    }
}

struct SubClassView: View {
    
    @ObservedObject var a: ClassA
    //@Binding
    //@ObservedObject
    
    var body: some View {
        Text("classA in SubClassView \(a.value)")
    }
}

結果

struct を受け渡し

生成 受け取り 結果 $ 挙動/原因
@State @Binding OK いる
@State @ObservedObject エラー structに@ObservedObjectがつけられない
@StateObject @Binding エラー structに@StateObjectがつけられない
@StateObject @ObservedObject エラー structに@StateObject / @ObservedObjectがつけられない
なし @Binding エラー 渡すところでエラー
なし @ObservedObject エラー structに@ObservedObjectがつけられない

class を受け渡し

生成 受け取り 結果 $ 挙動/原因
@State @Binding エラーは出ず いる 値が反応しない
@State @ObservedObject エラーは出ず いらない 生成Viewでは反応しない/受け取りVIewは反応する
@StateObject @Binding エラー 渡すところでエラー/何か裏技があるかも
@StateObject @ObservedObject OK いらない
なし @Binding エラー 渡すところでエラー/何か裏技があるかも
なし @ObservedObject エラーは出ず いらない 生成Viewでは反応しない/受け取りVIewは反応する

まとめ

王道は

  • struct には @State @Binding の組合せ
  • class には @StateObject @ObservedObject の組合せ

である。

裏技として

  • class を、 @State @Binding の組合せで受け渡しを行い、インスタンスが入れ替わったときに両方反応させ、中身が変わったときに反応させない
  • class を、 @State @ObservedObject の組合せで受け渡しを行い、インスタンスが入れ替わったときに両方反応させ、中身が変わったときに @ObservedObject の方だけ反応させる

が(使い所があれば)使える。

ちなみにインスタンス入れ替え部分のソースは以下である。

Button("change") { 
    let temp = ClassA()
    temp.value = Int.random(in: 1...100)
    classA = temp
}

続編

https://zenn.dev/samekard_dev/articles/ede0cfbcec3f12

Discussion