Where and When to Get Data for Watch Complication

Where and When to get data for Watch Complication

For watchOS 3, Apple recommends that you switch from using the complication datasource getNextRequestedUpdateDate scheduled update to update your complication.

The old way for watchOS 2

requestedUpdateDidBegin() is really only designed to update the complication. Keeping your complication (and watch app) up to date usually involves far more than reloading the timeline (and asynchronously retrieving data never fit in well with the old approach).

The new way for watchOS 3

The new and better approach is to use background refresh app tasks. You can use a series of background tasks to schedule and handle your app extension being woken in the background to:

  • Fetch new data

    • using WKWatchConnectivityRefreshBackgroundTask to obtain data from the phone, or
    • using WKURLSessionRefreshBackgroundTask to download data from a server
  • update your model once the data arrives,
  • update your complication from the model (by reloading or extending the timeline), and finally
  • update your app's dock snapshot to show the data on the dock

Call each tasks’s setTaskCompleted method as soon as the task is complete.

Other benefits of using app tasks

One of the key features about this design is that the watch extension can now handle a variety of foreground and background scenarios which cover:

  • initially loading data when your app/complication starts,
  • updating data in the background, when the extension is woken by a background task, and
  • updating data in the foreground, when the user resumes your app from the dock.

Apple recommends that you use each opportunity you are given regardless of whether your app is in the foreground or background to keep your complication, app, and dock snapshot up to date.

Are there any limitations?

The number of total available tasks per day is divided among the number of apps in the dock. The fewer apps in the dock, the more tasks your app could utilize. The more apps in the dock, the fewer you can utilize.

  • If your complication is active, your app can be woken up at least four times an hour.

  • If your complication is not active, your app is guaranteed to be woken at least once an hour.

Since your app is now running in the background, you're expected to efficiently and quickly complete your background tasks.

Background tasks are limited by the amount of CPU time and CPU usage allowed them. If you exceed the CPU time (or use more than 10% of the CPU while in the background), the system will terminate your app (resulting in a crash).

For more information

  • A good introduction explaining when and why to update your watch app is covered in Designing Great Apple Watch Experiences.

  • For specifics, the Keeping Your Watch App Up to Date session covers everything you need to know to keep your complication, app, and dock snapshot up to date.

  • WatchBackgroundRefresh sample code demonstrates how to use WKRefreshBackgroundTask to update WatchKit apps in the background.

Where should Apple Watch Complication data be stored?

You could store some data in UserDefaults, and access that from your complication data source.

ie.

//In a background task
func getComplicationData(){
let yourData = someNetworkCall()
/*
yourData = [
"complicationHeader": "Some string",
"complicationInner": "Some other stirng"
]

*/
UserDefaults.standard.set(yourData, forKey: "complicationData")
}

Then in your ComplicationDataSource

func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {

if let yourData = UserDefaults.standard.dictionary(forKey: "complicationData") as? [String: String] {
//Handle setting up templates for complications
}

}

Best way to get Core Location data from an Apple Watch Complication?

A few important points:

  1. Don't do any computation or async request in your Complication Controller, you will eat up your complication update budget for a day very quickly, plus OS will suspend the process after a few seconds.

  2. You can use Core Location in your Extension Delegate either in foreground (no restrictions) or in the background. If you want to use it in the background, you need to set UIBackgroundModes to location in your WatchKit Extension's Info.plist AND your location manager's allowBackgroundLocationUpdates property to true. Don't forget to set it to false when you don't need constant location updates. For instance, if you need to request the location once in the background, turn that property off once you receive the location fix.

  3. You can only update your complication so many times during the day (Apple doesn't publish the exact number). You can expect to be able to update it at least 4-6 times an hour, possibly more, but definitely not every minute. So you can forget about displaying any real-time data on your complication.

Apple Watch complication network requests

You'll run into issues related to asynchronously fetching data from within the complication data source, mostly due to the data being received after the timeline update is complete.

Apple recommends that you fetch the data from a different part of your app, and have it available in advance of any complication update:

The job of your data source class is to provide ClockKit with any requested data as quickly as possible. The implementations of your data source methods should be minimal. Do not use your data source methods to fetch data from the network, compute values, or do anything that might delay the delivery of that data. If you need to fetch or compute the data for your complication, do it in your iOS app or in other parts of your WatchKit extension, and cache the data in a place where your complication data source can access it. The only thing your data source methods should do is take the cached data and put it into the format that ClockKit requires.

Other ways to approach it:

  • The best way to update your complication (from your phone once you have received updated weather data) is to use transferCurrentComplicationUserInfo.

  • Alternately, you could have your watch app or glance cache its most recent weather details to be on hand for the next scheduled update.

If you absolutely must handle it from the complication:

You could have the scheduled timeline update get the extension to start an NSURLSession background task to asynchronously download the information from your weather service. The first (scheduled) update will then end, with no new data. Once the new weather data is received, you could then perform a second (manual) update to reload the complication timeline using the just-received data.

I don't have any personal experience with that approach, mostly because of the unnecessary need for back-to-back timeline updates.

Setting up initial data for a WatchOS complication

Take a look at the getCurrentTimelineEntry(for:withHandler:) function in ClockKit. This is where watchOS queries for real data. So, the user doesn't have to launch the actual watchOS application since your complication can act independently by providing data to watchOS.

After activating the watchOS complication, watchOS should automatically call the getCurrentTimelineEntry(for:withHandler:) function and will call it again after a given time interval specified in the getNextRequestedUpdateDate(handler:) function.

WatchKit Complication: get Complication data from extension delegate

// Get the complication data from the extension delegate.
let myDelegate = WKExtension.sharedExtension().delegate as! ExtensionDelegate
var data : Dictionary = myDelegate.myComplicationData[ComplicationCurrentEntry]!

Above from Apple's Doc is just an example on storing the data you need for your complication in your extension delegate seeing as how you can access it easily as a singleton. The reference to "myComplicationData" is an example of a Dictionary and is not a parameter in the ExtensionDelegate by default.

Either set up your own class as a singleton which holds data for your watch like so:

// Access by calling:
// Model.sharedModel.modelVal1
class Model {
static let sharedModel = Model()
var modelVal1: Float!
var modelVal2: String!
}

Or use the extension delegate as your singleton and add your properties to its class like below. This will allow you to access whatever variables you create in your ExtensionDelegate.

// ExtensionDelegate.swift
class ExtensionDelegate: NSObject, WKExtensionDelegate {
var dataVar1: Float!
var dataVar2: String!
var myDictionary: [String: String]!
}

// ComplicationController.swift
import WatchKit

class ComplicationController: NSObject, CLKComplicationDataSource {

func someMethod() {
let myDelegate = WKExtension.sharedExtension().delegate as! ExtensionDelegate
// Here is an example of accessing the Float variable
let accessVar = myDelegate.dataVar1
let myDict = myDelegate.myDictionary
}
}

Using either way will help keep your data in one spot so you can always access it from any class in your watch extension.

Extra large apple watch complication with color

It seem that it does not display color. The solution is to use CLKComplicationTemplateGraphicExtraLargeCircularView instead.



Related Topics



Leave a reply



Submit