I've followed along the Apple Developer Code-Along videos (https://developer.apple.com/news/?id=yv6so7ie) as well as looked at this repo with different Widget types (https://github.com/pawello2222/WidgetExamples) in search on how to populate a dynamic intent with items from Core Data.
My question is: How can I used the saved Core Data objects from the user to have them as "filters" or options in widget settings?
My Core Data model is called Favourite
and it is a Class Definition CodeGen file.
I have added the Intent target to my project and can get it to appear in the Widget settings, but when I tap in to "Choose" the list is empty. However, in my Core Data there are 3 saved items.
I have tried doing simple things like print(CoreDM.shared.getAllFavourites())
to see if I am even retrieving them all, but not listing in the settings, but the console prints out:
<NextDeparturesIntent: 0x283490bd0> {
favourite = <null>;
}
At this point I'm stuck on understanding on how I can get my Favourites visible and then usable. It seems everything else is hooked up and working or ready but the retrieval.
I have also tried re-adding into the Info.plist
of the intent the IntentSupported of the intent's name: NextDeparturesIntentHandling
:
but that had no success.
Favourite
- FavouritesCDModelIn my Core Data model I have more options but for this example:
NextDepartures.intentdefinition
This is what has been set up as followed by Apple's Code Along videos:
IntentHandler.swift
import Intents
class IntentHandler: INExtension, NextDeparturesIntentHandling {
let coreDM = CoreDataManager.shared
func provideFavouriteOptionsCollection(
for intent: NextDeparturesIntent,
with completion: @escaping (INObjectCollection<FavouriteRoutes>?, Error?) -> Void
) {
dump( coreDM.getAllFavourites() ) // <--- debug line
let favouriteRoutes = coreDM.getAllFavourites().map {
FavouriteRoutes(identifier: $0.uniqueId, display: $0.departingStopName!)
}
let collection = INObjectCollection(items: favouriteRoutes)
completion(collection, nil)
}
override func handler(for intent: INIntent) -> Any {
return self
}
}
import CoreData
final class CoreDataManager {
static let shared = CoreDataManager()
private init() {}
private let persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "FavouritesCDModel")
container.loadPersistentStores(completionHandler: { description, error in
if let error = error as NSError? {
fatalError("Core Data Store failed \(error.localizedDescription)")
}
})
return container
}()
var managedObjectContext: NSManagedObjectContext {
persistentContainer.viewContext
}
func getAllFavourites() -> [Favourite] {
let fetchRequest: NSFetchRequest<Favourite> = Favourite.fetchRequest()
do {
return try managedObjectContext.fetch(fetchRequest)
} catch {
return []
}
}
}
I didn't realise that the app, widget, and other targets are all sandboxed.
I incorrectly assumed everything within the same app ecosystem would be allowed access to the same items.
In order to get the above code to work is adding the file to the App Groups
and FileManager
.
Inside the persistentContainer
add in the storeURL
and descriptions:
let storeURL = FileManager.appGroupContainerURL.appendingPathComponent("COREDATAFILE.sqlite")
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeURL)]
Create a FileManager extension for the container url:
extension FileManager {
static let appGroupContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.domain.app")!
}
Make sure that the Info.plist
files have access to the app group
Make sure you add the App Groups
capability to each target that needs it, and add it in App Store Connect