How to get data from iOS app to Watch app in a background task?

Hi. I have an iOS app with a Watch app that works generally well, but I can't seem to get the Watch app to update with data from the iOS app after it's been suspended/killed by watchOS 4.


The Watch app has complications, which are self-updating (the data is based on dates, so no transfer from iOS - Watch is necessary). The data in the Watch app is richer than that shown in the complications, and includes images etc.


The moment watchOS kills my app when it hasn't been running for a while, it wipes the various bits of data it had in memory, so when I launch the app again, it says there's no data, then spends a few seconds retrieving data from the iOS app, which sometimes works and sometimes doesn't. This looks bad on the Watch, and it's missing any updates the user may have performed in the iOS app.


I should be able to launch the Watch app and see the most up to date information.


I'd like to avoid this situation by using a background task in the Watch app to wake up the iOS app and retrieve data at regular intervals, but all the examples I see use a URLSession within the

scheduleBackgroundRefresh(withPreferredDate:userInfo:scheduledCompletion:)
method. I don't get my data from a server; I get it from the iOS app, and it's all user-created, so there's no server involved anyway.


https://developer.apple.com/documentation/watchkit/wkapplicationrefreshbackgroundtask


So, how do I get the Watch app to wake up the iOS app at regular intervals, and retrieve its data so that the Watch app is up to date?


Some code:

In the init() method of the Watch app's ExtensionDelegate.m:

// Schedule a background refresh task
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
[offsetComponents setHour:1];
NSDate *refreshDate = [[NSCalendar autoupdatingCurrentCalendar] dateByAddingComponents:offsetComponents toDate:[NSDate date] options:0];
[[WKExtension sharedExtension] scheduleBackgroundRefreshWithPreferredDate:refreshDate userInfo:@{} scheduledCompletion:^(NSError * _Nullable error) {
  if(error == nil) {
    // Successfully scheduled

  } else {
    NSLog(@"Error scheduling background refresh task for date: %@", refreshDate);
  }
}];

This should tell the Watch app to schedule a background refresh task for an hour from now.


The handleBackgroundTasks() method in the Watch app's ExtensionDelegate.m:

- (void)handleBackgroundTasks:(NSSet *)backgroundTasks
{
  for(WKRefreshBackgroundTask *currentTask in backgroundTasks) {
    NSLog(@"currentTask class = '%@'", [currentTask class]);
    NSLog(@"currentTask.userInfo = %@", currentTask.userInfo);

    if([[WKExtension sharedExtension] applicationState] == WKApplicationStateBackground) {
      if([currentTask isKindOfClass:[WKApplicationRefreshBackgroundTask class]]) {

        // Is it here that I get the data from the iOS app? HOW?

        // Schedule another background refresh task
        NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
        [offsetComponents setHour:1];
        NSDate *refreshDate = [[NSCalendar autoupdatingCurrentCalendar] dateByAddingComponents:offsetComponents toDate:[NSDate date] options:0];
        [[WKExtension sharedExtension] scheduleBackgroundRefreshWithPreferredDate:refreshDate userInfo:@{} scheduledCompletion:^(NSError * _Nullable error) {
          if(error == nil) {
            // Successfully scheduled

          } else {
            NSLog(@"Error scheduling background refresh task for date: %@", refreshDate);
          }
        }];
        [currentTask setTaskCompletedWithSnapshot:false];
      }
      [currentTask setTaskCompletedWithSnapshot:true];
    }
  }
}

How do I get the data from the iOS app? The iOS app may be (is more often than not) in the background.


Thanks.

Accepted Reply

You need to use WatchConnectivity framework for that. In your background task, you can invoke WCSession.sendMessage and process the response in the callback handler. Just make sure you process the data as quick as possible, as background session time is limited (WatchOS kills it after about 15 seconds).


Do NOT call your setTaskCompleted() until after you process the response in the callback (or handle an error).

Replies

You need to use WatchConnectivity framework for that. In your background task, you can invoke WCSession.sendMessage and process the response in the callback handler. Just make sure you process the data as quick as possible, as background session time is limited (WatchOS kills it after about 15 seconds).


Do NOT call your setTaskCompleted() until after you process the response in the callback (or handle an error).

I might have got this working (without any help from Apple's documentation on background tasks) Let's see. If I have, I'll post my code here so others don't have to spend a fortnight trying.