【SwiftUI】入れ子のObservableObjectで子の変更からViewを再描画させる

(宣伝)写真から3Dモデルを作成するiPhoneアプリを開発しました。よかったら使ってみてください。

DDDOG

DDDOG

  • Clarity Solutions LLC
  • 写真/ビデオ
  • 無料
apps.apple.com

(ここから本題)

SwiftUI の ObservableObject は @Published を指定したメンバ変数の更新を監視して、View を再描画させるために使います。

この記事では、入れ子になった ObservableObject を作り、子の更新を検知して親を使用している View を再描画させる方法を説明します。

元ネタは Stack Overflow。

stackoverflow.com

SwiftUI は入れ子になった ObservableObject の監視をサポートしていません。

どういうことかというと、たとえば以下のように ObservableObject が ObservableObject を持っているとします。

class SubModel: ObservableObject {
  @Published var count = 0
}

class ParentModel: ObservableObject {
  @Published var submodel: SubModel = SubModel()
}

ParentModel のインスタンス@ObservedObject@EnvironmentObject で使用している View があるとき、 submodel.count の変更があってもその View は再描画されません。

ただ、通常はこういうことはあまりやりません。

SubModel が ObservableObject ではなく通常の struct であれば、親は SubModel の変更を View に通知します。

これをするケースは、たとえば SubModel だけを使用する View があるときなどが想定できます。

さて、ParentModel を使用している View に submodel.count の変更を受けて再描画させるためには、多少のトリックが必要になりまう。

それが以下のようなコードです。(Stack Overflow の回答と少しコードを変えてあります)

class SubModel: ObservableObject {
    @Published var count = 0
}

class ParentModel: ObservableObject {
    var cancellable: AnyCancellable? = nil

    @Published var submodel: SubModel? {
        didSet {
            cancellable = submodel?.objectWillChange.sink { [weak self] (_) in
                self?.objectWillChange.send()
            }
        }
    }
}

これは要するに子の ObservableObject に何か変更があったら objectWillChange がイベントを送るのですが、それを親の ObservableObject の objectWillChange にリレーしているということです。

これで、submodel.count の変更があったら ParentModel を使用している View が再描画されるようになります。