Allow ComplicationController to access CoreData SwiftUI

Viewed 313

In Xcode 12 beta 2 using SwiftUI I want my ComplicationController to make a FetchRequest in order to update my complications but I am having trouble injecting the persistent store into the environment.

I should note this is a pure SwiftUI App in watchOS where the app entry point is the @main struct. There is no ExtensionDelegate or HostingController.

For the watchOS app itself this is how I'm setting up the PersistentContainer:

struct RunPlanner: App {
    @Environment(\.scenePhase) private var scenePhase
    @StateObject private var persistentStore = PersistentStore.shared
    @ObservedObject var selection = TabSelection()
    
    var body: some Scene {
        WindowGroup {
            TabView(selection: $selection.currentTab) {
                WatchAddRunView(tabSelection: selection)
                    .tag(0)
                ContentView()
                    .tag(1)
            }
            .environment(\.managedObjectContext, persistentStore.context)
            .animation(.easeIn)
        }
        .onChange(of: scenePhase) { phase in
            switch phase {
                case .active:
                    print("\(#function) REPORTS - App change of scenePhase to ACTIVE")
                case .inactive:
                    print("\(#function) REPORTS - App change of scenePhase to INACTIVE")
                case .background:
                    print("\(#function) REPORTS - App change of scenePhase to BACKGROUND")
                    savePersistentStore()
                default:
                     print("\(#function) REPORTS - App change of scenePhase Default")
            }
        }
    }
    
    func savePersistentStore() {
        persistentStore.saveContext()
    }
}

This works for the app itself to save values to CoreData however my ComplicationController is not seeing the NSPersistentStoreContainer and I'm not sure how to inject it.

My current attempt within my ComplicationController class is this:

class ComplicationController: NSObject, CLKComplicationDataSource {
    @Environment(\.managedObjectContext) var moc

    let request = NSFetchRequest<RunEvents>(entityName: "RunEvents")
        ....complication code...
}

func getSavedRunName() -> String {
        var activeName = "Run Roster"
            do {
            let savedRuns = try moc.fetch(request)
            savedRuns.forEach({
                if $0.isActive {
                    guard let fetchedName = $0.name else { return }
                    activeName = fetchedName
                }
            })
        } catch {
            print("Error in Fetch Request")
        }
        
        return activeName
    }

However the getSavedRunName method will cause the app to crash on execution with the debugger saying "reason: '+entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name 'RunEvents''"

I've searched and fumbled around for various solutions with no positive results. Any insight here is very much appreciated.

-Dan

1 Answers

The CLKComplicationDataSource is a separate separate executable from the WKExtensionDelegate, so you need to set up your CoreData stack in each. Additionally, you use an App Group to share the same Core Data files. I use an extension like this from all my targets.

extension NSPersistentContainer {

    static func appContainer() -> NSPersistentContainer {
        let container = NSPersistentContainer(name: AppConstants.databaseName)
        
        let storeURL = URL.storeURL(for: AppConstants.appGroupName, databaseName: AppConstants.databaseName)
        let storeDescription = NSPersistentStoreDescription(url: storeURL)
        container.persistentStoreDescriptions = [storeDescription]
        
        container.loadPersistentStores { storeDescription, error in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        }
        return container
    }
    
}

Note that there would be additional work TBD to sync changes made in extensions back into the app's contexts. My complications (and widgets) are read-only, and the OS runs them on demand, so they initialize fresh and up-to-date.

When syncing across devices, I use CloudCore

Related