Slow rendering List backed by SwiftData @Query

Hello, I've a question about performance when trying to render lots of items coming from SwiftData via a @Query on a SwiftUI List. Here's my setup:

// Item.swift:

@Model final class Item: Identifiable {
  var timestamp: Date
  var isOptionA: Bool
  init() {
    self.timestamp = Date()
    self.isOptionA = Bool.random()
  }
}

// Menu.swift

enum Menu: String, CaseIterable, Hashable, Identifiable {
  var id: String { rawValue }
  case optionA
  case optionB
  case all
  var predicate: Predicate<Item> {
    switch self {
    case .optionA: return #Predicate { $0.isOptionA }
    case .optionB: return #Predicate { !$0.isOptionA }
    case .all: return #Predicate { _ in true }
    }
  }
}

// SlowData.swift

@main
struct SlowDataApp: App {
  var sharedModelContainer: ModelContainer = {
    let schema = Schema([Item.self])
    let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
    return try! ModelContainer(for: schema, configurations: [modelConfiguration])
  }()
  
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
    .modelContainer(sharedModelContainer)
  }
}

// ContentView.swift

struct ContentView: View {
  @Environment(\.modelContext) private var modelContext
  @State var selection: Menu? = .optionA
  var body: some View {
    NavigationSplitView {
      List(Menu.allCases, selection: $selection) { menu in
        Text(menu.rawValue).tag(menu)
      }
    } detail: {
      DemoListView(selectedMenu: $selection)
    }.onAppear {
      // Do this just once
//      (0..<15_000).forEach { index in
//        let item = Item()
//        modelContext.insert(item)
//      }
    }
  }
}

// DemoListView.swift

struct DemoListView: View {
  @Binding var selectedMenu: Menu?
  @Query private var items: [Item]
  init(selectedMenu: Binding<Menu?>) {
    self._selectedMenu = selectedMenu
    self._items = Query(filter: selectedMenu.wrappedValue?.predicate,
                        sort: \.timestamp)
  }
  var body: some View {
    // Option 1: touching `items` = slow!
    List(items) { item in
      Text(item.timestamp.description)
    }
    
    // Option 2: Not touching `items` = fast!
//    List {
//      Text("Not accessing `items` here")
//    }
    .navigationTitle(selectedMenu?.rawValue ?? "N/A")
  }
}

When I use Option 1 on DemoListView, there's a noticeable delay on the navigation. If I use Option 2, there's none. This happens both on Debug builds and Release builds, just FYI because on Xcode 16 Debug builds seem to be slower than expected: https://indieweb.social/@curtclifton/113273571392595819

I've profiled it and the SwiftData fetches seem blazing fast, the Hang occurs when accessing the items property from the List. Is there anything I'm overlooking or it's just as fast as it can be right now?

After more digging, it looks like List is doing the correct thing, but @Query seems to be loading all data up-front.

I've enabled the SQL debug logging and I see the following when I tap on the All menu item:

CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZISOPTIONA FROM ZITEM t0 ORDER BY t0.Z_PK
CoreData: annotation: sql connection fetch time: 0.0025s
CoreData: annotation: fetch using NSSQLiteStatement <0x60000622dd60> on entity 'Item' with sql text 'SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZISOPTIONA FROM ZITEM t0 ORDER BY t0.Z_PK' returned 15000 rows

But then if I add a print statement on the init of what would be the row view of each List item, it only calls the init for as many times as rows fit on-screen.

So, my final question is: If the fetch to the database is pretty much instant, and List only loads the cells that are visible on-screen, why there's such a massive hang? If instead of using @Query I simply pass 15_000 Items, there's no hang. But the fetch seems extremely fast, so I'm really confused at where's the problem.

Gist with the code: https://gist.github.com/xmollv/e682ad6c22ac0bfd4b410e9fdb6db5c4

"So, my final question is: If the fetch to the database is pretty much instant, and List only loads the cells that are visible on-screen, why there's such a massive hang? "

