Get Data to Complication: ExtensionDelegate not Called - apple-watch

(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);
}

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

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.

Related

Refresh watchos app based on internet connectivity

I have found a great description of the process for refreshing both the watchapp UI and its complications here. That works for me, even though sometimes the UI is only updated when I actually bring the app to the foreground (but that's another issue).
what I have been doing so far is this:
1 - I schedule a background refresh;
2 - When the background refresh task is called, I schedule a background downloadTask;
3 - After completion, the downloadTask call its delegate method (didFinishDownloadingTo), where I call another method to update the UI, reload my complications with new data and to schedule a snapshot refresh and another background refresh.
This is almost working fine (with the ocasional problem I mentioned above). But I put my watch on flight mode sometimes. What happens here is that the error delegate method is called, and so I schedule another app refresh for about 1 hour. If still in flight mode, it will request another refresh in one hour, and so on.
The problem with this approach is that when I turn off the flight mode, the app takes a long time to update its UI, depending on when the next update is scheduled.
Can someone suggest a better approach to get new data as soon as one gets internet connectivity back?
At the moment (watchOS 3.2) there is no framework in watchOS that could tell you when the device connects to the internet. However, you could leverage the Reachability framework in your iOS app and use the WatchConnectivity framework to signal your Watch app when it can refresh its UI.

What communication to use, updateApplicationContext or sendMessage for constant data sync

I have an iOS shopping list app, where items are added and displayed in a tableView. I want to create a Watch App Extension but I’m thinking of what is the best function call to use in this case, the updateApplicationContext(:) method or the sendMessage(:replyHandler:errorHandler:) method I was reading the documentation but I’m a little confused since both seem to work.
Here is the functionality I’m expecting to have…
What I want is to be able to add items in the iOS app even if the Watch app is Off, which is normal behavior, BUT I want the Watch app to update with whatever is in the tableView (in iOS) as soon as it is turned on and even if the iPhone is not On at the time the Watch is turned on.
In other words, I want the data in the iOS app to always be in sync with the Watch app.
Which is the best function call to use in this case, the updateApplicationContext(_:) method or the sendMessage(_:replyHandler:errorHandler:) method?
Thanks
As for me, I would use updateApplicationContext(_:) since you would want to update it in real time or in the background as it gets connected.
as for this sendMessage(_:replyHandler:errorHandler:) The cons is The isReachable property must currently be true for these methods to succeed. so you might get a slight delay to update your UI until it is reachable and ask for updates.

iOS NSNotification - how to safely ignore when coming out of suspended

Sometimes, when the app I'm working on comes out of suspended mode, I need to make sure it does not respond to the notifications iOS has saved for it while the app was suspended. Where's the right place to perform this cancellation where I can be sure my response code won't run?
I'm hoping that putting removeObserver is applicationWillEnterForeground would do the trick, but I couldn't find anything about this in the NSNotification doc.
Edit:
Let me add the details of the scenario:
The app I'm working on checks for changes to calendar events for the range of the next 7 days. It refreshes it's data (requeries eventStore) at two times:
1. When an NSNotification comes from the iPhone calendar about changes.
2. The first time the app is able to on a new day, because the 7 day range has changed.
The design challenge I'm having is the case where both scenarios are true at the same time (the app returns from being suspended and it's a new day, and calendar events were modified while the app was suspended ). I only want to requery the event store one time in that case. My thought is to, in applicationWillEnterForeground, on the case that it's the first launch of the day, tell the app to ignore or drop any notifications.
I'm getting the impression my assumptions about how NSNotifications would work in this case might be off. Any guidance would be great!
Why not remove the observer from the notification centre in -applicationDidEnterBackground:?
Then you can start observing again in -applicationWillEnterForeground:.
What NSNotifications come in while you’re suspended? Are you running a background task?
I can’t really think of a case where not responding to an NSNotification would be a good idea. They are effectively messages like any other, just with a different routing mechanism.
UILocalNotification would be a different issue.

How to run a ~30sec process in the background every hour (iphone app)

