備忘録として書き残しておく。気が向いたら書き直すかもしれない。
SwiftUIのListで下図のような画面を実装している。
縦スクロールと横スクロールの混ざったリストは珍しくないが、この画面の厄介なところは横スクロールでも別途APIを叩いてデータを取得する必要がある。1画面1APIではなくて、縦スクロール用に1APIと横スクロール分x1API叩く必要がある。実装にめちゃくちゃ苦労しているが、APIの話はここではあまり関係ない。
実装としてはこんな感じだ。実際のコードとは全然違うのでイメージとして受け取ってください。
var body: some View { List { ForEach(vItems) { hItems in HorizontalScrollView(items: hItems) } } }
この画面は上から下方向へスワイプさせることで画面をrefreshさせる。ここではrefreshとは「オブジェクトは一旦削除して再度追加してListに表示させること」を指すことにする。
refreshしても画面の表示が変わらない。前回のデータが引き続き表示されてしまっているようだ。HorizontalScrollViewに触るなどするとViewの更新が掛かってAPIから取得した新しいデータが反映される。
Listが前回のデータをキャッシュしている疑いが濃厚だが、何を基準にしてキャッシュしているのかわからない。HorizontalScrollViewに渡しているObservableObjectの配列は以下の通り。最初は id
を基準にしているのかと考えた。
class ItemModel: ObservableObject, Identifiable, Equatable { // ... (省略) // MARK: - Identifiable var id: String { item_id } // MARK: - Equatable static func == (lhs: ItemModel, rhs: ItemModel) -> Bool { return lhs.id == rhs.id } }
なんで前回のデータが表示されているのかがわからなかったが「キャッシュされている」「おそらくid
で判定している」と当たりがついたので、SwiftUIのListのキャッシュについて調べていくと「ios - SwifUI ForEach List keeps modified values when reloading a @Published array inside ObservableObject - Stack Overflow」が出てきた。
アイテム数が同じだったらどうするのなど考えていないのであまり良い例ではないが Equatable
の処理を下記のように変更したところ、refreshした際にきちんと新しいデータが描画されるようになった。
class ItemModel: ObservableObject, Identifiable, Equatable { // ... (省略) // MARK: - Identifiable var id: String { item_id } // MARK: - Equatable static func == (lhs: ItemModel, rhs: ItemModel) -> Bool { return lhs.id == rhs.id && lhs.items.count == rhs.items.count } }
国居さんにも教えてもらった。
そうみたい
— 國居貴浩 (@reborn_xcc) 2021年12月2日
なのでForEachでviewに振るidとして
オブジェクト側のid+リビジョン番号
なんかの組み合わせを指定して、リビジョン番号を加算していくといいんじゃないかな
知らんけどhttps://t.co/bBtvFbqwjp