Connecting SwiftUI app to CoreData

Just finished watching the Data Essentials in SwiftUI session, and I had a few questions relating to connecting the views to the data model.

In particular, one example from the video was this:
Code Block
@main
struct BookClubApp: App {
@StateObject private var store = ReadingListStore()
var body: some Scene {
WindowGroup {
ReadingListViewer(store: store)
}
}
}

I assume that the initializer for ReadingListStore() would connect to any arbitrary data backing. If we're using CoreData, how does @StateObject wrapper on the store relate to/impact the use of @FetchRequest property wrapper on CoreData requests? Should we no longer use @FetchRequest because @StateObject is handling ensuring that changes to store are reflected in the UI?

Second, the video used this example to emphasize the difference between @ObservedObject and @StateObject:
Code Block
struct ReadingListViewer: View {
var body: some View {
NavigationView {
ReadingList()
Placeholder()
}
}
}
struct ReadingList: View {
@StateObject var store = ReadingListStore()
var body: some View {
// ...
}
}

What I didn't quite understand was whether even with the @StateObject the store was being recreated every time that ReadingList view appeared on screen (but it's preventing the store from being recreated every time the view is redrawn while on screen)? If the store is drawing from some data model elsewhere, either way the issue is one of efficiency, not data loss, right?

Finally, I had a question about data relating to the app - scene distinction. If our app doesn't have a need to show the same data side by side in split screen (iPadOS) or in different windows (Mac), where is the best place to do our logic so that we get a new project/document/etc anytime a user opens a new window (ie creates a new scene)? In other words if the data model is for example a canvas with a bunch of shapes on it, where should we put the logic for having the app show a new canvas every time a new scene is created? If we did something like this:
Code Block
@main
struct BookClubApp: App {
var body: some Scene {
WindowGroup {
ReadingListViewer()
}
}
}
struct ReadingListViewer: View {
@StateObject var store = ReadingListStore()
var body: some View {
NavigationView {
ReadingList()
Placeholder()
}
}
}

Would that allow us to assume that every time ReadingListStore() is called, we are getting a new scene?

Replies

I think it’s is quite the opposite. The new @StateObject property wrapper will instantiate just before the view is created and will live until that view is destroyed. Based on this view being the main view for the app, I would expect the data and the view to live until the app closes. Adding core data just ensures the data is persisted, but we still need to fetch the data before the view is created. But more importantly, we need to save the data more carefully so that the app will fetch the fresher data changes whenever the view is about to appear. Therefore, if this main view gets replaced during the app’s life cycle, I would expect the data would go away, any unsaved changes would be lost. That is why we should now use the onChange event to save data changes when they happen. That way, when the view is recreated, we can load the persisted data and not lose our previously made changes.
Not so much an answer, more a report of where I've got to with this so far... in the hope that it may prompt further discussion and frankly help me find a stable solution.

I've successfully hooked up a Core Data persistent store in the following manner...

Code Block
@main
struct CoreDataApp: App {
    @StateObject var persistentStore = PersistentStore.shared
    var body: some Scene {
        WindowGroup {
            ContentView(store: persistentStore)
                .environment(\.managedObjectContext, persistentStore.context)
        }
    }
}

where the persistent store is...

Code Block
import Cocoa
import CoreData
class PersistentStore: ObservableObject {
    var context: NSManagedObjectContext { persistentContainer.viewContext }
static let shared = PersistentStore()
    private init() {}
    private let persistentStoreName: String = "TheNameOfMySQLiteStore"
    // MARK: - Core Data stack
    lazy var persistentContainer: NSPersistentContainer = {
// ... per Apple's Core Data stack persistent container template ...
}
}    

(Acknowledgement to KrakenDev for the one line singleton.)

and then I carry the reference to the PersistentStore into the ContentView as an @ObservedObject...

Code Block
struct ContentView: View {
    @ObservedObject var store: PersistentStore
...etc.
}

The most important part of this process IMHO is the injection of the NSManagedObjectContext (viewContext) from a single instance of the NSPersistentContainer into each and any view that requires access to the context. NSManagedObjects by default adopt the ObservableObject protocol so I don't need to apply this to each Core Data subclassed NSManagedObject entities. Although for the use in List, it is worth adopting the Identifiable protocol. Any change or addition or deletion to an NSManagedObject within the context therefore remains in the context, until such time that the App closes. If at any stage I call "do try context.save()", then any create, update or delete will persist.

My advice - don't focus on @ObservableObject and @ObservedObject - focus on injecting an NSManagedObjectContext into the views where you need to create, read, update or delete.

So exactly what happens when the App closes is not clear to me and is possibly why Apple still recommend the use of AppDelegate in it's SwiftUI Core Data template, because it provides API such as...

Code Block
    func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {}

... which helps us manage Core Data related activity.

I'm finding it relatively easy to implement CRUD processes. I'm using @FetchRequest to grab data from the     @Environment(\.managedObjectContext) var context that I include in each View struct as required, and I've begun to experiment with dynamic fetch requests that take a generic NSManagedObject.

What I'm finding extremely difficult is the process of maintaining stable views using the new App & Scene protocols and WindowGroup container. I'm putting that down to two things...
  • my lack of knowledge/understanding;

  • if the new API were ready for Core Data, then Xcode would provide a template to use these with Core Data.

Which leads me to guess that AppDelegate instantiated apps with Core Data may be the safest path for the immediate future.

Not that I'm heading down that path yet!
There is a tutorial from RayWenderlich.com on how to use CoreData with SwiftUI. Also, there are videos and written tutorials on HackingWithSwift.com
It appears you are missing some of the fundamentals.

I am not allowed to include URLs. Just Google SwiftUI CoreData to find them quickly.
I found a much better implementation of Core Data with new App and Scene protocols that provides access to change of app state through the .onChange(of: scenePhase) modifier.

I'd recommend using that.

Search Apple Developer Forums for "Using Core Data with SwiftUI App Protocol" under tag wwdc20-10041

Here is the thread link... look to the accepted answer...
https://developer.apple.com/forums/thread/650876