The time shown as "sql connection fetch time" in the SQLDebug log is the time Core Data uses to run the SQL statement, which is quite different from the time SwiftData uses to retrieve the data from the data store to the item array.

To show the difference, I tweaked your code in the following way:

struct DemoListView: View {
    @Binding var selectedMenu: Menu?
    @Query private var items: [Item]
    
    var itemsWithLog: [Item] {
        let startTime = Date.now
        let tempItems = items
        print("Time taken to fetch: \(Date.now.timeIntervalSince(startTime))")
        return tempItems
    }

    init(selectedMenu: Binding<Menu?>) {
        self._selectedMenu = selectedMenu
        self._items = Query(filter: selectedMenu.wrappedValue?.predicate)
    }
    
    var body: some View {
        List(itemsWithLog) { item in
            ListRowView(item: item)
        }
        .navigationTitle(selectedMenu?.rawValue ?? "N/A")
    }
}

With this, when running your app on my iPhone 16 Pro Max simulator and selecting the all row, I got the following output:

CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZISOPTIONA FROM ZITEM t0 ORDER BY t0.Z_PK
CoreData: annotation: sql connection fetch time: 0.0059s
CoreData: annotation: fetch using NSSQLiteStatement <0x60000236ed00> on entity 'Item' with sql text 'SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZISOPTIONA FROM ZITEM t0 ORDER BY t0.Z_PK' returned 15000 rows
CoreData: annotation: total fetch execution time: 0.0062s for 15000 rows.
Time taken to fetch: 0.37052595615386963

As you can see, the time SwiftData used to retrieve the data was 0.37s, which was significantly larger than 0.0062s, the "total fetch execution time".

By time profiling the app, I confirmed that the 0.37s was pretty much the hang time shown in the Instruments trace.

So the app actually works correctly, except that the performance of SwiftData isn't quite as good as you would expect. If that is an issue for your app, I’d suggest that you file a feedback report – If you do so, please share your report ID here for folks to track.

In a situation like this, where the data set can eventually grow to pretty large, which can trigger a performance issue, I'd probably consider designing the app in a way that presents a smaller data set, like adding some filters or implementing pagination.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Hey @DTS Engineer, thanks for your response! I do believe this should work as expected (no hang whatsoever) without the need for pagination. On UIKit + CoreData you don't need pagination, using an NSFetchedResultsController allows you to display as many items as you wish on a UITableView/UICollectionView without any hangs whatsoever.

I've filed FB15507372 and put up the project for anyone to download and run here so it's easily reproducible: https://github.com/xmollv/SlowData

This is still an issue on iOS 26.1.

This is still an issue on iOS 26.2.

This is still an issue. What should we do? Really just paginate?

So basically, could I build the Photos app (or something like it) in SwiftData. Seems to be no. Because Query cannot handle anything remotely close to the thousands of displayed objects.

I tested a manual fetch on context. Performed in milliseconds as expected. But anything in Query, completely fails with like 10 seconds when it gets bad

@bryan1anderson I don't think you can. If your dataset is large enough (like my example), you'll always get hangs. It looks to me like Query/Predicate is the issue here, they seem to run everything on the Swift side rather than being transformed into SQL queries that run directly on the underlying SQL database.

On the example that I posted and that @DTS Engineer confirmed, the SQL part runs really fast, but the overall fetch is slow.

I'd love for Apple to give this more attention, because if having a big dataset is a deal breaker, nobody -unless you're 100% sure your data will always be very small- should use Swift Data. Imagine if you build a very complex app using Swift Data and your dataset keeps growing, you'll hit a point where you need to re-write the model layer using Core Data, kinda insane if you ask me.

@xmollv this is wild. I added a little button to your app that simply creates a new item. The button hangs for over 2 seconds because the update takes you know however many milliseconds but then the query kicks in on the main thread and blocks everything. Its ridiculously bad.

@DTS Engineer can you revisit this? We have submitted issues, but this is ridiculously problematic

Slow rendering List backed by SwiftData &#64;Query
 
 
Q