I have an iphone app that has a 30second process that does some network IO. Basically, while the app is in the background, i want this process to run every hour (actually once a day, but if it fails i want it to re-run in an hours time).
With the background features of ios 4, is this possible? If so, how? What are the limitations that i'll come up against?
Thanks so much!
Take a look at Apple's documentation about running code in the background.
http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/BackgroundExecution/BackgroundExecution.html
There are few different ways of approaching backgrounded tasks. The only apps that can have fully backgrounded processes are "audio", "voip" and "location" apps, and this needs to be declared in the Info.plist.
If your app is not of this type, you'll probably find it difficult to do what you want easily. There are methods which allow you to keep your app alive in the background for a finite period of time (also at that link), but eventually your app will be shut down.
Local Notifications will only prompt the user to open the app - do you really want to have an alert pop-up on the phone every 30 seconds?
I was making some kind of similar research, have a look at this SO answer in case you didn't manage to find it before. Applications like DataMan or Data Usage must have some sort of periodic code execution in the background, so I'm not 100% convinced that what you're asking for is impossible..
I believe that Using Local notifications will help....
check following....
http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/IPhoneOSClientImp/IPhoneOSClientImp.html#//apple_ref/doc/uid/TP40008194-CH103-SW1
An application can create and schedule a local notification, and the operating system then delivers it at the schedule date and time. If it delivers it when the application is not active in the foreground, it displays an alert, badges the application icon, or plays a sound—whatever is specified in the UILocalNotification object. If the application is running in the foreground, there is no alert, badging, or sound; instead, the application:didReceiveLocalNotification: method is called if the delegate implements it.
The delegate can inspect the properties of the notification and, if the notification includes custom data in its userInfo dictionary, it can access that data and process it accordingly. On the other hand, if the local notification only badges the application icon, and the user in response launches the application, the application:didFinishLaunchingWithOptions: method is invoked, but no UILocalNotification object is included in the options dictionary.

When does an iphone application receive didChangeAuthorizationStatus: delegate call?

I have a question about CLLocationManagerDelegate. The documentation says if the user changes the settings for your location services (in the iPhone's Settings.app) then your app is supposed to receive an didChangeAuthorizationStatus: message to the delegate. My question is, when would this happen?
If the user changed the setting, it means they are in the settings app, and your app is either backgrounded or not running at all, so in the former case, when would your app's CLLocationManager delegate get the didChangeAuthorizationStatus: call?
I just ran across this method an hour ago, so good timing on the question!
It looks like in my case this method gets called:
When the app becomes active.
On allowing Location Services for the app on the initial startup of the app.
I wrote a quick test app you can find here:
https://github.com/mharper/LocationServices
It simply logs the authorization status whenever the method gets called.
This delegate method will be called when:
The first time you init a CLLocationManager instance
1.1. If that's the first time your App launch on device, you'll receive state kCLAuthorizationStatusNotDetermined before user see the [Allow/Don't Allow] UIAlertView. (At this time, you can find that the UISwitch of your App in Settings - Privacy - Location Service is turn off or not shown.
1.2. When user re-launch your App. Because decision has been made in previous launch, so you can retrieve the state.
The first time after user made decision. This is obvious. After you call the startUpdatingLocation/startUpdatingHeading, then iOS show the UIAlertView.
In your answer's situation, user made changes in Settings, if your App is running in background, you'll receive the new state when your App become active. Otherwise, reference 1.2.
If your app is running in the background or not at all it will be called the moment the user returns to your application.
I encounter the same issue as well.
my solution is put the request gps authorization code in main loop.
I guess it can also works if you put in another run loop.
I ran into this problem of handling location permission changes correctly recently and did a lot of research and debugging.
If the app is not running at all and the user changes location permissions in Settings, then when you start your app, locationManager:didChangeAuthorizationStatus is called when the location manager is initialized as stated in mharper's answer. This behavior is not in Apple's docs.
If the app is in the background/suspended, I tested it in the simulator and it looks like the delegate function is also called.