Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 226 Vote(s) - 3.56 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How do I get a background location update every n minutes in my iOS application?

#1
I'm looking for a way to get a background location update every n minutes in my iOS application. I'm using iOS 4.3 and the solution should work for non-jailbroken iPhones.

I tried / considered following options:

* `CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges`: This works in the background as expected, based on the configured properties, but it seems not possible to force it to update the location every n minutes
* `NSTimer`: Does work when the app is running in the foreground but doesn't seem to be designed for background tasks
* Local notifications: Local notifications can be scheduled every n minutes, but it's not possible to execute some code to get the current location (without the user having to launch the app via the notification). This approach also doesn't seem to be a clean approach as this is not what notifications should be used for.
* `UIApplication:beginBackgroundTaskWithExpirationHandler`: As far as I understand, this should be used to finish some work in the background (also limited in time) when an app is moved to the background rather than implementing "long-running" background processes.

How can I implement these regular background location updates?
Reply

#2
Unfortunately, all of your assumptions seem correct, and I don't think there's a way to do this. In order to save battery life, the iPhone's location services are based on movement. If the phone sits in one spot, it's invisible to location services.

The `CLLocationManager` will only call `locationManager:didUpdateToLocation:fromLocation:` when the phone receives a location update, which only happens if one of the three location services (cell tower, gps, wifi) perceives a change.

A few other things that might help inform further solutions:

- Starting & Stopping the services causes the `didUpdateToLocation` delegate method to be called, but the `newLocation` might have an old timestamp.

- [Region Monitoring might help][1]

[1]:http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html#//apple_ref/doc/uid/TP40009497-CH2-SW10

- When running in the background, be aware that it may be difficult to get "full" LocationServices support approved by Apple. From what I've seen, they've specifically designed `startMonitoringSignificantLocationChanges` as a low power alternative for apps that need background location support, and strongly encourage developers to use this unless the app absolutely needs it.

Good Luck!

UPDATE: These thoughts may be out of date by now. Looks as though people are having success with @wjans answer, above.
Reply

#3
Now that iOS6 is out the best way to have a forever running location services is...

- (void)applicationWillResignActive:(UIApplication *)application
{
/*
Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
*/

NSLog(@"to background");

app.isInBackground = TRUE;

UIApplication *app = [UIApplication sharedApplication];

// Request permission to run in the background. Provide an
// expiration handler in case the task runs long.
NSAssert(bgTask == UIBackgroundTaskInvalid, nil);

bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
// Synchronize the cleanup call on the main thread in case
// the task actually finishes at around the same time.
dispatch_async(dispatch_get_main_queue(), ^{

if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
}];

// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

// Do the work associated with the task.

locationManager.distanceFilter = 100;
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
[locationManager startMonitoringSignificantLocationChanges];
[locationManager startUpdatingLocation];

NSLog(@"App staus: applicationDidEnterBackground");
// Synchronize the cleanup call on the main thread in case
// the expiration handler is fired at the same time.
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
});
});

NSLog(@"backgroundTimeRemaining: %.0f", [[UIApplication sharedApplication] backgroundTimeRemaining]);

}

Just tested it like that:

I started the app, go background and move in the car by some minutes. Then I go home for 1 hour and start moving again (without opening again the app). Locations started again. Then stopped for two hours and started again. Everything ok again...

DO NOT FORGET USING the new location services in iOS6

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *loc = [locations lastObject];

// Lat/Lon
float latitudeMe = loc.coordinate.latitude;
float longitudeMe = loc.coordinate.longitude;
}
Reply

#4
To someone else having nightmare figure out this one. I have a simple solution.

1. look this example from [raywenderlich.com][1]-> have sample code, this works perfectly, but unfortunately no timer during background location. this will run indefinitely.
2. Add timer by using :

-(void)applicationDidEnterBackground {
[self.locationManager stopUpdatingLocation];

UIApplication* app = [UIApplication sharedApplication];

bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];

self.timer = [NSTimer scheduledTimerWithTimeInterval:intervalBackgroundUpdate
target:self.locationManager
selector:@selector(startUpdatingLocation)
userInfo:nil
repeats:YES];

}

3. Just don't forget to add "App registers for location updates" in info.plist.


[1]:

[To see links please register here]

Reply

#5
I did this in an application I'm developing. The timers don't work when the app is in the background but the app is constantly receiving the location updates. I read somewhere in the documentation (i can't seem to find it now, i'll post an update when i do) that a method can be called only on an active run loop when the app is in the background. The app delegate has an active run loop even in the bg so you dont need to create your own to make this work.
[Im not sure if this is the correct explanation but thats how I understood from what i read]

First of all, add the `location` object for the key `UIBackgroundModes` in your app's info.plist. Now, what you need to do is start the location updates anywhere in your app:

CLLocationManager locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;//or whatever class you have for managing location
[locationManager startUpdatingLocation];

