酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

備忘録:SwiftUIのListでrefreshした時に前回のデータが表示されている。画面が再描画されると新しいデータが反映される

備忘録として書き残しておく。気が向いたら書き直すかもしれない。


SwiftUIのListで下図のような画面を実装している。

縦スクロールと横スクロールの混ざったリストは珍しくないが、この画面の厄介なところは横スクロールでも別途APIを叩いてデータを取得する必要がある。1画面1APIではなくて、縦スクロール用に1APIと横スクロール分x1API叩く必要がある。実装にめちゃくちゃ苦労しているが、APIの話はここではあまり関係ない。

f:id:ch3cooh393:20211203091355p:plain

実装としてはこんな感じだ。実際のコードとは全然違うのでイメージとして受け取ってください。

    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
    }
}

国居さんにも教えてもらった。