I am using the new iOS13 background task framework, with the implementation of a BGAppRefreshTask type. I don't know what I'm doing wrong as I've tried many different approaches, but the background task never executed more than once I want to update the user's location to a server at constant intervals, I enabled background location updates and background fetch capabilities. I tried the method
performFetchWithCompletionHandler
But its depreciated in iOS 13, and I'm testing in an iPhone 11 where it never works except when I run it from Debug > Simulate background fetch Other than that I've tried this code which I saw in WWDC 2019
Here it is
App Delegate :
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
LocationHelper.shared.locationManager = CLLocationManager()
if #available(iOS 13.0, *) {
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.testApp.updateLocation", using: nil) { task in
//This task is cast with processing request (BGProcessingTask)
self.handleLocationUpdateTask(task: task as! BGAppRefreshTask)
}
self.registerLocalNotification()
} else {
// Fallback on earlier versions
}
}
func applicationDidEnterBackground(_ application: UIApplication) {
if #available(iOS 13.0, *) {
cancelAllPendingBGTask()
scheduleLocationUpdate()
} else {
// Fallback on earlier versions
}
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// For iOS < 13
// fetch data from internet now
if #available(iOS 13.0, *) {
}
else {
LocationHelper.shared.locationManager.startMonitoringSignificantLocationChanges()
fetchSomeData(completion: { (bool) in
if bool {
LocationHelper.shared.locationManager.stopMonitoringSignificantLocationChanges()
completionHandler(.newData)
} else {
LocationHelper.shared.locationManager.stopMonitoringSignificantLocationChanges()
completionHandler(.failed)
}
})
}
}
func registerLocalNotification() {
let notificationCenter = UNUserNotificationCenter.current()
let options: UNAuthorizationOptions = [.alert, .sound, .badge]
notificationCenter.requestAuthorization(options: options) {
(didAllow, error) in
if !didAllow {
print("User has declined notifications")
}
}
}
//MARK: Register BackGround Tasks
@available(iOS 13.0, *)
func scheduleLocalNotification() {
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.delegate = self
notificationCenter.getNotificationSettings { (settings) in
if settings.authorizationStatus == .authorized {
self.fireNotification()
}
}
}
@available(iOS 13.0, *)
func scheduleLocationUpdate() {
let request = BGAppRefreshTaskRequest(identifier: "com.testApp.updateLocation") // BGProcessingTaskRequest(identifier: "com.testApp.updateLocation")
request.earliestBeginDate = Date(timeIntervalSinceNow: 10) // fetch data after 10 secs.
//Note :: EarliestBeginDate should not be set to too far into the future.
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule image fetch: (error)")
}
}
@available(iOS 13.0, *)
func handleLocationUpdateTask(task: BGAppRefreshTask) {
scheduleLocationUpdate()
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
let lastOperation = queue.operations.last!
task.expirationHandler = {
queue.cancelAllOperations()
}
lastOperation.completionBlock = {
task.setTaskCompleted(success: !lastOperation.isCancelled)
}
queue.addOperation {
self.fetchSomeData { (bool) in
self.scheduleLocalNotification()
task.setTaskCompleted(success: bool)
}
}
}
@available(iOS 13.0, *)
func cancelAllPendingBGTask() {
BGTaskScheduler.shared.cancelAllTaskRequests()
}
func fireNotification() {
// Create Notification Content
let notificationContent = UNMutableNotificationContent()
// Configure Notification Content
notificationContent.title = Constants.Values.appName.rawValue
notificationContent.body = "The app just tried to run a background task on \(UserDefaults.standard.string(forKey: "LastBackgroundFetch") ?? "NEVER")"
notificationContent.badge = 1
if #available(iOS 12.0, *) {
notificationContent.sound = .defaultCriticalSound(withAudioVolume: .infinity)
} else {
// Fallback on earlier versions
}
// Add Trigger
let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 1.0, repeats: false)
// Create Notification Request
let notificationRequest = UNNotificationRequest(identifier: "local_notification", content: notificationContent, trigger: notificationTrigger)
UNUserNotificationCenter.current().delegate = self
// Add Request to User Notification Center
UNUserNotificationCenter.current().add(notificationRequest) { (error) in
if let error = error {
print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
}
}
}
func fetchSomeData(completion: @escaping (Bool) -> () ) {
LocationHelper.shared.updateUser { (bool) in
let date = DateFormatter.sharedDateFormatter.string(from: Date())
UserDefaults.standard.set(date, forKey: "LastBackgroundFetch")
completion(bool)
}
}
In this code, I was able to test the background mode as per apple's documentation by pausing the app and then typing this code in the debugger and then resuming
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.FindFamily.updateLocation"]
even after this, the code only ran once and then stopped, when actually it should keep calling itself as scheduleLocationUpdate()
is called inside the function handleLocationUpdateTask(task:)
, which would be called after every 10 secs (approx.)
I found the problem I was using background processes to achieve something which was possible without it I enabled background location updates in the capabilities section and it started giving me continuous updates in the background.