7

(It looks like this issue has been encountered by others in previous weeks, but there haven't been any solutions that I've found.)

I'm trying to do a really basic thing: Get data from either my iOS app or my Watch app to my Complication Controller.

I am turning out to be way less capable of getting this done than I thought. watchOS 2 Transition Guide indicates that I should "[fetch] the needed data from the extension delegate" using the following code:

ExtensionDelegate* myDelegate = [[WKExtension sharedExtension] delegate];  
NSDictionary* data = [myDelegate.myComplicationData objectForKey:ComplicationCurrentEntry];

Great. Except, I haven't been able to figure out how to get this to work on the extension side. Though even more importantly, I can't seem to even get the extension delegate code to run at all from a complication controller launch. When I run the complication, I get this message: "Extension received request to wake up for complication support." However, none of the code within any of the extension delegate's methods seems to run. I've also set breakpoints within every method and none of those breakpoints are hit.

It also looks like "transferCurrentComplicationUserInfo:" is also suggested to be used for complication updates, though it's unclear precisely how it's used. As much as I've gathered, it's used to wake up the watch extension so that ExtensionDelegate can store the new data for the next time the complication controller runs, but due to the previous issue I haven't been able to confirm.

I've got one maybe workaround (pinging the server from the complication controller and hoping that session variables persist so I can send relevant data), but there's every chance that if I can't get this worked out my complication work will be hosed. Any help here would be tremendous.

By the way, here's the code I have for "getCurrentTimelineEntryForComplication", if that's helpful at all.

- (void)getCurrentTimelineEntryForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimelineEntry * __nullable))handler {  
    NSDate* entryDate = [NSDate date];  

    ExtensionDelegate* myDelegate = [[WKExtension sharedExtension] delegate];  
    NSString* data = [myDelegate.complicationData objectForKey:@"meow"];  
    NSLog(@"complication data: %@", data);  

    CLKComplicationTimelineEntry* entry = [self getTimelineEntry:@"2015-08-25 00:19:42" entryDate:entryDate complication:complication];  

    handler(entry);  
}
Sergio Prado
  • 1,197
  • 2
  • 16
  • 33
  • where in the WatchKit extension do you active the WCSession? What object do you set as its delegate? – ccjensen Sep 02 '15 at 16:10
  • @ccjensen having the same exact problem, did you guys ever figure anything out? – SRMR Feb 21 '16 at 22:20
  • I never got a reply to my questions so I don't think this was ever figured out – ccjensen Feb 21 '16 at 23:30
  • @ccjensen I'm having the same exact problem http://stackoverflow.com/questions/35542729/call-extensiondelegate-to-create-refresh-data-for-complication ... I activate `WCSession` in my `ExtensionDelegate`, any ideas? – SRMR Feb 22 '16 at 00:14

2 Answers2

8

I've been working with Complications in WatchOS2 since Xcode 7 Beta 4. I'm now on the latest, Xcode Beta 6. I've had a number of issues as in both Beta versions running on the Watch, running on the iPhone then installing to the Watch, and running on the simulator frequently give false negatives due to what appears to be buggy APIs and OS releases. I have been able to get data to show on complications in the following way.

  • Ensure that your primary Interface Controller implements the WCSessionDelegate protocol.
  • Implement both the didReceiveMessage and didReceiveApplicationContext methods in your Interface Controller.
  • In your iPhone app, attempt to send a message using the WCSession to the Watch.
  • If the message fails to send from the iPhone app, send the application context.
  • Back in the Interface Controller, when you receive a message -or- a context, update the values in your Extension Delegate.
  • Still in the Interface Controller and still after receiving a message -or- context, get a handle to the CLKComplicationServer and for each complication in activeComplications call reloadTimelineForComplication.
  • In your Complication Controller's getCurrentTimelineEntryForComplication grab the data you set in the Extension Delegate and set the values in your CLKComplicationTimelineEntry.
  • This will work usually when the App is already open on the Watch, the app is still resident in memory, but backgrounded on the Watch, or you start the app and their is context waiting which it consumes.
  • I have not been able to get the historical timeline entries to function (or the future ones). Nor, I have I been able to get the timeline to update independently of the Watch app.

