はじめに
SwiftUIはクラスではなく構造体でViewを取り回します。クラスと同じ感覚でプロパティの値を好きなように書き換えることができません。
そのため、状態を保持する構造体をSwiftUIのフレームワークが用意してくれています。StateやBindingというやつですね。
この他にもObservableObjectプロトコルとかもあります。
いろいろ用意しているから性質や使い所をよく理解して使ってねというアップルの親切心…… ややこしさに涙が出てきます。
せっかくなので慣れる意味も込めて、StateやBindingを利用したデータのバケツリレーを行ってみます。パターン化したいのであんちょこ作りです。
本題
やりたいこと
登場人物は次の3者。
- 何かのアクションに対して何らかのデータを返してもらうクラス
- 親ビュー
- 子ビュー
子ビューにはButtonViewとTextViewを用意し、ButtonViewをタップしたら親ビューを経由してネットからデータを取得します。取得できたら親ビューを経由して子ビューのTextViewに表示します。
ステップ1:何かのアクションに対して何らかのデータを返してもらうクラスを作る
ObservableObject
プロトコルに準拠したクラスにします。
持ってきたデータを入れる変数を作ります。この変数には@Published
属性を付けておきます。データを持ってくる処理を作り、持ってきたデータをこの変数に代入するようにします。
@Published
属性をつけることでSwiftUIフレーム側に変数の変更を伝達することができます。
ステップ2:親ビューでObservableObjectプロトコルに準拠したクラスのインスタンスを作る
ObservableObject
プロトコルに準拠したクラスが持つ@Published
属性付き変数の変更を受け取ることができるように準備します。
といっても単純な話で、ObservableObject
プロトコルに準拠したクラスのインスタンス変数に@ObservedObject
を付けるだけです。
Observedですよ!スペルミス注意!(XCodeが補完してくれますが)
ステップ3:子ビューにデータを受け取るための変数を作る
子ビューはデータを受け取るだけの受け身な子です。ですので、データを受け取る変数には@Binding
構造体を付けておきます。
親ビューがデータの変更を受け取ってそれをそのまま子ビューに投げつけるイメージです。
ステップ4:子ビューのButtonViewタップ処理を親ビューに任せる
折返し地点に来ました。ここから実際に値を取得する処理を作っていきます。
子ビューのButtonViewタップ処理を親ビューに任せるのはお馴染みのDelegateでサクッと。
ステップ5:親ビューから子ビューへデータを渡す
おそらく赤い警告がステップ3からずっと出ていたかと思います。子ビューの@Binding
構造体を付けた変数が初期化されていないからですね。
親ビューのbody
メソッドで子ビューを生成するときに@Published
属性付き変数を渡してしまいましょう。
ここはイメージつきにくいと思いますので、概念コード例を載せておきます。
struct SampleView: View,ステップ4のDelegate {
@ObservedObject private var observedObj = ObservedObj()
var body: some View {
VStack {
// data:ステップ3で作った@Binding構造体付き変数
// fetchdata:`ObservableObject`プロトコルに準拠したクラスの`@Published`属性付き変数
ChildView(delegate: self,data: $observedObj.fetchdata)
}
}
}
代入先が@Binding
なので$
を付けます。これでBindingオブジェクトを渡すことができます。なお、$
を付けるのは@Published
属性付き変数ではなくクラスのインスタンスの方です。
ステップ6:親ビューにてデータを取得しに行く
Delegateでおまかせされた処理内にて、データを取得しに行きます。ステップ2にて何かのアクションに対して何らかのデータを返してもらうクラスのインスタンスを生成済みなので、そのインスタンスが持つデータ取得処理を叩きます。
まとめ
以上であんちょこは終わりです。子ビューのボタンをタップすると、子ビューまでデータが返ってくると思います。
ここでは親ビュー経由のパターンを示しました。子ビュー間での連動が必要な場合などはこのパターンを使う必要があると思います。
例えば、親ビュー上に子ビュー1(ButtonView)と子ビュー2(ImageView)があり、ボタンをタップしたら画像を取得し子ビュー2に表示する など。
子ビューが親ビューから完全に独立しているならば、親ビューを経由せずに直接データ取得クラスを叩いても良いかもしれません。
例えば、TabViewの各ページ内の処理など。