Using WidgetKit + SwiftData

Yesterday I was setting up my first widget, scratching an itch I’ve had for a while “empty states” in some lock screen widgets and watch complications. I was following a couple of articles by Majid Jabrayilov, which I found really useful in getting some of the boilerplate set up for adding a widget extension to an app. Something those posts don’t touch on is integrating with the main app’s persistent data store, in my case SwiftData.

Working on it myself, I quickly ran into a process crash in Previews and the Simulator. The crash report included this snippet:

CrashReportError: Fatal Error in ModelContainer.swift

MyWidgetExtension crashed due to fatalError in ModelContainer.swift at line 144.

failed to find a currently active container for MyModel

I started searching for resources on integrating SwiftData with WidgetKit. The best resource, which helped me to head off a future issue with sharing data across app targets by setting up an AppGroup, was from Paul Hudson who had shared a light-on-detail post1 on accessing a SwiftData container from widgets. The post suggested adding “the modelContainer() modifier to your widget, e.g. in your StaticConfiguration or AppIntentConfiguration.” Unfortunately trying to add modelContainer(_:) to a Widget struct inside body fails as that modifier is not defined for WidgetConfiguration and adding it to the view inside that configuration results in the above error.

The issue was that I’d been trying to get a widget set up to display the static data for a SwiftData PersistentModel rather than pulling it out of the datastore. I follow the common practice of defining one or more instances of a model as a static function that can be passed into Previews as the data rather than relying on persisted data to be pulled out, but in this case it bit me because just initializing those models apparently relies on having an active container that could back them, even if it is unused.

As there was no example code to resolve or even explain this problem that I could find, I wanted to share this solution which allowed me to continue using un-persisted data in my Widget Previews. This var is never called, but having it in memory as part of the Provider instance allowed the MyModel instances to be created and displayed.

struct Provider: AppIntentTimelineProvider {
  var sharedModelContainer: ModelContainer = { // Note that we create and assign this value;
    let schema = Schema([MyModel.self])        // it is not a computed property.
    let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

    do {
      return try ModelContainer(for: schema, configurations: [modelConfiguration])
    } catch {
      fatalError("Could not create ModelContainer: \(error)")
    }
  }()
}
  1. “How to access a SwiftData container from widgets” is part of a series by Paul on getting started with SwiftData. I’m sure makes more sense in context of the tutorial path it belongs to, but I didn’t read through the rest.