SwiftUIのListのRowにProgressViewを入れたときに1回目は表示されるのに2回目以降は表示されない不思議な現象にハマったので紹介します。
発生した現象
Listの一番下まで到達したら続きを読み込む処理のために以下のようにProgressView
(見やすくするために赤い枠線を付けています)を表示させようとしました。
動画を見ていただくとわかるのですが、スクロールして2回目にProgressView
が表示されるときには枠線は表示されていますがIndicatorが表示されていません。
このときのコードは以下になります。
struct ContentView: View { @State var items: [ItemType] = (0..<20).map { .item($0) } + [.loading] var body: some View { List(items) { item in switch item { case let .item(id): Text("ID: \(id)") case .loading: ProgressView() .frame(maxWidth: .infinity) .border(.red) // 見やすくするため .onAppear { Task { try await Task.sleep(nanoseconds: 10000000000) // load してるっぽくする let newElements:[ItemType] = (items.count-1..<items.count+20).map { .item($0) } items.insert(contentsOf: newElements, at: items.count - 1) } } } } } } enum ItemType: Identifiable { case item(Int) case loading var id: String { switch self { case let .item(id: id): return "\(id)" case .loading: return "loading" } } }
原因の推測と対策
原因ははっきりとしたことはわからないのですが、ProgressView
の中身がUIActivityIndicatorView
でデフォルトでhidesWhenStopped = true
になっていてListの機能で再利用されるときに非表示→表示となったときにIndicatorが非表示になったままになったのだとおもいます。
対策としてはProgressView
が再利用されなくなれば良いのでIdentifiable
の実装でUUID
のような毎回変化するIDを返してあげればこの問題を回避できます。
enum ItemType: Identifiable { case item(Int) case loading var id: String { switch self { case let .item(id: id): return "\(id)" case .loading: return "loading_\(UUID().uuidString)" // Viewが再利用されなくなるので表示される } } }
動作の様子はこんな感じです。
この対策が正しいのかはわかりませんが、ひとまず動きます。