Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apple Sample Code for WatchKit Extension Background Refresh

Tags:

I've been searching for the answer to this question for quite a long time, so I think I'm willing to risk some downvotes to post it.

Basically, I want to make the Apple provided sample code for Apple Watch background refresh actually work (link and code below).

I've tried both in the simulator and on an iPhone 6s with an Apple Watch Series 2, and the background tasks are never successfully completed to the point where the time updates. I've tried pinning the watch app to the dock, and I've tried keeping the app in the foreground and sending it to the background, both in the simulator and on the actual watch. I even tried waiting almost a year to see if Xcode or the Apple Watch would receive an update that would make it work.

Has anyone successfully modified the Apple provided code to make it work?

You can download the entire runnable sample project here: WatchBackgroundRefresh: Using WKRefreshBackgroundTask to update WatchKit apps in the background

 /*  Copyright (C) 2016-2017 Apple Inc. All Rights Reserved.  See LICENSE.txt for this sample’s licensing information   Abstract:  The main interface controller.  */  import WatchKit import Foundation   class MainInterfaceController: WKInterfaceController, WKExtensionDelegate, URLSessionDownloadDelegate {     // MARK: Properties      let sampleDownloadURL = URL(string: "http://devstreaming.apple.com/videos/wwdc/2015/802mpzd3nzovlygpbg/802/802_designing_for_apple_watch.pdf?dl=1")!      @IBOutlet var timeDisplayLabel: WKInterfaceLabel!      private let dateFormatter: DateFormatter = {         let formatter = DateFormatter()         formatter.dateStyle = .none         formatter.timeStyle = .long          return formatter     }()      // MARK: WKInterfaceController      override func awake(withContext context: Any?) {         super.awake(withContext: context)          // Configure interface objects here.         WKExtension.shared().delegate = self         updateDateLabel()     }      // MARK: WKExtensionDelegate     func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {         for task : WKRefreshBackgroundTask in backgroundTasks {             print("received background task: ", task)             // only handle these while running in the background             if (WKExtension.shared().applicationState == .background) {                 if task is WKApplicationRefreshBackgroundTask {                     // this task is completed below, our app will then suspend while the download session runs                     print("application task received, start URL session")                     scheduleURLSession()                 }             }             else if let urlTask = task as? WKURLSessionRefreshBackgroundTask {                 let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: urlTask.sessionIdentifier)                 let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)                  print("Rejoining session ", backgroundSession)             }             // make sure to complete all tasks, even ones you don't handle             task.setTaskCompleted()         }     }      // MARK: Snapshot and UI updating      func scheduleSnapshot() {         // fire now, we're ready         let fireDate = Date()         WKExtension.shared().scheduleSnapshotRefresh(withPreferredDate: fireDate, userInfo: nil) { error in             if (error == nil) {                 print("successfully scheduled snapshot.  All background work completed.")             }         }     }      func updateDateLabel() {         let currentDate = Date()         timeDisplayLabel.setText(dateFormatter.string(from: currentDate))     }      // MARK: URLSession handling      func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {         print("NSURLSession finished to url: ", location)         updateDateLabel()         scheduleSnapshot()     }      func scheduleURLSession() {         let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: NSUUID().uuidString)         backgroundConfigObject.sessionSendsLaunchEvents = true         let backgroundSession = URLSession(configuration: backgroundConfigObject)          let downloadTask = backgroundSession.downloadTask(with: sampleDownloadURL)         downloadTask.resume()     }      // MARK: IB actions      @IBAction func ScheduleRefreshButtonTapped() {         // fire in 20 seconds         let fireDate = Date(timeIntervalSinceNow: 20.0)         // optional, any SecureCoding compliant data can be passed here         let userInfo = ["reason" : "background update"] as NSDictionary          WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: fireDate, userInfo: userInfo) { (error) in             if (error == nil) {                 print("successfully scheduled background task, use the crown to send the app to the background and wait for handle:BackgroundTasks to fire.")             }         }     }  } 

The following is output when run on the simulator. Similar output (but not necessarily exactly the same) when running in other configurations:

successfully scheduled background task, use the crown to send the app to the background and wait for handle:BackgroundTasks to fire. received background task:  <WKSnapshotRefreshBackgroundTask: 0x7b019030> received background task:  <WKApplicationRefreshBackgroundTask: 0x7a711290> application task received, start URL session 
like image 411
PerpetualStudent Avatar asked Sep 07 '17 01:09

PerpetualStudent


People also ask

What is a WatchKit extension?

A WatchKit Extension process that executes the watch app's application logic. This is bundled with your Watch app and runs on the Apple Watch. A Watch app that displays user interface elements and handles navigation.

What is background app refresh in Javascript?

What is Background App Refresh? Background App Refresh allows the apps on your iPhone to automatically search for new information even if they're not directly running on the screen. On Android devices, the background refresh function allows background data usage, and it works the same way.

Should I turn background app refresh off?

As far as which apps need background app refresh, that's up to your preferences. Generally, you should keep it enabled for any apps you use frequently and disable it for apps you rarely open. Thankfully, both Android and iOS let you turn off and tweak background app refresh.

How can we handle background operations iOS?

Use the BackgroundTasks framework to keep your app content up to date and run tasks requiring minutes to complete while your app is in the background. Longer tasks can optionally require a powered device and network connectivity. Register launch handlers for tasks when the app launches and schedule them as required.


1 Answers

For anyone who may find this, there were 2 problems that I saw, both with the URLSession scheduling. With these changes, I think the Apple sample code actually works, at least on the simulator.

-The sampleDownloadURL needs to be secure, so a URL with HTTPS is necessary. This one works: https://api.weather.gov/points/42.3584,-71.0598/forecast

-It looks to me like the delegate for the URLSession was never set to self, so the following change fixed that:

let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil) 

The following working (although less complete) code was very helpful: What's New in watchOS 3: Background Tasks

Edit: One more issue that may occur is that the tasks are completed immediately after they are received. In practice, they should be saved (in a local variable, for instance) and then completed after all processing for that task is complete. For this code, I think that means hanging onto the WKApplicationRefreshBackgroundTask and not calling setTaskCompleted() on it until right after the call to scheduleSnapshot.

like image 183
PerpetualStudent Avatar answered Nov 14 '22 01:11

PerpetualStudent