If you are having trouble, here are some debugging things to try. As I stated above, the API and OS appears to be very buggy. The steps below do work (sometimes).

  • In the sim, use the Reset all Settings option on both the iPhone and Watch sim.
  • On the device, restart the Watch. If necessary, unpair and repair the Watch, although this takes a really long time to do.
  • On the iPhone, delete the app (which will also delete the Watch app if installed) and reinstall.

I hope that helps!

Justin

Justin Domnitz
  • 3,217
  • 27
  • 34
  • Ever run into a problem on the `ComplicationController` side where you can't seem to get the data you set in the `ExtensionDelegate` via `WCSession:` to be pulled? – SRMR Feb 22 '16 at 00:15
  • Yes, I ran into this when I failed to respond to getTimelineEntriesForComplication (beforeDate and afterDate) correctly. Initially, I didn't understand what these methods were intended for and I was filling all requested timeline entries with the same data. This kept the Complication Controller from getting any new data for the time span covered by these timeline entries. To fix this, make sure you are responding to getTimelineEntriesForComplication with only the know set of good timeline entries and call handler(nil) when done. I hope that helps! – Justin Domnitz Feb 22 '16 at 14:17
  • Ahhh, I think that makes sense. What made it click for you? Did you come across an article/tutorial/doc that made that clearer for you? – SRMR Feb 22 '16 at 14:23
  • 1
    Figured this out through trial and error and brute force. There's very limited info out there on how to properly use and implement Complications, as I'm sure you found! – Justin Domnitz Feb 22 '16 at 14:24
  • Yeah I'm trying my hardest, figure out a little more little by little (I think), but still stuck: http://stackoverflow.com/questions/35542729/call-extensiondelegate-to-create-refresh-data-for-complication/35545557#35545557 – SRMR Feb 22 '16 at 14:55
-1

In order to make the ComplicationController respond to WCSession activity you must make the controller conform to WCSessionDelegate, then manage didReceiveUserInfo from within the ComplicationController. The ExtensionDelegate is not woken up for these updates when in the background. You can still update your delegate from the controller if necessary.

Also, as of right now, the simulator does not send transferCurrentComplicationUserInfo to the watch sim, you have to test on devices.

markdrayton
  • 65
  • 1
  • 7
  • I've never experienced a problem with `getTimelineEntriesForComplication` on the simulator; it supports time travel. Your approach should be avoided for these reasons: `ComplicationController` is a data *source* which gets instantiated on demand by the complication server. It isn't meant to receive data or be a repository for data. Apple recommends that you separate fetch and data manager responsibilities into distinct modules. This is especially significant when [you consider complications from the perspective of a watchOS 3 background task](http://stackoverflow.com/a/37867082/4151918). –  Jul 04 '16 at 17:32
  • the issue is that transferCurrentComplicationUserInfo is not reaching the ComplicationController as the WCsession is activated by the ExtensionDelegate (and there can only be one WCSession delegate). The best solution is to handle the session business with a singleton that can be shared across the watch app – markdrayton Jul 04 '16 at 21:54
  • natasha describes the technique here https://www.natashatherobot.com/watchconnectivity-say-hello-to-wcsession/ – markdrayton Jul 04 '16 at 21:58
  • The misunderstanding is that the transfer is meant to go to the complication controller. No, that's not the design of a watch connectivity session. What actually happens is that the user info gets transferred **between the phone and the watch extension**. It is then up to the extension to a) store the complication user info in a model or data manager, and b) get the complication to reload the timeline (which uses the new info that now resides with the model or data manager). A session transfer is never meant to communicate directly with a complication controller. –  Jul 04 '16 at 22:02
  • You definitely can use a singleton for the session manager, but the complication controller needs to get at the data, not at the session. That's the distinction (and fundamental reason for splitting up the "network" code into a network part and a data manager part). –  Jul 04 '16 at 22:05
  • Thats what you would reasonably expect, however the problem is that the transfer from transferCurrentComplicationUserInfo doesn't happen if the watch extension is not currently active (as when looking at the watch face). I resolved this by creating a singleton to manage the WCSession which is then shared by each part of the watch app, making the WCSession accessible to the ComplicationController. Then updates can be processed etc. – markdrayton Jul 04 '16 at 22:13
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/116413/discussion-between-petahchristian-and-markdrayton). –  Jul 04 '16 at 22:14