【SwiftUI】ObservableObjectを階層構造で使う

ObservableObjectを階層構造で扱う場合の注意点を解説します

スポンサーリンク

環境

この記事の情報は次のバージョンで動作確認しています。

【Xcode】12.5RC
【Swift】5.4
【iOS】14.5
【macOS】Big Sur バージョン 11.5.2
スポンサーリンク

階層構造にした時の問題点

次のようなモデルクラスで考えます。
ObservableObjectの配列を扱うため、階層構造のクラスになっています。

上記のクラスを使って画面を実装した例です。
リストの項目追加と、各項目をクリックした時のチェックのOn/OFFを実現します。

実行画面がこちらです。
アイテムの追加はうまくいくのですが、なぜか項目をクリックしてもチェックされません。
tapGestureイベントが発生して値は変わっているのですが、Viewは再描画されないのです。

実行画面1

Viewが再描画されない理由

項目のタップによってSubModelクラスの値は変更されますが、上位のModelクラスには変更が発生しません。
クラス配列の場合、要素の追加/削除・順序入れ替えなどが発生した場合の変更は感知しますが、要素クラスの中身の変更は感知できません。
この為、View側もモデルの変更を感知できず、再描画が発生しないのです。

スポンサーリンク

対処方法その1:強制再描画

サブクラスの値を修正すると同時に、上位のModelクラスに対して更新イベントを強制的に発生させる方法です。
下記コードのobjectWillChange.send()がそれにあたります。

再描画のタイミングをコントロールできるので、大変便利な仕組みですが、データとViewの同期を開発者が実装しなければいけない点は、SwiftUIの思想である、Single Source of Truth に則していないように思います。

スポンサーリンク

対処方法その2:Viewを分割する

データモデルの階層に合わせてViewを分割する方法です。
分割したSubView内で、SubModelに対しての@ObservedObject定義ができる為、Viewが変更を感知できるようになります。
View再描画の範囲を最小限に絞る意味でも、こちらの方が理にかなっているのではないでしょうか?

実行画面2

スポンサーリンク

あわせて読みたい記事

【SwiftUI】@ObservedObjectの使い方
(2020/11/28 更新) SwiftUIのデータバインディングの仕組みの一つで、データクラスの更新を監視する@ObservedObjectの使い方を解説します。 これを利用すると複数のViewから同一インスタンスへの同期が可能になりま...
【SwiftUI】データバインディング
SwiftUIのデータバインディングの仕組みである@State、@ObservedObject、@EnvironmentObject各々の特徴について解説します。