iOS Significant Location Change Update (Location Update Frequency) - ios

In my app I need to be able to have the app opened from a terminated state when the user has a significant change in location, so I'm calling the startMonitoringSignificantLocationChanges method of CLLocationManager which does exactly that.
The app is opening as it should, but the problem is that I'm getting location change updates after long periods of time, for example 20 mins even though I've moved 1000's of meters and some times I don't get any update at all. I need this to be updated after 500 meters. It doesn't matter to me if it waits for a long period of time, but I need it to update after the 500 meters which is considered a Significant Location Change according to Apple's Documentation here.
- (void) checkIfSignificantLocationChange : (NSDictionary *) launchOptions {
// Application is launched because of a significant location change
if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey])
{
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
}];
CLLocationManager *locationManager = [CLLocationManager new];
CLLocation *location = [locationManager location];
[locationManager startMonitoringSignificantLocationChanges];
if (bgTask != UIBackgroundTaskInvalid)
{
//Clean up code. Tell the system that we are done.
[[UIApplication sharedApplication] endBackgroundTask: bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
}
Can someone please help me to see what I'm doing wrong here? Why am I sometimes not getting any update at all after 30+ minutes even though I've moved well over 500 meters.
This method is called when the app detects a significant location change. I'm using the iOS Simulator Location - Freeway Drive for testing purposes because I was sick of driving all over the place.
Thanks in advance!!

Related

Sending Latitude and Longitude to Server when app is in background

I have gone through so many links, even after that I haven't found a proper solution for getting latitude and longitude.
Periodic iOS background location updates
iOS long-running background timer with "location" background mode
I tried from some links and forums but it is working for only 3 mins, then app is not at all updating the user location.
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
//create new uiBackgroundTask
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
//and create new timer with async call:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[locationManager startUpdatingLocation];
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(startTrackingBg) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
// store data
CLLocation *newLocation = [locations lastObject];
//tell the centralManager that you want to deferred this updatedLocation
if (_isBackgroundMode && !_deferringUpdates)
{
_deferringUpdates = YES;
[locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:10];
}
}
Ok.
After struggling for 3days, it is working for me for sending latitude and longitude when app is in background even after 3 mins.
I checked my app, continuously sending lat long for more than a hour in background.
It can help some one at least.
First Please add below two keys in your pList.
1.NSLocationAlwaysUsageDescription
2.NSLocationWhenInUseUsageDescription
Bothe are strings and you can give any value.
Then please turn on background fetch and check location updates under capabilities in project section.
Then import Corelocation framework and add this below code.
locationManager is a global variable.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//create CLLocationManager variable
locationManager = [[CLLocationManager alloc] init];
//set delegate
locationManager.delegate = self;
app = [UIApplication sharedApplication];
// 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.
if ([locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
[locationManager setAllowsBackgroundLocationUpdates:YES];
}
locationManager.desiredAccuracy = 45;
locationManager.distanceFilter = 100;
// Once configured, the location manager must be "started".
[locationManager startUpdatingLocation];
}
- (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.
[locationManager stopUpdatingLocation];
[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[locationManager setDistanceFilter:kCLDistanceFilterNone];
locationManager.pausesLocationUpdatesAutomatically = NO;
locationManager.activityType = CLActivityTypeAutomotiveNavigation;
[locationManager startUpdatingLocation];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
[locationManager stopUpdatingLocation];
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
bgTask = UIBackgroundTaskInvalid;
}];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(startTrackingBg)
userInfo:nil
repeats:YES];
}
-(void)startTrackingBg {
[locationManager startUpdatingLocation];
NSLog(#"App is running in background");
}
//starts automatically with locationManager
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
latitude=newLocation.coordinate.latitude;
longitude=newLocation.coordinate.longitude;
NSLog(#"Location: %f, %f",newLocation.coordinate.longitude, newLocation.coordinate.latitude);
}
You need to refer this apple documentation handling location events in the background
You need to enable location updates in background modes in capabilities of your Xcode project.
The Standard location service wont work in background mode so you have to use either Significant-change location service or Visits service .
Use this code to
locationManager.delegate = self
locationManager.startMonitoringSignificantLocationChanges()
enable Significant-change location service.
Building on Santos answer which has most of the important steps to make background positioning work, I have clarified and corrected some details.
Project settings
You should add these keys to your Xcode Target Info property list. Make sure to add a valid description to each of them, or your app might fail approval.
NSLocationAlwaysUsageDescription
NSLocationWhenInUseUsageDescription
NSLocationAlwaysAndWhenInUseUsageDescription
Next, in Target Capabilities, Turn on Background Modes and check Location updates and Background fetch. Location updates will enable locations in the background and background fetch will allow you to use the network in the background.
Start monitoring
Start by creating an instance of CLLocationManager, configure it and turn on background updates, then start it. Although the code below uses the most common function startUpdatingLocation to get locations, there are several other services to use. It is important to choose the most suitable service as this impacts greatly on battery usage and if the app will be re-launched or not by iOS. See below for more info on this.
// Declare as properties in your class
#property (strong, nonatomic) CLLocationManager *locationManager;
#property (strong, nonatomic) CLLocation *lastLocation;
// Call to start
- (void)initializeAndStartLocationService {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
// Must be set for background operation
self.locationManager.allowsBackgroundLocationUpdates = YES;
// Optional configuration
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.pausesLocationUpdatesAutomatically = YES;
self.locationManager.showsBackgroundLocationIndicator = YES;
// Authorize - the lazy way
[self.locationManager requestAlwaysAuthorization];
// Start the standard location service, there are others
[self.locationManager startUpdatingLocation];
}
// Delegate method that will receive location updates
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
// Keep the last received location
self.lastLocation = [locations lastObject];
NSLog(#"New position %f, %f", self.lastLocation.coordinate.latitude, self.lastLocation.coordinate.longitude);
// Do something with the location or retrieve the location later
// :
}
Stop monitoring
Don't forget to stop monitoring to conserve battery. You can start and stop several times on a single instance of CLLocationManager.
- (void)dealloc {
[self.locationManager stopUpdatingLocation];
}
Automatic app re-launch considerations
When your app is running in the background it can (and actually frequently will after some time) be terminated by iOS. Depending on the type of location updates you are using, iOS will or will not re-launch your app automatically for you. In cases where iOS do not re-launch, the user must start your app again to continue background processing.
Read more on this page.
Read more
CLLocationManager - Core Location | Apple Documentation
Handling Location Events in the Background | Apple Documentation

Support background location updates in iOS6 to 9

Is it possible to support background location updates from iOS6.0 all the way to the new iOS9?
Also, is is possible to keep the app updating the location in background even when the app is closed or when the device is locked?
Can you please give me some documentation about how to do that?
Thanks in advance!
Update
I've downloaded a sample project from here but for some reason it's not updating location in background, even when using simulated location and moving more than 500m. So i've changed this 2 methods:
- (void)startMonitoringLocation {
if (_anotherLocationManager)
[_anotherLocationManager stopMonitoringSignificantLocationChanges];
self.anotherLocationManager = [[CLLocationManager alloc]init];
_anotherLocationManager.delegate = self;
_anotherLocationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
_anotherLocationManager.activityType = CLActivityTypeOtherNavigation;
if(IS_OS_8_OR_LATER) {
[_anotherLocationManager requestAlwaysAuthorization];
}
[_anotherLocationManager startMonitoringSignificantLocationChanges];
}
- (void)restartMonitoringLocation {
[_anotherLocationManager stopMonitoringSignificantLocationChanges];
if (IS_OS_8_OR_LATER) {
[_anotherLocationManager requestAlwaysAuthorization];
}
[_anotherLocationManager startMonitoringSignificantLocationChanges];
}
And instead of using stopMonitoringSignificantLocationChanges and startMonitoringSignificantLocationChanges, I'm now using stopUpdatingLocation and startUpdatingLocation. The problem is that now when I'm going to background, it's starting to fetch location and never stops. Why?
yes it is possible , but why do you need to use the IOS 6 , i have no idea , by the way of you want to send location update at background this code will help you through this
- (void)applicationDidEnterBackground:(UIApplication *)application
{
bgTask = [application beginBackgroundTaskWithName:#"MyTask" expirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application 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, preferably in chunks.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
The app needs to register the background mode for location updates.
See the Background Execution chapter in the App Programming Guide for iOS

Location Services not working in background called by WatchKit

My Apple Watch app requires some data and requests it from the corresponding iPhone app. To fulfill the request the iPhone app requires the users location.
After receiving and testing with a real Apple Watch I found out that my iPhone app does not receive location updates when running in background. If the iPhone app is active in foreground it works without issues. With the simulator it worked in both cases.
In both cases (active and background) the WatchKit extension calls and starts successfully the iPhone app and goes all the way until startUpdatingLocation is called in the iPhone app. But in case the app is running in background didUpdateLocations is never called.
I tried with requestAlwaysAuthorization as well as requestWhenInUseAuthorization. No difference.
I also activated then the "location updates" background mode within capabilities. But again no difference.
Has someone else faced the same problem and found a way to receive the location also in background?
Here some code. First the check if authorization is required.
// iOS 8 check to avoid crash on older iOS
if ([self.locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)])
{
[self requestLocationAlwaysAuthorization];
}
else
{
[self runLocationUpdate];
}
Here the check for the proper Location Manager rights.
- (void)requestLocationAlwaysAuthorization
{
CLAuthorizationStatus currentAuthStatus = [CLLocationManager authorizationStatus];
if (currentAuthStatus == kCLAuthorizationStatusDenied)
{
//request user to change setting
}
else if (currentAuthStatus == kCLAuthorizationStatusRestricted)
{
//request user to change setting
}
else if (currentAuthStatus == kCLAuthorizationStatusNotDetermined)
{
[self.locationManager requestAlwaysAuthorization];
[self runLocationUpdate];
}
else if (currentAuthStatus == kCLAuthorizationStatusAuthorizedWhenInUse)
{
//maybe when in use is also enough?
[self runLocationUpdate];
}
else if (currentAuthStatus == kCLAuthorizationStatusAuthorizedAlways)
{
//all ok
[self runLocationUpdate];
}
}
Here the call of startUpdatingLocation. The didUpdateLocations delegate will only be called when iPhone app is active.
-(void)runLocationUpdate
{
[self.locationManager startUpdatingLocation];
}
Three things to check and be aware of:
Location Permissions like [self.locationManager requestAlwaysAuthorization]; are only acknowledged once by the OS. If you have already requested permission, doesn't matter the level, the OS will NOT display a request to the user. The OS will just pass over the request and leave the permission level as is. The only time you can be assured that the OS will display the request to the user is if the [CLLocationManager authorizationStatus] returns kCLAuthorizationStatusNotDetermined. In every other case, you must manually request permission by displaying an Alert or other form of UI display. Also note that the OS retains whether or not it already displayed the request, even if you delete your app and reinstall it. So to test, you need to reset your Simulator's Content or your iPhone's Location Privacy.
Make sure you have added the plist keys for NSLocationAlwaysUsageDescription AND NSLocationWhenInUseUsageDescription If you don't add this to your plist, the OS will ignore any Location Permission Requests.
If you want to use requestAlwaysAuthorization to get location data from the phone (not the watch app extension) while the phone app is in the background, will also require you register for Background Modes Location updates under Project>Target>Capabilities.
UPDATE
Use a background task to give your app time to respond when in the background. Something like this:
-(void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply{
UIApplication *app = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier bgTask __block = [app beginBackgroundTaskWithName:#"watchAppRequest" expirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
//make your calls here to your tasks, when finished, send the reply then terminate the background task
//send reply back to watch
reply(replyInfo);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[app endBackgroundTask:bgTask];
bgTask=UIBackgroundTaskInvalid;
});
}

CLLocationManager, didEnterRegion and UILocalNotification

So after much trial and error and reading Apples documents and SO threads I thought I had my didEnterRegion working properly.
This is what I finished up with...
- (void)locationManager:(LocationManager *)locationManager didEnterRegion:(CLRegion *)region{
NSLog(#"Location manager did enter region called");
[self.locationManager stopUpdatingLocation];
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive) {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
UILocalNotification *localNotification = [[UILocalNotification alloc]init];
localNotification.alertBody = #"You are about to arrive";
localNotification.alertAction = #"Open";
localNotification.applicationIconBadgeNumber = [[UIApplication sharedApplication]applicationIconBadgeNumber]+1;
[[UIApplication sharedApplication]presentLocalNotificationNow:localNotification];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"App Running in foreground notification fired");
[self setupAlarmTriggeredView];
//vibrate device
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
});
}
}
As you can see it does a simple check to see if the app is active, if not it sets up a UILocalNotification which it presents immediately, if it is it just vibrates and changes the view.
When testing on the simulator using a GPX file to move the location over the boundary, both scenarios work perfectly. However when testing the app out and about, when the app is in the background the notification doesn't seem to fire until you wake the device. ie. you can cross the boundary and nothing happens, then when you unlock the device, boom, it vibrates and the view is changed accordingly. (if the app is not foremost when you unlock this doesn't happen until you re-launch the app).
Should I be doing this setup in appDidFinishLaunchingWithOptions? In my testing both that method and didRecieveLocalNotification: in the app delegate don't get called until 'after' the notification has been fired AND the user has re-launched the app by actioning the notification (at which point you can check the for the launch key in the options array), this doesn't seem to be of any use for the initial firing of the notification as part of didEnterRegion. At the moment I have no code in either of these methods.
As far as I'm aware I don't need to be doing any background location updates for didEnterRegion, this is handled automatically by iOS.