Next, write a method to handle the location updates,
say `-(void)didUpdateToLocation:(CLLocation*)location`, in the app delegate. Then implement the method `locationManager:didUpdateLocation:fromLocation` of `CLLocationManagerDelegate` in the class in which you started the location manager (since we set the location manager delegate to 'self'). Inside this method you need to check if the time interval after which you have to handle the location updates has elapsed. You can do this by saving the current time every time. If that time has elapsed, call the method UpdateLocation from your app delegate:

NSDate *newLocationTimestamp = newLocation.timestamp;
NSDate *lastLocationUpdateTiemstamp;

int locationUpdateInterval = 300;//5 mins

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (userDefaults) {

lastLocationUpdateTiemstamp = [userDefaults objectForKey:kLastLocationUpdateTimestamp];

if (!([newLocationTimestamp timeIntervalSinceDate:lastLocationUpdateTiemstamp] < locationUpdateInterval)) {
//NSLog(@"New Location: %@", newLocation);
[(AppDelegate*)[UIApplication sharedApplication].delegate didUpdateToLocation:newLocation];
[userDefaults setObject:newLocationTimestamp forKey:kLastLocationUpdateTimestamp];
}
}
}

This will call your method every 5 mins even when your app is in background.
Imp: This implementation drains the battery, if your location data's accuracy is not critical you should use `[locationManager startMonitoringSignificantLocationChanges]`

Before adding this to your app, please read the [Location Awareness Programming Guide][1]

[1]:http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/LocationAwarenessPG/Introduction/Introduction.html
Reply

#6
Attached is a Swift solution based in:

Define `App registers for location updates` in the info.plist

Keep the locationManager running all the time

Switch `kCLLocationAccuracy` between `BestForNavigation` (for 5 secs to get the location) and `ThreeKilometers` for the rest of the wait period to avoid battery drainage

This example updates location every 1 min in Foreground and every 15 mins in Background.

The example works fine with Xcode 6 Beta 6, running in a iOS 7 device.

In the App Delegate (mapView is an Optional pointing to the mapView Controller)

func applicationDidBecomeActive(application: UIApplication!) {
if appLaunched! == false { // Reference to mapView used to limit one location update per timer cycle
appLaunched = true
var appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
var window = appDelegate.window
var tabBar = window?.rootViewController as UITabBarController
var navCon = tabBar.viewControllers[0] as UINavigationController
mapView = navCon.topViewController as? MapViewController
}
self.startInitialPeriodWithTimeInterval(60.0)
}

func applicationDidEnterBackground(application: UIApplication!) {
self.startInitialPeriodWithTimeInterval(15 * 60.0)
}

func startInitialPeriodWithTimeInterval(timeInterval: NSTimeInterval) {
timer?.invalidate() // reset timer
locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getFirstLocationUpdate:"), userInfo: timeInterval, repeats: false)
}

func getFirstLocationUpdate(sender: NSTimer) {
let timeInterval = sender.userInfo as Double
timer?.invalidate()
mapView?.canReportLocation = true
timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("waitForTimer:"), userInfo: timeInterval, repeats: true)
}

func waitForTimer(sender: NSTimer) {
let time = sender.userInfo as Double
locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
finalTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getLocationUpdate"), userInfo: nil, repeats: false)
}

func getLocationUpdate() {
finalTimer?.invalidate()
mapView?.canReportLocation = true
}

In the mapView (locationManager points to the object in the AppDelegate)

override func viewDidLoad() {
super.viewDidLoad()
var appDelegate = UIApplication.sharedApplication().delegate! as AppDelegate
locationManager = appDelegate.locationManager!
locationManager.delegate = self
canReportLocation = true
}

func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
if canReportLocation! {
canReportLocation = false
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
} else {
//println("Ignore location update")
}
}

Reply

#7
It seems that stopUpdatingLocation is what triggers the background watchdog timer, so I replaced it in didUpdateLocation with:

[self.locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers];
[self.locationManager setDistanceFilter:99999];

which appears to effectively power down the GPS. The selector for the background NSTimer then becomes:

- (void) changeAccuracy {
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[self.locationManager setDistanceFilter:kCLDistanceFilterNone];
}

All I'm doing is periodically toggling the accuracy to get a high-accuracy coordinate every few minutes and because the locationManager hasn't been stopped, backgroundTimeRemaining stays at its maximum value. This reduced battery consumption from ~10% per hour (with constant kCLLocationAccuracyBest in the background) to ~2% per hour on my device
Reply

#8
I did write an app using Location services, app must send location every 10s.
And it worked very well.

Just use the "[allowDeferredLocationUpdatesUntilTraveled:timeout][1]" method, following Apple's doc.


What I did are:

**Required:** Register background mode for update Location.

**1.** Create `LocationManger` and `startUpdatingLocation`, with `accuracy` and `filteredDistance` as whatever you want:

