ioscore-datacloudkitwatchosapple-watch-complication

How to update watch complications using CoreData + CloudKit with a private database?


My app uses a database synchronized between iPhone, watch and iCloud. Until recently, I used custom synchronization code.
Particularly, when data changes on the iPhone, the watch complications were updated via transferCurrentComplicationUserInfo(_:). This updated the complications more or less immediately.

The new version of the app uses instead CoreData + CloudKit, and iPhone and watch are synchronized automatically with iCloud using the private database.
If, e.g., the iPhone updates data, this is automatically uploaded to iCloud, and iCloud sends a silent push notification to the watch to update the data there.
If I open the app on the watch, the update is applied, and the watch shows the new data. So far so good.

The problem is updating the complication data while the app does not run on the watch.
In my current version, the complications are only updated when the app is activated. This is obviously not the idea of complications, and I am sure I am missing something.

How can I update the complications even if the app is terminated or in the background?

EDIT due to the comment of Paulw11:

The complication has to be updated as soon as possible after data have been changed on the iPhone.
Since this can happen any time, scheduling a background refresh task in the watch does not solve the problem.


Solution

  • Short answer:

    It is possible to send push notifications to a watch, using a push type complication, but this is currently not possible with iCloud.
    With iCloud one has to send a silent push, and to update the complications locally.

    Long answer:

    watchOS 6 implemented the PushKit framework:

    PushKit notifications differ from the ones you handle with the User Notifications framework. Instead of displaying an alert, badging your app’s icon, or playing a sound, PushKit notifications wake up or launch your app and give it time to respond.

    It uses a PKPushRegistry object that lets you specify a PKPushType, among others complication. Docu:

    Use this type of notification to deliver updated data related for your watchOS app’s complication. The watchOS app’s complication must be active on the user’s current clock face. If it is not, the system does not deliver pushes of this type. For watchOS 6 and later, send the push notification directly to Apple Watch.

    The bad news:
    PushKit requires a Remote Notification Server. There are many commercial server that let you send push notifications with a specific PKPushType, but iCloud does not.

    With iCloud, and thus also with CoreData + CloudKit mirroring, one can store a subscription record in the respective database that generates an internal permanent query. When the query fires, iCloud sends a silent remote notification. It wakes up the app, and the payload lets you update the complications.
    There are several subscription types that let iCloud respond to specific changes: CKDatabaseSubscription, CKRecordZoneSubscription, and CKQuerySubscription.
    CoreData + CloudKit mirroring with a private database uses a specific zone named com.apple.coredata.cloudkit.zone, so here a CKRecordZoneSubscription may be appropriate, see here. It lets you also specify a recordType, so that push notifications are only sent when a record of this type is modified.

    EDIT:

    Warning: An update using silent remote notifications is only possible very rarely, and not every push may be handled. The docs say:

    The system treats background notifications as low priority: you can use them to refresh your app’s content, but the system doesn’t guarantee their delivery. In addition, the system may throttle the delivery of background notifications if the total number becomes excessive. The number of background notifications allowed by the system depends on current conditions, but don’t try to send more than two or three per hour.

    When a device receives a background notification, the system may hold and delay the delivery of the notification, which can have the following side effects: When the system receives a new background notification, it discards the older notification and only holds the newest one.