I have a particular use case where I want to be able to start location updates when the app is already in the background. Specifically, I want to be able to press a button on my Pebble watchapp, and that causes my companion iOS app to begin location updates.
I am able to successfully start the location updates from my Pebble only if the iOS app is either in the foreground or has just entered the background within the past few seconds (like 5?). After a few seconds has passed with the app in the background, I can no longer start the updates. I know that the updates haven't started, since the backgroundTimeRemaining starts counting down to 0 (and if the updates did start, it stays at the max value). I also have the blue bar at the top that shows when location updates are on ("_____ is Using Your Location") and that also fails to show up after this weird ~5 second threshold has passed.
I've looked at almost all of the related questions on SO, and I've already tried basically everything that's been suggested, such as starting a background task right before starting the location updates. I've also tried delaying the start of the location updates by a couple seconds after starting the background task, and that doesn't work either.
The strange thing is, I also have an Apple Watch app that does the same things as the Pebble app, and the Apple Watch is able to start the location updates without fail all the time. Maybe the Apple Watch can do something special where it unsuspends the iOS app?
This is on iOS 9.
Any kind of help is appreciated, I'll try anything that's suggested.
Update: I put together a quick sample app that demonstrates the issue. The code is posted below, or you can download the project from GitHub: https://github.com/JessicaYeh/BackgroundLocationTest
If you try it out, you can see that setting the delay to 2 seconds works, but when the delay is longer (like 10 seconds), the updates don't start.
//
// AppDelegate.swift
// BackgroundLocationTest
//
// Created by Jessica Yeh on 2/18/16.
// Copyright © 2016 Yeh. All rights reserved.
//
import UIKit
import CoreLocation
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let locationManager = CLLocationManager()
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
var timer: NSTimer?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
debugPrint(__FUNCTION__)
// Setup location manager
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
return true
}
@objc func checkBackgroundTimeRemaining() {
debugPrint(UIApplication.sharedApplication().backgroundTimeRemaining)
}
@objc func endBackgroundTask() {
if self.backgroundTask != UIBackgroundTaskInvalid {
UIApplication.sharedApplication().endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
}
}
func applicationWillResignActive(application: UIApplication) {
debugPrint(__FUNCTION__)
}
func applicationDidEnterBackground(application: UIApplication) {
debugPrint(__FUNCTION__)
// Cancel old timer and background task
timer?.invalidate()
endBackgroundTask()
// Start a new background task
backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler {
self.endBackgroundTask()
}
// Start updating location
// let delaySec = 2.0 // successfully starts location update
let delaySec = 10.0 // doesn't start location update
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delaySec * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue(), {
debugPrint("Attempting to start updating location")
self.locationManager.startUpdatingLocation()
})
// Keep checking every 3 sec how much background time app has left
timer = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: Selector("checkBackgroundTimeRemaining"), userInfo: nil, repeats: true)
}
func applicationWillEnterForeground(application: UIApplication) {
debugPrint(__FUNCTION__)
}
func applicationDidBecomeActive(application: UIApplication) {
debugPrint(__FUNCTION__)
}
func applicationWillTerminate(application: UIApplication) {
debugPrint(__FUNCTION__)
}
}
extension AppDelegate: CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
debugPrint(__FUNCTION__)
}
}
Well, I don't exactly what you tried, but make sure you've turned on the Location updates on your target's Capabilities (Capabilities -> Background Modes) and also, for iOS 9.0, add this:
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"9.0")) {
self.locationManager.allowsBackgroundLocationUpdates = YES;
}
and change the line:
locationManager.requestWhenInUseAuthorization()
to:
locationManager.requestAlwaysAuthorization().
Good luck!