iOS: Today Extensions / Widgets

A Today extension, also known as a widget, is a way for the app to provide quick access to its main functions and the up-to-date information in the Notification Center. The content for the widgets might be directly fetched from the web, but quite often there is a requirement to share data between the App and its widget.

How to add Today extension / Widget to an existing project?

Just add a new target of type “Today extension” to your project . To debug/start Today extension just select the proper target and run.

Can the same code, e.g. views and controllers, be shared/utilized between Widget and App targets?

Yes. Be sure to select checkboxes for the corresponding files in the “Target Membership” window of the Xcode. There are, however, some limitations, especially using swipe gestures. The swipe gesture triggers swiping between the Today and Notifications sections within Notification Center, so it doesn’t really provide the best or most reliable user experience.

What about CoreData in Widgets?

No problem, almost.. In general, by default, an App and its extension cannot access the same data, since their security domains are different. In order to solve this problem there should be created an App Group. An App Group provides a shared bundle that can be used by the app and its extensions as a shared container in which data can be stored. The App group can be configured in the apple developer portal (s. nice tutorial) or directly in Xcode under target capabilities.

In order to share the same data base in both App and widget, Persistent Container must be initialized with NSPersistentStoreDescription pointing to the shared sqlite file:

let persistentContainer = NSPersistentContainer(name: "DataMode")
persistentContainer.persistentStoreDescriptions = [NSPersistentStoreDescription(url: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.yourdomain.appname")!.appendingPathComponent("DataModel.sqlite"))]
persistentContainer.loadPersistentStores { (persistentStoreDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}

Afterwards, fetching data from CoreDate in the Widget works as usual. A bit tricky is writing data into Context within Widgets. The widget doesn’t run in the same process as the App. That’s why no notification like NSManagedObjectContextDidSaveNotification is sent. For the managed objects fetched by the app this can mean they become outdated. What to do? Read this blog entry.

“Unable to Load” message in Today’s Widget?

  • be sure to set preferredContentSize
    self.preferredContentSize = CGSizeMake(320, 100);
    
  • reboot your device, if you have a feeling, that the widget isn’t even loaded by xcode
  • this can be a memory issue, please read this.