I have a complication that works on Simulator, but doesn't work on an actual device when I TestFlight it to test on an actual device (and for clarity sake if there is any confusion, I'm not talking about debugging via device, but just testing if it works on a device).
Specifically, on the Watch device:
getPlaceholderTemplateForComplication
works on Simulator too)...getCurrentTimelineEntryForComnplication
works on
Simulator)...getTimelineEntriesForComplication:afterDate
works on Simulator)...Info
on iPhone:
game.duel = playoffs[“Duel”] as! String
game.tv = playoffs[“TV”] as! String
game.td = playoffs[“TD”] as! AnyObject
let dictionary = [“Duel” : game.duel, “TV” : game.tv, “TD” : game.td]
let transferComplication = WCSession.defaultSession().transferCurrentComplicationUserInfo(dictionary)
ExtensionDelegate
in WatchKit Extension:
var duelArray = [String]()
var tvArray = [String]()
var tdArray = [NSDate]()
let defaults = NSUserDefaults.standardUserDefaults()
if let duel = userInfo[“Duel”] as? String, let tv = userInfo[“TV”] as? String, let td = userInfo[“TD”] as? String {
duelArray.append(duel)
tvArray.append(tv)
tdArray.append(td as! NSDate)
defaults.setObject(duelArray, forKey: “DuelSaved”)
defaults.setObject(tvArray, forKey: "TVSaved”)
defaults.setObject(tdArray, forKey: "TDSaved”)
}
ComplicationController
in WatchKit Extension:
func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {
switch complication.family {
case .ModularLarge:
let mlTemplate = CLKComplicationTemplateModularLargeStandardBody()
if let currentDuel = defaults.arrayForKey(“DuelSaved”) as? [String] {
let firstDuel = currentDuel[0]
let headerTextProvider = CLKSimpleTextProvider(text: firstDuel)
mlTemplate.headerTextProvider = headerTextProvider
} else {
// …
}
if let currentTV = defaults.arrayForKey(“TVSaved”) as? [String] {
let firstTV = currentTV[0]
let body1TextProvider = CLKSimpleTextProvider(text: firstTV)
mlTemplate.body1TextProvider = body1TextProvider
} else {
// …
}
if let currentTD = defaults.arrayForKey("TDSaved"){
let firstTD = currentTD[0]
let body2TextProvider = CLKTimeTextProvider(date: firstTD as! NSDate)
mlTemplate.body2TextProvider = body2TextProvider
} else {
// …
}
let timelineEntry = CLKComplicationTimelineEntry(date: NSDate(), complicationTemplate: mlTemplate)
handler(timelineEntry)
// …
}
func getTimelineEntriesForComplication(complication: CLKComplication, afterDate date: NSDate, limit: Int, withHandler handler: (([CLKComplicationTimelineEntry]?) -> Void)) {
let headerArray = defaults.arrayForKey(“DuelSaved”)
let body1Array = defaults.arrayForKey("TVSaved")
let body2Array = defaults.arrayForKey("TDSaved")
guard let headers = headerArray, texts = body1Array, dates = body2Array else { return }
var entries = [CLKComplicationTimelineEntry]()
for (index, header) in headers.enumerate() {
let text = texts[index]
let date1 = dates[index]
let headerTextProvider = CLKSimpleTextProvider(text: header as! String, shortText: headerShort as? String)
let body1TextProvider = CLKSimpleTextProvider(text: text as! String)
let timeTextProvider = CLKTimeTextProvider(date: date1 as! NSDate)
let template = CLKComplicationTemplateModularLargeStandardBody()
template.headerTextProvider = headerTextProvider
template.body1TextProvider = body1TextProvider
template.body2TextProvider = timeTextProvider
switch complication.family {
case .ModularLarge:
let timelineEntry = CLKComplicationTimelineEntry(date: date1 as! NSDate, complicationTemplate: template)
entries.append(timelineEntry)
// …
}
func requestedUpdateDidBegin() {
let server=CLKComplicationServer.sharedInstance()
for comp in (server.activeComplications) {
server.reloadTimelineForComplication(comp)
}
}
This is the flow of the data:
transferCurrentComplicationUserInfo
passes data to the Watch ExtensionDelegate
wherein the data is saved in NSUserDefaults
. ComplicationController
then pulls its initial data from NSUserDefaults
.
At first glance:
This doesn't appear to be working code, as you'd be seeing a lot of Xcode fix-it errors about "Unicode curly quote found, ...".
Please avoid force downcasting with as!
as it can fail and your code will crash. You've got lots of unnecessary type casting taking place. As I mentioned before, you should be typing your variables to allow the compiler to catch any programmer errors.
For example, if your dictionary's keys and values are both strings, then safely type it as:
var playoffs: [String: String]
Your extension delegate code may conditionally fail, if the as?
downcast is not possible (because you passed something different than what you expected to receive). Make sure you're passing the types of values that you expect, or that whole block won't run. You can easily check that in the debugger by setting a breakpoint and stepping through that code.
You also need to explicitly update your complication, once the info is received.
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let ... { // Retrieve values from dictionary
// Update complication
let complicationServer = CLKComplicationServer.sharedInstance()
guard let activeComplications = complicationServer.activeComplications else { // watchOS 2.2
return
}
for complication in activeComplications {
complicationServer.reloadTimelineForComplication(complication)
}
}
}
It's really convoluted what you're doing with the arrays and NSUserDefaults
. While it's perfectly appropriate to persist data between launches, NSUserDefaults
is never meant to be a way to "pass" details from one part of your code to another.
Your complication data source should get its data from a model or data manager, instead of from NSUserDefaults
.
The getCurrentTimelineEntryForComplication
if let ... { } else {
code makes no sense. If you didn't get an array of strings, what do you expect to do in the else
block?
You can also prepare your data before the switch statement, to make your code more readable and compact, like so:
func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {
// Call the handler with the current timeline entry
let recentData = DataManager.sharedManager.complicationData ?? "???"
let template: CLKComplicationTemplate?
let simpleTextProvider = CLKSimpleTextProvider(text: recentData)
switch complication.family {
case .ModularLarge:
let modularLargeTemplate = CLKComplicationTemplateModularLargeStandardBody()
modularLargeTemplate.headerTextProvider = CLKSimpleTextProvider(text: "Update Complication", shortText: "Update")
modularLargeTemplate.body1TextProvider = simpleTextProvider
template = modularLargeTemplate
case .UtilitarianLarge:
let utilitarianLargeTemplate = CLKComplicationTemplateUtilitarianLargeFlat()
utilitarianLargeTemplate.textProvider = simpleTextProvider
template = utilitarianLargeTemplate
case .CircularSmall:
let circularSmallTemplate = CLKComplicationTemplateCircularSmallSimpleText()
circularSmallTemplate.textProvider = simpleTextProvider
template = circularSmallTemplate
case .ModularSmall:
let modularSmallTemplate = CLKComplicationTemplateModularSmallSimpleText()
modularSmallTemplate.textProvider = simpleTextProvider
template = modularSmallTemplate
case .UtilitarianSmall:
let utilitarianSmallTemplate = CLKComplicationTemplateUtilitarianSmallFlat()
utilitarianSmallTemplate.textProvider = simpleTextProvider
template = utilitarianSmallTemplate
}
let timelineEntry = CLKComplicationTimelineEntry(date: NSDate(), complicationTemplate: template!)
handler(timelineEntry)
}
The likely issue is that ClockKit
asked for complication data, well before your extension even received it, so your complication data source had no data to provide, and no entries appear.
Even though something happens to work on the simulator, it doesn't mean your code is robust to also work on the actual device. There are all sorts of differences that can account for why it won't work on the real hardware, which is why you absolutely need to debug interactively on the device. It will help you realize why your code isn't working as intended.
Ideally, you should be doing this type of interactive debugging and solving those other issues before coming here, so you can ask a very specific question with a minimal working block of code. Questions which require someone to broadly debug your code really aren't useful to others in general.