Hi everyone!
I have an app on the App Store that uses Core Data as its data store. (It's called Count on Me: Tally Counter. Feel free to check it out.) One of the app's core feature is an interactive widget with a simple button. When the button is tapped, it's supposed to update the entity in the store.
My requirement is that the changes are then reflected with minimal latency in the main app and – ideally – also on other devices of the same iCloud user. And vice-versa: When an entity is updated in the app (or on another device where the same iCloud user is logged in), the widget that shows this entity should also refresh to reflect the changes.
I have read multiple articles, downloaded sample projects, searched Stackoverflow and the Apple developer forums, and tried to squeeze a solution out of AI, but couldn't figure out how to make this work reliably.
So I tried to reduce the core problem to a minimal example project. It has two issues that I cannot resolve:
-
When I update an entity in the app, the widget is immediately updated as intended (due to a call to WidgetCenter's
reloadAllTimelines
method). However, when I update the same entity from the interactive widget using the same app intent, the changes are not reflected in the main app. -
For the widget and the app to use the same local data store, I need to enable App Groups in both targets and set a custom location for the store within the shared app group. So I specify a custom URL for the
NSPersistentStoreDescription
when setting up the Core Data stack. The moment I do this, iCloud sync breaks.
Issue no. 1 is far more important to me as I haven't officially enabled iCloud sync yet in my real app that's already on the App Store. But it would be wonderful to resolve issue no. 2 as well. Surely, there must be a way to synchronize changes to the source of truth triggered by interactive widget with other devices of the same iCloud user. Otherwise, the feature to talk to the main app and the feature to synchronize with iCloud would be mutually exclusive.
Some other developers I talked to have suggested that the widget should only communicate proposed changes to the main app and once the main app is opened, it processes these changes and writes them to the NSPersistentCloudKitContainer
which then synchronizes across devices. This is not an option for me as it would result in a stale state and potential data conflicts with different devices. For example, when a user has the same widget on their iPhone and their iPad, taps a button on the iPhone widget, that change would not be reflected on the iPad widget until the user decides to open the app on the iPhone. At the same time, the user could tap the button multiple times on their iPad widget, resulting in a conflicting state on both devices. Thus, this approach is not a viable solution.
An answer to this question will be greatly appreciated. The whole code including the setup of the Core Data stack is included in the repository reference above. Thank you!
The reason your main app doesn't update when your widget changes the shared store is most likely because the viewContext in your main app doesn't automatically detect and merge remote changes. Here, remote changes mean the changes made by a different process, or by using batch processing.
Your main app can detect and handle remote changes in the following way:
-
Observe the the .NSPersistentStoreRemoteChange notification to get notified of any remote change.
-
In the notification handler, consume the store persistent history to detect the relevant changes and merge them to the managed object context tied to your main app UI. Alternatively, you can manage to reset the context, and then re-fetch, which should give you the up-to-date data.
For more information about this topic, see the following Apple sample projects:
Regarding using Core Data + CloudKit (NSPersistentCloudKitContainer
) in an extension, I'd like to suggest against doing that, as discussed in the following technote:
Best,
——
Ziqiao Chen
Worldwide Developer Relations.