I have a SwiftUI app with an intents extension target. I also have a core data store located on a shared app group.
The intents extension can successfully write data to the store using newBackgroundContext()
in the Shortcuts app. My SwiftUI app can read the data from the app group in viewContext
but only when I force quit the app and re-open it.
I think I need a way to let my app know that it has been updated in a different context but I'm not sure how do do that?
I've tried setting context.automaticallyMergesChangesFromParent = true
in the app delegate and adding context.refreshAllObjects()
to onAppear
in the SwiftUI view but that doesn't seem to make any difference.
mergeChanges(fromContextDidSave:) seems promising but I'm not sure how to get the Notification.
I'm new to Core Data and very confused, so any pointers in the right direction would be great, thanks!
Here's a sanitised version of my current code:
App Delegate
lazy var persistentContainer: NSCustomPersistentContainer = {
let container = NSCustomPersistentContainer(name: "exampleContainerName")
container.loadPersistentStores { description, error in
if let error = error {
print("error loading persistent core data store")
}
}
return container
}()
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
print("error saving core data context")
}
}
}
class NSCustomPersistentContainer: NSPersistentContainer {
override open class func defaultDirectoryURL() -> URL {
var storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "exampleAppGroupName")
storeURL = storeURL?.appendingPathComponent("exampleContainerName")
return storeURL!
}
}
Scene Delegate
guard let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext else {
fatalError("Unable to read managed object context.")
}
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: ExampleView.environment(\.managedObjectContext, context))
self.window = window
window.makeKeyAndVisible()
}
Swift UI View
import SwiftUI
import CoreData
struct ExampleView: View {
@Environment(\.managedObjectContext) var managedObjectContext
@FetchRequest(
entity: ExampleEntityName.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \ExampleEntityName.date, ascending: false),
]
) var items: FetchedResults<ExampleEntityName>
var body: some View {
VStack {
List(items, id: \.self) { item in
Text("\(item.name)")
}
}
}
}
Intent Extension
class NSCustomPersistentContainer: NSPersistentContainer {
override open class func defaultDirectoryURL() -> URL {
var storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "exampleContainerName")
storeURL = storeURL?.appendingPathComponent("exampleAppGroupName")
return storeURL!
}
}
let persistentContainer: NSCustomPersistentContainer = {
let container = NSCustomPersistentContainer(name: "exampleContainerName")
container.loadPersistentStores { description, error in
if let error = error {
print("error loading persistent core data store: \(error)")
}
}
return container
}()
let context = persistentContainer.newBackgroundContext()
let entity = NSEntityDescription.entity(forEntityName: "ExampleEntityName", in: context)
let newEntry = NSManagedObject(entity: entity!, insertInto: context)
newEntry.setValue(Date(), forKey: "date")
newEntry.setValue("Hello World", forKey: "name")
do {
try context.save()
} catch {
print("Failed saving")
}
Don't know if you discovered an answer yet, but I had a similar problem using UserDefaults and an intent extension in a shared app group. The thing that finally worked for me was adding a call to load the items in the method below in the SceneDelegate:
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
SiriResponseManager.shared.grabUD()
}
In my example I am using a singleton that houses the method to load the items from the UserDefaults. This singleton class is an ObservableObject. In my first view that loads (ContentView in my case) I assign an ObservedObject variable to my singleton class and retrieve the @State variables in my singleton to populate the string in my view.
Hopefully this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With