iOS Not the typical background location tracking timer issue

i'm developing a sample app that tracks the user's position in background, but i don't want to leave the location service always enabled, but something in my timer does not behave properly.
My idea was that every x minutes, the service goes on, and when it have a correct new location it is released again, now is set to 10 seconds just for testing. (Significant LocationChange did not the trick, not accurated enough)
I was searching a lot (iOS Dev center + StackOverflow) and found the "new" background location features, that allows you to run code over 10 minutes after going to background, using beginBackgroundTaskWithExpirationHandler, a few blocks, and a timer.
I set the background mode to Location and by now i think i don't need to handle the end of the background time (first i want to get a location every 15-20 seconds)
the code is working "fine" but:
The timer sometimes fires, sometimes does not.
When the timer fires, it takes a minimum of 10 minutes to do it.
Some random actions in the OS (like entering to search desktop) appears to estimulate the timer to fire (not sure of this, i don't realize how it is possible, but there it is...)
And by over the code will be another qüestion.
appdelegates's methods:
//applicationDidEnterBackground
- (void)applicationDidEnterBackground:(UIApplication *)application{
NSLog(#"to background");
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[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.
_triggertimer = nil;
[self initTimer];
});
NSLog(#"backgroundTimeRemaining: %.0f", [[UIApplication sharedApplication] backgroundTimeRemaining]);}
//initTimer
- (void) initTimer{
NSLog(#"InitTimer ");
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
_triggertimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(checkUpdates:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_triggertimer forMode:NSDefaultRunLoopMode] ;
[[NSRunLoop currentRunLoop] run];
}];}
//checkUpdates
- (void)checkUpdates:(NSTimer *)timer{
NSLog(#"CheckUpdates ");
UIApplication* app = [UIApplication sharedApplication];
if (nil == _locationManager) _locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.distanceFilter = 10;
_locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
[_locationManager startUpdatingLocation];
[_locationManager startMonitoringSignificantLocationChanges];
double remaining = app.backgroundTimeRemaining;
NSLog(#"Reminaing %f", remaining);}
I tried lots of thing to try to fix this and maybe i messed or missed something... What do you see? maybe some concept errors, i'm trying to introduce myself to the blocks and I don't domain them yet ¬¬
By the way,why all the codes i've found contains this before doing anything with beginBackgroundTaskWithExpirationHandler?
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
I thought that this is for taking that 600 seconds of background... but i'm not sure!
When your app is backgrounded the timer will no longer fire unless you have the "location" value for the UIBackgroundModes key set in the app's info.plist.
You can extend the time that you are allowed to run in the background (if you haven't set "location") using beginBackgroundTaskWithExpirationHandler but that should always be paired with the corresponding end call.
Ok, the problem was that i was calling beginBackgroundTaskWithExpirationHandler twice, one in ApplicationDidEnterBackground and another one inside initTimer...

Resources