Search code examples
iosswiftswiftuiwidgetwidgetkit

Using CoreData as dynamic Widget Intents in SwiftUI


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.


Files

Core Data Model - Favourite - FavouritesCDModel

In my Core Data model I have more options but for this example:

  • UUID
  • beginName
  • finishName

Widget Intent - NextDepartures.intentdefinition

This is what has been set up as followed by Apple's Code Along videos:

app-name-intent - 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
  }
}

CoreDataManager

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 []
    }
  }
}

Solution

  • 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.


    CoreDataManager

    Inside the persistentContainer add in the storeURL and descriptions:

    let storeURL = FileManager.appGroupContainerURL.appendingPathComponent("COREDATAFILE.sqlite")
    container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeURL)]
    

    FileManager+Ext

    Create a FileManager extension for the container url:

    extension FileManager {
      static let appGroupContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.domain.app")!
    }
    

    Info.plist

    Make sure that the Info.plist files have access to the app group

    Signing and Capabilities

    Make sure you add the App Groups capability to each target that needs it, and add it in App Store Connect