A Today extension, also known as a widget, is a great way to present up-to-date information in the Notification Center. This information might be directly fetched from the web, but can also be related to a local feature of your app and show its status.
If you have experience with widgets, you have probably used Core Data to share the data between your app and its widget. Accessing the app’s Core Data store from inside the widget is not a big deal when both the app and the widget are members of the same app group and the database file is stored in the shared container. In case your widget just reads data and doesn’t write to the store, you won’t get any trouble with synchronization. The fetched objects of your app’s managed object context will always represent what is currently on disk.
Today extensions can do more than just show content. They can also include controls, e.g. buttons, to offer some simple interaction. But in this case it might be necessary to manipulate, add or delete entries in the database. For the managed objects fetched by the app this means they become outdated. No notification like NSManagedObjectContextDidSaveNotification
is sent because the app and the widget don’t run in the same process.
One way to solve this could be to let the widget set a flag in the user defaults shared with the app. The app can react on the flag when it becomes active by resetting the managed object context. Unfortunately, in most cases this is not practicable. This approach requires to discard references to all the fetched managed objects since they are invalid afterwards.
The ideal solution, then, is to apply the specific set of changes to the managed object context to bring it in line with what is on disk. The good news: Core Data already supports a situation where the database file was changed by another process. This is how changes from iCloud are being adopted. It uses the method mergeChangesFromRemoteContextSave
of NSManagedObjectContext
which handles the merge based on the provided dictionary of changes. The keys must be those known from the NSManagedObjectContextDidSaveNotification
. The values should be an array of NSManagedObjectID
objects or NSURL
objects containing a URI.
The trick now is to take those sets of managed objects delivered by NSManagedObjectContextDidSaveNotification
in the widget, produce exactly the required notification data needed for mergeChangesFromRemoteContextSave
and propagate it via the shared NSUserDefaults
. Since NSManagedObjectID
doesn’t implement NSCoding
and therefore can’t be serialized, we use its URI which provides an archivable reference. The following method shows how this can be done:
Your app can now unarchive the data as soon as it becomes active. This can be done either directly in applicationDidBecomeActive
or by listening to the UIApplicationDidBecomeActiveNotification
notification. The notifications caught and archived in the widget are being merged in the same order they occured:
The effect you are getting is the same as when calling mergeChangesFromContextDidSaveNotification
after a NSManagedObjectContextDidSaveNotification
notification. The changes will be picked up by the fetched results controllers and by any other object registered on notifications from the context. The UI can then be updated properly avoiding a full reload.