Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update user location on demand using silent push notifications? iOS

I want to track users current location only on demand. I need only initial location, not continuos updates. I thought best way to do this would be sending silent notification to user in order to get user's current location once. Receiving silent notification works just fine, but LocationManager doesn't get initial fix on the location. My Location Manager Delegate class instantiates normally but doesn't fire didUpdateLocations function at any point after location manager method startUpdatingLocation() is called. When App starts normally from AppDelegate:didFinishLaunchingWithOptions, LocationManager updates users location without problems.

I'm developing on Xcode 6.3.2 to iOS 8.3

My AppDelegate:DidReceiveRemoteNotification looks like this:

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler handler: (UIBackgroundFetchResult) -> Void) {

    var bgTask = bgManager()
    bgTask.registerBackgroundTask()
    LocManager.locationMngr.startUpdatingLocation() // LocManager is global variable

    let delayInSeconds = 8.0
    let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC)))
    dispatch_after(popTime, dispatch_get_main_queue()) {
            println(LocManager.updated)
            bgTask.endBackgroundTask()
            handler(UIBackgroundFetchResult.NewData)
    }
}

I first tried only with line:

LocManager.locationMngr.startUpdatingLocation()

but when it didn't work I added background task and dispatch_after to make sure my location Manager would get enough time to retrieve first location update before termination.

Here is the LocationManager class:

class LocationManager: NSObject, CLLocationManagerDelegate {
let locationMngr:CLLocationManager
var coord:CLLocationCoordinate2D
var updated:Bool
    override init() {
        locationMngr = CLLocationManager()
        coord = CLLocationCoordinate2D()
        updated = false
        super.init()
        locationMngr.delegate = self
        locationMngr.desiredAccuracy = kCLLocationAccuracyHundredMeters

        locationMngr.startUpdatingLocation() //Should call didUpdateLocations after some time
    }

    //Doesn't get called in AppDelegate remoteNotifications method
    func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!)
    {
        var loc:CLLocation = locations.first as! CLLocation
        self.coord = loc.coordinate
        locationMngr.stopUpdatingLocation()
        updated = true
    }

and Background Manager class:

class bgManager:NSObject{
    var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
    override init(){
        super.init()
    }
    func registerBackgroundTask() {
        backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler {
        [unowned self] in
        self.endBackgroundTask()
        }
    }

    func endBackgroundTask() {
        UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
        backgroundTask = UIBackgroundTaskInvalid
    }
}

JSON of received remote push:

"aps" : {
        "sound" : "",
        "content-available" : 1,
        "priority" : 5
    }

I have all required plist.info keys and background modes for fetch, notifications and location enabled in project Capabilities: Background Modes. plist.info snippet:

<dict>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
        <string>location-services</string>
        <string></string>
        <string></string>
    </array>
    <key>UIBackgroundModes</key>
    <array>
        <string>remote-notification</string>
        <string>fetch</string>
        <string>location</string>
    </array>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>Your location is needed for this app.</string>
    ...

I've browsed SO furiously for similar problem and couldn't find any helping solution including this question:

IOS - Running background task for update user location using swift

Periodic iOS background location updates

Core Location delegate methods not getting called in iOS 8.3 Xcode 6.3.1

Swift: location not being updated when returned from background

like image 223
Toni Nurmi Avatar asked Jul 15 '15 13:07

Toni Nurmi


2 Answers

I came across the same issue and went through the exact steps as OP to no avail. Eventually I figured out that I only had requested CLLocationManager.requestWhenInUseAuthorization() which only provides location when app is in foreground. I had to request CLLocationManager.requestAlwaysAuthorization() in order for app to request location in background. Also make sure you have NSLocationAlwaysUsageDescription entry in Info.plist file. With these changes, I was able to use CLLocationManager.startUpdatingLocation() directly and was able to receive the location updates in background.

like image 86
SS. Avatar answered Sep 30 '22 19:09

SS.


When the app is launched in the background, calling startMonitoringSignificantLocationChanges() before startUpdatingLocation() worked for me.

Remember to call stopMonitoringSignificantLocationChanges() and stopUpdatingLocation() when you are finished.

like image 25
Luis Valdés Avatar answered Sep 30 '22 18:09

Luis Valdés