10

I have written the below code that has a timer that calls a callback function every minute. When the app goes to the background I have started another timer that calls the same callback method, but the background timer works for only three minutes.

I understand that Apple allows background tasks for only three minutes. My app is more like a tracking app that tracks the location of the user every minute even when the app is in background, so I need to implement this functionality.

I learned that beginBackgroundTaskWithExpirationHandler is to be used but I don't know whether my implementation is correct.

Note: I have Required background modes in plist toApp registers for location updates.

Any working code or links are much appreciated.

override func viewDidLoad() {
    super.viewDidLoad()

    timeInMinutes = 1 * 60

    self.locationManager.requestAlwaysAuthorization()

    self.locationManager.requestWhenInUseAuthorization()

    if CLLocationManager.locationServicesEnabled() {
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
        locationManager.startUpdatingLocation()
    }

    var timer = NSTimer.scheduledTimerWithTimeInterval( timeInMinutes, target: self, selector: "updateLocation", userInfo: nil, repeats: true)
}

func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
    let locValue:CLLocationCoordinate2D = manager.location!.coordinate

    self.latitude = locValue.latitude
    self.longitude = locValue.longitude

    if UIApplication.sharedApplication().applicationState == .Active {

    } else {
        backgroundTaskIdentifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({ () -> Void in
            self.backgroundTimer.invalidate()
            self.backgroundTimer = NSTimer.scheduledTimerWithTimeInterval( 60.0, target: self, selector: "updateLocation", userInfo: nil, repeats: true)
        })
    }
}

func updateLocation() {
    txtlocationLabel.text = String(n)
    self.n = self.n+1

    var timeRemaining = UIApplication.sharedApplication().backgroundTimeRemaining

    print(timeRemaining)

    if timeRemaining > 60.0 {
        self.GeoLogLocation(self.latitude,Longitude: self.longitude) {
            results in
            print("View Controller: \(results)")
            self.CheckResult(String(results))
        }
    } else {
        if timeRemaining == 0 {
            UIApplication.sharedApplication().endBackgroundTask(backgroundTaskIdentifier)
        }

        backgroundTaskIdentifier2 = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({ () -> Void in
            self.backgroundTimer.invalidate()
            self.backgroundTimer = NSTimer.scheduledTimerWithTimeInterval( 60.0, target: self, selector: "updateLocation", userInfo: nil, repeats: true)
        })
    }
}
mfaani
  • 33,269
  • 19
  • 164
  • 293
Carey
  • 139
  • 2
  • 9

2 Answers2

17

Periodic location updates are a bit tricky in IOS.There's a good thread that discusses this, you can read more here

iOS will terminate your app after a few minutes, regardless if your timer is running or not. There is a way around this though, I had to do something similar when writing an ionic app so you can check out the code for this here, that link has a swift class that manages the periodic location updates in iOs.

In order to get periodic locations in the background, and not drain the battery of the device, you need to play with the accuracy of the location records, lower the accuracy of the location manager setting its desiredAccuracy to kCLLocationAccuracyThreeKilometers, then, every 60 seconds you need to change the accuracy to kCLLocationAccuracyBest, this will enable the delegate to get a new, accurate location update, then revert the accuracy back to low. The timer needs to be initialized every time an update is received.

There's also a way to wake up the app in the background after its been killed by the user, use the app delegate to have the app listen for significant changes in location before its killed. This will wake up the app in the background when the user's location makes a big jump (can be around 200ms). When the app wakes up, stop monitoring for significant changes and restart the location services as usual to continue the periodic updates.

Hope this helps.

Update

In Swift 2 you'll also need:

self.locationManager.allowsBackgroundLocationUpdates = true
Community
  • 1
  • 1
Daniel Ormeño
  • 2,743
  • 2
  • 25
  • 30
  • Thanks for the help... I am quite new to Swift...I Tried changing the accuracy value every 60 seconds as you said...I still get the same result(i.e the app runs and updates location only for three minutes after it is backgrounded) – Carey Mar 24 '16 at 09:07
  • 1
    Hello, sorry I forgot to mention, and I believe this is missing in your code, you also need to enable background locations in your location manager, something along the lines of self.locationManager.allowsBackgroundLocationUpdates = true – Daniel Ormeño Mar 24 '16 at 09:17
  • No problem!, happy to help. – Daniel Ormeño Mar 24 '16 at 10:07
  • In my case, with iOS 10, the feature to wake up the app after it has been killed by the user is not working anymore. I had the code of @DanielOrmeño up and running. However, since the update, a significant location change restarts the app but it is killed again after three minutes. Can anyone confirm this? Is there any other workaround? – mikey Jan 03 '17 at 19:21
  • Hello @mikey, I haven't touched this for a while but after quick search I couldn't find any breaking changed in iOS10, if the app is getting killed it might be because the intervals you are using for your update are too long, apple allows you to add a flag to ask the os for more time to complete a task. See "implementing long running tasks" from https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html hopefully this will help – Daniel Ormeño Jan 03 '17 at 21:49
  • Thanks for your answer, @DanielOrmeño .. I created a very simple app only containing your location manager. And you are right: When the user kills the app manually, it will be reanimated on a significant location change. I guess, due to my 5 second update interval, I had just killed it in an unfavorable moment. However, the app does not (really) survive a device reboot. After I have done a reboot, a significant location change reanimates the app for 3 minutes, then, unfortunately, it stops working. Any ideas on that? – mikey Jan 06 '17 at 12:02
  • Ok, one more (last) comment: The app does survive forever, also after reboot. Sorry for all the confusion. However, one thing I realized that definitely changed with iOS 10 is the handling of silent (content-available) push messages. I do receive those messages when the app is in background. I do not receive these messages anymore when the app has been killed by the user or when I reboot iOS. This was working in iOS 9. – mikey Feb 11 '17 at 21:09
1

You can use library for background location tracking, example of use:

    var backgroundLocationManager = BackgroundLocationManager()

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {

     backgroundLocationManager.startBackground() { result in
            if case let .Success(location) = result {
                LocationLogger().writeLocationToFile(location: location)
            }
    }

    return true
}

It's working when application is killed or suspended.

Roman Barzyczak
  • 3,785
  • 1
  • 30
  • 44