Hi everyone,
In the simple app below, I have a QueryView that has LazyVStack containing 100k TextField's that edit the item's content. The items are fetched with a @Query. On launch, the app will generate 100k items. Once created, when I press any of the TextField's , a severe hang happens, and every time I type a single character, it will cause another hang over and over again.
I looked at it in Instruments and it shows that the main thread is busy during the duration of the hang (2.31 seconds) updating QueryView. From the cause and effect graph, the update is caused by @Observable QueryController <Item>.(Bool).
-
Why does it take too long to recalculate the view, given that it's in a
LazyVStack? (In other words, why is the hang duration directly proportional to the number of items?) -
How to fix the performance of this app? I thought adding
LazyVStackwas all I need to handle the large dataset, but maybe I need to add a custom pagination with.fetchLimiton top of that? (I understand thatModelActorwould be an alternative to@Querybecause it will make the database operations happen outside of the main thread which will fix this problem, but with that I will lose the automatic fetching of@Query.)
Thank you for the help!
import SwiftData
import SwiftUI
@main
struct QueryPerformanceApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(for: [Item.self], inMemory: true)
}
}
}
@Model
final class Item {
var name: String
init(name: String) {
self.name = name
}
}
struct ItemDetail: View {
@Bindable var item: Item
var body: some View {
TextField("Name", text: $item.name)
}
}
struct QueryView: View {
@Query private var items: [Item]
var body: some View {
ScrollView {
LazyVStack {
ForEach(items) { item in
VStack {
ItemDetail(item: item)
}
}
}
}
}
}
struct ContentView: View {
let itemCount = 100_000
@Environment(\.modelContext) private var context
@State private var isLoading = true
var body: some View {
Group {
if isLoading {
VStack(spacing: 16) {
ProgressView()
Text("Generating \(itemCount) items...")
}
} else {
QueryView()
}
}
.task {
for i in 1...itemCount {
context.insert(Item(name: "Item \(i)"))
}
try? context.save()
isLoading = false
}
}
}