You’ve used Time Travel to provide a timeline of complication data, allowing your users to seek forward and backward in time to their hearts’ content. What do you do when you need to update this data? Suppose a user adds a new calendar event for a coffee date, or you have a weather app and a new hourly forecast comes out? ClockKit includes multiple methods of updating your complication with new information, so that once you provide an initial set of data, you can add to it or refresh it altogether. Let’s go through these methods and see which ones are best for NomNomNom.
The easiest way to refresh complication data is at an interval. By providing a refresh date to ClockKit, you can get a callback when it’s time to provide more data. For NomNomNom, once you provide a timeline of data, unless that data changes, you don’t need to update during the day. Compared to a weather app that could update every 15 minutes, your data is relatively unchanging. To that end, you could simply update every night at midnight with a new day’s worth of data. To tell ClockKit when to wake up your complication controller to receive new updates, implement getNextRequestedUpdateDateWithHandler(_:) to provide midnight as the update date:
| func getNextRequestedUpdateDateWithHandler(handler: (NSDate?) -> Void) { |
| handler(NSDate().midnightAfter) |
| } |
This code tells ClockKit to request an update at midnight. It’s not enough to implement this, however; you need to respond to the update request. When ClockKit wakes up your app to provide additional data, it’ll call requestedUpdateDidBegin on your complication controller. Let’s implement that now:
| func requestedUpdateDidBegin() { |
| let complicationServer = CLKComplicationServer.sharedInstance() |
| |
| for complication in complicationServer.activeComplications { |
| complicationServer.extendTimelineForComplication(complication) |
| } |
| } |
To update your complications, you need to talk to a part of the system you haven’t seen yet: the complication server. Represented by the CLKComplicationServer class, the complication server is responsible for managing the active complications in the app—that is, the ones the user has installed onto her current watch face. You can query these complications with the activeComplications property, which returns an array of complications for this app that are currently active. Most of the time, there will be only one active complication, but if an app supports multiple complication families on the same watch face, a user could install both simultaneously. For any active complication, you call extendTimelineForComplication(_:) on the complication server, which tells ClockKit to ask for new events beyond the last provided timeline entry. You could also call reloadTimelineForComplication(_:) if you needed to reload the entire timeline; for apps where the data can change once it’s created, this is a better option.
Now that you’ve called extendTimelineForComplication(_:), you’re finished! ClockKit will continually update the complication at or around midnight every night, pulling in new calendar entries as needed. NomNomNom doesn’t need to update very often, so this is a good solution for you. For other types of apps, let’s next look at some other methods of reloading the timeline.
The CLKComplicationServer class is available from all of WatchKit, not just from your complication controller. From your WatchKit extension, you can query the installed set of complications, as well as call extendTimelineForComplication(_:) or reloadTimelineForComplication(_:) on the complication server. When you call either of those methods from within your WatchKit extension, ClockKit will either extend or reload the timeline. It’s possible that this could happen in the background while your watch app is open, so be sure that any common data sources can be read from multiple threads at once!
Why would you want to use manual updates? Any watch app where the complication timeline changes as a result of user activity is a great candidate. For instance, if you implemented a watch app for NomNomNom to add new food-based events to the user’s calendar, you could call these methods as new events were added. By proactively reloading your timeline, you can ensure that the data is always relevant when the complication becomes active. Even while the user is using your watch app, the complication can be a mere double-click away, since double-clicking the Digital Crown returns to the watch face and therefore your complications!
Reloading the timeline manually from the watch app works for when the user is on her watch, but for when she’s on her phone, you can use WatchConnectivity to update your complications. Just as with updating from the WatchKit extension, this is perfect for responding to user interaction by reloading the timeline. There’s no CLKComplicationServer to talk to on iOS, so instead use WatchConnectivity’s WCSession to send over the new data. Just like transferUserInfo(_:) for sending arbitrary data, WCSession has a complication-specific method, transferCurrentComplicationUserInfo(_:). By calling the latter with complication-specific information, your iOS app can update the complications on its counterpart watchOS app.
When you call transferCurrentComplicationUserInfo(_:), your session delegate handles it the same way as a call to transferUserInfo(_:). You need to implement session(_:didReceiveUserInfo:) and handle the info manually. Once you’ve handled the information from the iOS app—saved it to your database and the like—you still need to call extendTimelineForComplication(_:) or reloadTimelineForComplication(_:) for any active complication that now has new data, because ClockKit is extremely conservative about calling these methods on your behalf.
If all user info transfers are handled by the same session delegate method, why is there a separate transferCurrentComplicationUserInfo(_:) method on WCSession? As you’ll see later in this chapter, ClockKit is aggressive about limiting processing power. Any processing relating to a user info transfer begun with transferCurrentComplicationUserInfo(_:) is “marked” as belonging to the complication controller, and it may impact power use restrictions.
The source of updates for your complications keeps getting farther and farther away from your complication controller. First it was elsewhere in the WatchKit Extension, and then it was in the containing iOS app. You can update the timeline from even farther away: a remote server! This is a great option for apps that rely on a backend service for their data. If you turned NomNomNom into a social calendar for food lovers, you’d want to be able to update your calendar from the website. When that happened, you’d send a push notification to the phone. On the phone, you’d use the PKPushRegistry to register for push notifications of type PKPushTypeComplication. The push registry has a delegate method, pushRegistry(_:didReceiveIncomingPushWithPayload:forType:), that you can implement to handle these notifications. Handling them might look something like this:
| func pushRegistry(registry: PKPushRegistry!, |
| didReceiveIncomingPushWithPayload payload: PKPushPayload!, |
| forType type: String!) { |
| if type == PKPushTypeComplication { |
| let info = ["PushNotificationPayload": payload.dictionaryPayload] |
| |
| WCSession.defaultSession().transferCurrentComplicationUserInfo(info) |
| } |
| } |
Once you receive the notification in your iOS app, you still need to transfer the information to your watchOS app. From there, you can handle the data and update your complications. This is a great option for apps where the data changes frequently or at unpredictable intervals.
With these four methods of updating your complication, you’re bound to find something that fits whatever kind of app you’re making. ClockKit makes handling updates easy, and the same code that creates your timeline in the first place is automatically used to get future entries. All of this has a cost, however, and ClockKit is always monitoring how much energy your app is using. Next, let’s look at how ClockKit handles apps that try to spend too much time updating their information.
If there’s a limited resource in ClockKit, it’s time. Time spent on the CPU computing complication data causes the watch to lose battery life, and too many complications trying to load too much data would quickly overwhelm the battery and deplete a watch full of third-party complications by midday. To combat this, ClockKit encourages developers to spend as little energy as possible in their complication controllers. It keeps track of how much time each app has spent and gives each of them a power budget. Once an app has gone over its daily power budget, it can no longer update its complication until the next day. While that wouldn’t be a huge problem for NomNomNom, it could be extremely problematic for other apps—a complication that deals with stock prices, for instance, would be completely useless if it couldn’t update after 10 a.m.
To combat the power budget restrictions in ClockKit, you need to do everything in your power to make complication calculations quick. Later you’ll find an entire chapter about performance in general, but for ClockKit, there are some specific recommendations. First, don’t compute anything more than you need to. If you’re transferring a userInfo object from iOS, for instance, remove all unnecessary data from the dictionary before sending it across. Instead of making network calls when you update your complication, take advantage of Background App Refresh on iOS to perform expensive network calls when it doesn’t affect the complication controller’s power budget. Finally, whenever possible, use extendTimelineForComplication(_:) instead of reloadTimelineForComplication(_:); the former will only add new timeline entries, but the latter will re-create existing complications!
There’s no way to ask the complication server how much of your power budget you’ve used up. Once it’s up, it’s up. You do get one last chance to update your complication, however: when your requested update time is reached, if your budget is exhausted, ClockKit will call requestedUpdateBudgetExhausted on your complication controller in place of requestedUpdateDidBegin. This is your last chance to update complications for the day, so the timeline entries you create should not be short-lived. Once this method finishes and your timeline is finalized for the day, you won’t receive any more CLKComplicationDataSource methods until the next day.
18.222.179.204