Open3

SwiftUI的ビューとは?

kabeyakabeya

SwiftUI的ビューとInterface Builder的ビュー

SwiftUI標準のNavigationSplitViewにしても、以下の自作のInspectableViewにしても、ツールバーがあると勝手にサイドバーの表示非表示をトグル的に切り替える操作を行うボタンを付けてしまうという振る舞いをします。

https://zenn.dev/kabeya/articles/c382ac7f48727f

ビュー自身がサイドバーのスプリッタをドラッグして非表示にすることができてしまい、かつ表示を戻す方法がないため、ボタンやメニューなどビューの外から、サイドバーの表示非表示の切り替えられるようにする必要があります。

ビューが「サイドバーの表示非表示のトグルをさせる」という外部インターフェイスを持っていれば済みそうな話ですが、SwiftUI標準のNavigationSplitViewは、サイドバーの表示非表示を切り替える、というようなメソッドは持っていません。

SwiftUIは「ビューの状態を表す変数群、というようなものがあって、それをビューが表現する」というような考え方ですね。
Interface Builder的なビューであれば、IBActionで定義されたビューの機能を用いて、ビューをコントローラが操作するという感じですが、SwiftUI的ビューだと、サイドバーが表示されているか表示されていないかは、ビューの外側にある変数が管理して、それをビューは反映する、という感じになります(という理解。違ってたらごめんなさい)。
Interface BuilderはMVCという感じで、SwiftUI=MVVM、VMの部分が「ビューの状態を表す変数群」ということなのかなと思います。

で、分かったようなつもりになっていざ実装するとちょっと問題にぶつかりました。

外からは表示非表示しかコントロールできない(させない)ので、ビューの@Binding変数は、サイドバーがvisibleかどうかだけ、幅はビュー自身が管理できればOK(@State変数で管理)、という感じで考えていました。

で、サイドバーをドラッグして小さくした結果、(ビュー自身が判断して)非表示にしたとき、visibleかどうかを切り替えたうえ、幅を0にします。で次に外部からvisibleかどうかの変数を書き換えると、再描画、つまりvar bodyが呼び出されます。その中で幅0だと困るので最小幅まで戻そうとしますが、そうするとvar bodyの中で幅の変数(=@State)を書き換えてしまうことになるため「挙動が未定義です」と言われます。

とは言え幅の変数を@Bindingにしても、var bodyの中で書き換えたら同じことで、外でvisibleと幅の変数の整合性を取るしかない気がします。

ビューの操作として、ドラッグして最小幅までいったときに非表示にするとか幅=0にするとかいうのは、ビュー側の話であって、外の人には関係ない話のはずで、非表示→表示に戻したとき幅を0→最小幅に戻してやる必要があるなんてのを外の人が知る必要はないと思います。

@Bindingの変数が変わる(or 変わった?)タイミングから、var bodyが呼ばれるタイミングの間のどこかで、こうした@のついた変数間のオーケストレーションというかコンダクティングというか、調和処理的なものを行う仕掛けが要るんじゃないかと思っています。

didSetとかwillSetとかって、前はあったけど今はないとかいう話を読むこともあります。どうなってるのかなと思います。

kabeyakabeya

この問題に関しては、なんとなく「非表示にする際に幅0にする」のをやめれば解決するような気もしますね。
すべての「複数の内部変数が1つの外部変数によって連動して変わるようなケースで、var bodyで連動させようとすると怒られる問題」がそんな感じなのかが気になるところです。

もうひとつの懸念である、NavigationSplitViewを使ってるとツールバーがあったときに、勝手にツールバーにボタンを追加する問題、に関しては、もう少し問題点を整理する必要がありそうです。

kabeyakabeya

@Bindingの変数が変わる(or 変わった?)タイミングから、var bodyが呼ばれるタイミングの間のどこかで、こうした@のついた変数間のオーケストレーションというかコンダクティングというか、調和処理的なものを行う仕掛けが要るんじゃないかと思っています。

これに関して、以下の機能の実装で行った「var body中で、DispatchQueue.main.asyncを使って@State変数を更新する」という手法が使えるような気がします。

https://zenn.dev/kabeya/articles/6ef8bf14a31df9

別途検証します。