Importing Data into SwiftData in the Background Using ModelActor and @Query

I have an app with fairly typical requirements - I need to insert some data (in my case from the network but could be anything) and I want to do it in the background to keep the UI responsive.

I'm using SwiftData.

I've created a ModelActor that does the importing and using the debugger I can confirm that the data is indeed being inserted.

On the UI side, I'm using @Query and a SwiftUI List to display the data but what I am seeing is that @Query is not updating as the data is being inserted. I have to quit and re-launch the app in order for the data to appear, almost like the context running the UI isn't communicating with the context in the ModelActor.

I've included a barebones sample project. To reproduce the issue, tap the 'Background Insert' button. You'll see logs that show items being inserted but the UI is not showing any data.

I've tested on the just released iOS 18b3 seed (22A5307f).

The sample project is here:

https://hanchor.s3.amazonaws.com/misc/SwiftDataBackgroundV2.zip

Answered by DTS Engineer in 795327022

I see the issue a bug on SwiftData + SwiftUI side because @Query is supposed to merge the change from the model actor (ModelActor) and trigger a SwiftUI update, and so would suggest that you file a feedback report and post your report ID for folks to track.

For a workaround before the issue is fixed on the framework side, you might consider observing .NSManagedObjectContextDidSave notification and triggering a SwiftUI update from the notification handler. For example:

import Combine

extension NotificationCenter {
    var managedObjectContextDidSavePublisher: Publishers.ReceiveOn<NotificationCenter.Publisher, DispatchQueue> {
        return publisher(for: .NSManagedObjectContextDidSave).receive(on: DispatchQueue.main)
    }
}

struct MySwiftDataView: View {
    @Query private var items: [Item]
    
    // Use the notification time as a state to trigger a SwiftUI update.
    // Use a state more appropriate to your app, if any.
    @State private var contextDidSaveDate = Date()
    
    var body: some View {
        List {
            ForEach(items) { item in
                Text("\(item.timestamp)")
            }
            // Refresh the view by changing its `id`.
            // Use a way more appropriate to your app, if any.
            .id(contextDidSaveDate)
        }
        .onReceive(NotificationCenter.default.managedObjectContextDidSavePublisher{ notification in
            contextDidSaveDate = .now
        }
    }
}

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Accepted Answer

I see the issue a bug on SwiftData + SwiftUI side because @Query is supposed to merge the change from the model actor (ModelActor) and trigger a SwiftUI update, and so would suggest that you file a feedback report and post your report ID for folks to track.

For a workaround before the issue is fixed on the framework side, you might consider observing .NSManagedObjectContextDidSave notification and triggering a SwiftUI update from the notification handler. For example:

import Combine

extension NotificationCenter {
    var managedObjectContextDidSavePublisher: Publishers.ReceiveOn<NotificationCenter.Publisher, DispatchQueue> {
        return publisher(for: .NSManagedObjectContextDidSave).receive(on: DispatchQueue.main)
    }
}

struct MySwiftDataView: View {
    @Query private var items: [Item]
    
    // Use the notification time as a state to trigger a SwiftUI update.
    // Use a state more appropriate to your app, if any.
    @State private var contextDidSaveDate = Date()
    
    var body: some View {
        List {
            ForEach(items) { item in
                Text("\(item.timestamp)")
            }
            // Refresh the view by changing its `id`.
            // Use a way more appropriate to your app, if any.
            .id(contextDidSaveDate)
        }
        .onReceive(NotificationCenter.default.managedObjectContextDidSavePublisher{ notification in
            contextDidSaveDate = .now
        }
    }
}

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

iOS 18 issue submitted as FB14240514

Importing Data into SwiftData in the Background Using ModelActor and @Query
 
 
Q