-(void) initLocationManager
{
// Create the manager object
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
_locationManager.delegate = self;
// This is the most important property to set for the manager. It ultimately determines how the manager will
// attempt to acquire location and thus, the amount of power that will be consumed.
_locationManager.desiredAccuracy = 45;
_locationManager.distanceFilter = 100;
// Once configured, the location manager must be "started".
[_locationManager startUpdatingLocation];
}

**2.** To keep app run forever using `allowDeferredLocationUpdatesUntilTraveled:timeout` method in background, you must restart `updatingLocation` with new parameter when app moves to background, like this:

- (void)applicationWillResignActive:(UIApplication *)application {
_isBackgroundMode = YES;

[_locationManager stopUpdatingLocation];
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[_locationManager setDistanceFilter:kCLDistanceFilterNone];
_locationManager.pausesLocationUpdatesAutomatically = NO;
_locationManager.activityType = CLActivityTypeAutomotiveNavigation;
[_locationManager startUpdatingLocation];
}

**3.** App gets updatedLocations as normal with `locationManager:didUpdateLocations:` callback:

-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
// store data
CLLocation *newLocation = [locations lastObject];
self.userLocation = newLocation;

//tell the centralManager that you want to deferred this updatedLocation
if (_isBackgroundMode && !_deferringUpdates)
{
_deferringUpdates = YES;
[self.locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:10];
}
}

**4.** But you should handle the data in then `locationManager:didFinishDeferredUpdatesWithError:` callback for your purpose

- (void) locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error {

_deferringUpdates = NO;

//do something
}

**5.** ***NOTE:*** I think we should reset parameters of `LocationManager` each time app switches between background/forground mode.


[1]:

[To see links please register here]

Reply

#9
if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
}

This is needed for background location tracking since iOS 9.
Reply

#10
Here is what I use:

import Foundation
import CoreLocation
import UIKit

class BackgroundLocationManager :NSObject, CLLocationManagerDelegate {

static let instance = BackgroundLocationManager()
static let BACKGROUND_TIMER = 150.0 // restart location manager every 150 seconds
static let UPDATE_SERVER_INTERVAL = 60 * 60 // 1 hour - once every 1 hour send location to server

let locationManager = CLLocationManager()
var timer:NSTimer?
var currentBgTaskId : UIBackgroundTaskIdentifier?
var lastLocationDate : NSDate = NSDate()

private override init(){
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.activityType = .Other;
locationManager.distanceFilter = kCLDistanceFilterNone;
if #available(iOS 9, *){
locationManager.allowsBackgroundLocationUpdates = true
}

NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.applicationEnterBackground), name: UIApplicationDidEnterBackgroundNotification, object: nil)
}

func applicationEnterBackground(){
FileLogger.log("applicationEnterBackground")
start()
}

func start(){
if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedAlways){
if #available(iOS 9, *){
locationManager.requestLocation()
} else {
locationManager.startUpdatingLocation()
}
} else {
locationManager.requestAlwaysAuthorization()
}
}
func restart (){
timer?.invalidate()
timer = nil
start()
}

func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case CLAuthorizationStatus.Restricted:
//log("Restricted Access to location")
case CLAuthorizationStatus.Denied:
//log("User denied access to location")
case CLAuthorizationStatus.NotDetermined:
//log("Status not determined")
default:
//log("startUpdatintLocation")
if #available(iOS 9, *){
locationManager.requestLocation()
} else {
locationManager.startUpdatingLocation()
}
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

if(timer==nil){
// The locations array is sorted in chronologically ascending order, so the
// last element is the most recent
guard let location = locations.last else {return}

beginNewBackgroundTask()
locationManager.stopUpdatingLocation()
let now = NSDate()
if(isItTime(now)){
//TODO: Every n minutes do whatever you want with the new location. Like for example sendLocationToServer(location, now:now)
}
}
}

func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
CrashReporter.recordError(error)

beginNewBackgroundTask()
locationManager.stopUpdatingLocation()
}

func isItTime(now:NSDate) -> Bool {
let timePast = now.timeIntervalSinceDate(lastLocationDate)
let intervalExceeded = Int(timePast) > BackgroundLocationManager.UPDATE_SERVER_INTERVAL
return intervalExceeded;
}

func sendLocationToServer(location:CLLocation, now:NSDate){
//TODO
}

func beginNewBackgroundTask(){
var previousTaskId = currentBgTaskId;
currentBgTaskId = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
FileLogger.log("task expired: ")
})
if let taskId = previousTaskId{
UIApplication.sharedApplication().endBackgroundTask(taskId)
previousTaskId = UIBackgroundTaskInvalid
}

timer = NSTimer.scheduledTimerWithTimeInterval(BackgroundLocationManager.BACKGROUND_TIMER, target: self, selector: #selector(self.restart),userInfo: nil, repeats: false)
}
}

I start the tracking in AppDelegate like that:

BackgroundLocationManager.instance.start()
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through