Skip to main content
mbehan

Detecting Which Complication Launched Your WatchKit App

One of the joys of working with watchOS, much like it was working with iPhone OS many years ago, is the enforced simplicity. Free from worrying about about the unending device combinations and configurations and the unlistible features and extension points of modern iOS the constraints of a limited SDK focus your creativity. Simple, robust, yet still delightful interfaces flow from your fingertips, designers designs are readily translated to working product.

Sadly though, we're not content for long. Just like in the early days of iPhone OS, you soon find yourself wanting to do just a tiny bit more than Apple has made available, and so focus and delight makes way to our more common friend, the ugly hack. Today's feature that just couldn't wait for a proper API is: detecting which watch face complication launched my app.

How It Works #

When your app is launched in response to the user tapping a complication, the handleUserActivity method of your WKExtensionDelegate is called. You're given a userInfo dictionary, and this is where we'd hope to find the details of which complication had launched us. Sadly though there's no CLKComplicationFamilyKey to let you know the user tapped the circular small rather than the utilitarian large to launch the app, but there is something we can use, the CLKLaunchedTimelineEntryDateKey. This gives us the exact date and time that the complication was created at. By remembering exactly when we created which complication then we can figure out which complication resulted in the app being launched and acting accordingly.

The Code #

// 1.
class ComplicationTimeKeeper{
    
    static let shared = ComplicationTimeKeeper()
   
    var utilitarianLarge : Date?
    var utilitarianSmall : Date?
    var circularSmall : Date?
    var modularLarge : Date?
    var modularSmall : Date?
}

// 2. in your CLKComplicationDataSource
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping ((CLKComplicationTimelineEntry?) -> Void)) { 
     
     // Call the handler with the current timeline entry
     switch complication.family {
     case .utilitarianLarge:
            
         let date = Date()
         ComplicationTimeKeeper.shared.utilitarianLarge = date
            
         let template = CLKComplicationTemplateUtilitarianLargeFlat()
         template.textProvider = CLKSimpleTextProvider(text:"Something")
            
         let timelineEntry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
         handler(timelineEntry)
            
     default: handler(nil)
     }
}

// 3. in your WKExtensionDelegate
func handleUserActivity(_ userInfo: [AnyHashable : Any]?) {
    
    guard let userInfo = userInfo, let timelineDate = userInfo[CLKLaunchedTimelineEntryDateKey] as? Date else{
        return
    }
    
    if let utilLarge = ComplicationTimeKeeper.shared.utilitarianLarge, timelineDate.compare(utilLarge) == .orderedSame {
         WKExtension.shared().rootInterfaceController?.pushController(withName: "SomeController", context: nil)
    }
}

In 1, we create a singleton (no shameful hack is complete wihtout one) to track when our various complications were made.

In 2, we setup the utilitarian large complication and store the creation date, just add more cases to the switch statement for other complication families that you are supporting.

Finally in 3 we check what time the complication that launched the app was created and check which one it was and launch the relavant interface controller.

Limitations #

The code above has a couple of limitations that you may need to work around. First it doesn't take Time Travel into account so if your app supports that each complication may have more than one corresponding datetime. Secondly (though in practice I haven't seen this be an issue) I don't see why two complications couldn't have clashing datetimes, for that you could add a method to ComplicationTimeKeeper that returns the next unique date.

It's Time For Complications #

Apple made much of the value of complications at this years WWDC. Having originally not allowed you to make your own in watchOS 1, to allowing you but telling you its only if you really have something super important that gets updates throughout the day in watchOS 2, now this year they told us we really need to have a complication even if its just an icon to launch your app. It seems they've noticed, as anyone who has worn Apple Watch for any reasonable amount of time will tell you, that complications are the best way to access the functionality of an app. But everything they talked about at WWDC was about having a complication, singular. You can support multiple complication families, but you can only have one of each and they are treated as different views of a single feature, showing more data when you've the room, but not really doing anything different.

Ideally, we'd have the ability to provide multiple complications for each complication family. If that was the case you could have a watch face with each complication slot filled by the same application, each showing something else (the built in world clock complication can already do this, but nothing else) and crucially each performing a different function of your app when they're tapped. I wouldn't be surprised if this is something that is eventually supported in WatchKit, but for now at least we can ugly hack our way to using different complication families to provide different functionality.