https://developer.apple.com/tutorials/swiftui/handling-user-input
このチュートリアルの最後の場面で、LandmarkDetail画面で、お気に入りのスターを表示する処理と、タップによってスターをオン/オフする処理が実装される。
この画面の設計が気持ち悪い。
まず、LandmarkDetailはinitでlandmark: Landmark
を受けてvarに保存している。
それを、self.landmark.name
などの表示に使っている。
一方、LandmarkDetailは@EnvironmentObject
でuserData: UserData
も暗黙に受け取っている。
そして、self.landmark.id
を使ってself.userData.landmarks
からインデックスを引いてきて、self.userData.landmarks[index]
として、Landmark
型を取り出す処理がある。
こうやって取り出した方のLandmark
は、isFavorite
の方の表示に使っている。
このように、引数で受けたLandmark
を静的な値の表示、UserData
の中のLandmark
を動的な値の表示にと使い分けている。この作りは、値が静的か動的かどうかという必要のない関心をコードに生じさせてしまっていると思う。
また、LandmarkDetail
は一つのランドマークを表示するための画面なのに、UserData
型固有のデータ構造に依存したコードになってしまっていて、これも必要のない関心が埋め込まれていると思う。
この画面は本来、@Binding var landmark: Landmark
などとやって、表示するLandmark
一つだけに依存させるべきで、画面表示側で、必要であればこれをUserData.landmarks
と結合させる、といった構造になるべきだと思う。
ただ現状、SwiftUIにはlandmarks: [Landmark]
からある一要素に対するBinding<Landmark>
を作る標準の道具が無いような気がする?
Binding型で受け取ると大本があることが暗喩されちゃうということ?
インターフェースとしては結局普通の型と同じでリード/ライトできるだけだけれど。
あと、通信で取得した場合でも、普通に値を保持しつつBindingに包む事自体はできるはず。
でも確かに微妙な気もする。
確かに明示的に値で持っておいて、必要に応じて変更通知を送信したり、それを受信して読み込んだりするほうが、
インターフェース的に自然な気はする。
ただIdentifiableをグローバルで考えちゃうと、環境を切ったり、小さい範囲で使いたい場合に面倒になるから、
殆どの場合にグローバルと同一であったとしても、なんらかのコンテキストオブジェクトは指定するようにしたほうが良いと思う。
例えばサブミット前の編集作業中の状態を表すオブジェクトと、それをPOSTして確定するまでのサーバ状態を知識として持っておくオブジェクトは、同じIDを持つけど状態としては独立させておきたい。
SwiftUI的にはそのようなコンテキストに当たるのがEnvironmentObjectだと思う。
そうすると現状のUserDataはまあまあ惜しくて、
landmarksでアクセスするんではなくて、
idを渡すgetter/setterと、idを渡してPublisherを返すAPIを追加してやるのがいいのかな。
そのへんはボイラープレート化しそうだから標準的な道具がやっぱり欲しいですね。