It seems that in iOS 7 an app can not start Location Manager (by calling startUpdatingLocation) from the background task anymore.
In iOS 6 I used approach described here: https://stackoverflow.com/a/6465280 to run background location update every n minutes. The idea was to run background task with a timer and start Location Manager when the timer triggers it. After that turn off Location Manager and start another background task.
After updating to iOS 7 this approach does not work anymore. After starting Location Manager an app does not receive any locationManager:didUpdateLocations. Any ideas?
I found the problem/solution. When it is time to start location service and stop background task, background task should be stopped with a delay (I used 1 second). Otherwise location service wont start. Also Location Service should be left ON for a couple of seconds (in my example it is 3 seconds).
Another important notice, max background time in iOS 7 is now 3 minutes instead of 10 minutes.
Updated on October 29 '16
There is a cocoapod APScheduledLocationManager that allows to get background location updates every n seconds with desired location accuracy.
let manager = APScheduledLocationManager(delegate: self)
manager.startUpdatingLocation(interval: 170, acceptableLocationAccuracy: 100)
The repository also contains an example app written in Swift 3.
Updated on May 27 '14
Objective-C example:
1) In ".plist" file set UIBackgroundModes to "location".
2) Create instance of ScheduledLocationManager anywhere you want.
#property (strong, nonatomic) ScheduledLocationManager *slm;
3) Set it up
self.slm = [[ScheduledLocationManager alloc]init];
self.slm.delegate = self;
[self.slm getUserLocationWithInterval:60]; // replace this value with what you want, but it can not be higher than kMaxBGTime
4) Implement delegate methods
-(void)scheduledLocationManageDidFailWithError:(NSError *)error
{
NSLog(#"Error %#",error);
}
-(void)scheduledLocationManageDidUpdateLocations:(NSArray *)locations
{
// You will receive location updates every 60 seconds (value what you set with getUserLocationWithInterval)
// and you will continue to receive location updates for 3 seconds (value of kTimeToGetLocations).
// You can gather and pick most accurate location
NSLog(#"Locations %#",locations);
}
Here is implementation of ScheduledLocationManager:
ScheduledLocationManager.h
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#protocol ScheduledLocationManagerDelegate <NSObject>
-(void)scheduledLocationManageDidFailWithError:(NSError*)error;
-(void)scheduledLocationManageDidUpdateLocations:(NSArray*)locations;
#end
#interface ScheduledLocationManager : NSObject <CLLocationManagerDelegate>
-(void)getUserLocationWithInterval:(int)interval;
#end
ScheduledLocationManager.m
#import "ScheduledLocationManager.h"
int const kMaxBGTime = 170; // 3 min - 10 seconds (as bg task is killed faster)
int const kTimeToGetLocations = 3; // time to wait for locations
#implementation ScheduledLocationManager
{
UIBackgroundTaskIdentifier bgTask;
CLLocationManager *locationManager;
NSTimer *checkLocationTimer;
int checkLocationInterval;
NSTimer *waitForLocationUpdatesTimer;
}
- (id)init
{
self = [super init];
if (self) {
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.distanceFilter = kCLDistanceFilterNone;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
}
return self;
}
-(void)getUserLocationWithInterval:(int)interval
{
checkLocationInterval = (interval > kMaxBGTime)? kMaxBGTime : interval;
[locationManager startUpdatingLocation];
}
- (void)timerEvent:(NSTimer*)theTimer
{
[self stopCheckLocationTimer];
[locationManager startUpdatingLocation];
// in iOS 7 we need to stop background task with delay, otherwise location service won't start
[self performSelector:#selector(stopBackgroundTask) withObject:nil afterDelay:1];
}
-(void)startCheckLocationTimer
{
[self stopCheckLocationTimer];
checkLocationTimer = [NSTimer scheduledTimerWithTimeInterval:checkLocationInterval target:self selector:#selector(timerEvent:) userInfo:NULL repeats:NO];
}
-(void)stopCheckLocationTimer
{
if(checkLocationTimer){
[checkLocationTimer invalidate];
checkLocationTimer=nil;
}
}
-(void)startBackgroundTask
{
[self stopBackgroundTask];
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
//in case bg task is killed faster than expected, try to start Location Service
[self timerEvent:checkLocationTimer];
}];
}
-(void)stopBackgroundTask
{
if(bgTask!=UIBackgroundTaskInvalid){
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
-(void)stopWaitForLocationUpdatesTimer
{
if(waitForLocationUpdatesTimer){
[waitForLocationUpdatesTimer invalidate];
waitForLocationUpdatesTimer =nil;
}
}
-(void)startWaitForLocationUpdatesTimer
{
[self stopWaitForLocationUpdatesTimer];
waitForLocationUpdatesTimer = [NSTimer scheduledTimerWithTimeInterval:kTimeToGetLocations target:self selector:#selector(waitForLoactions:) userInfo:NULL repeats:NO];
}
- (void)waitForLoactions:(NSTimer*)theTimer
{
[self stopWaitForLocationUpdatesTimer];
if(([[UIApplication sharedApplication ]applicationState]==UIApplicationStateBackground ||
[[UIApplication sharedApplication ]applicationState]==UIApplicationStateInactive) &&
bgTask==UIBackgroundTaskInvalid){
[self startBackgroundTask];
}
[self startCheckLocationTimer];
[locationManager stopUpdatingLocation];
}
#pragma mark - CLLocationManagerDelegate methods
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
if(checkLocationTimer){
//sometimes it happens that location manager does not stop even after stopUpdationLocations
return;
}
if (self.delegate && [self.delegate respondsToSelector:#selector(scheduledLocationManageDidUpdateLocations:)]) {
[self.delegate scheduledLocationManageDidUpdateLocations:locations];
}
if(waitForLocationUpdatesTimer==nil){
[self startWaitForLocationUpdatesTimer];
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
if (self.delegate && [self.delegate respondsToSelector:#selector(scheduledLocationManageDidFailWithError:)]) {
[self.delegate scheduledLocationManageDidFailWithError:error];
}
}
#pragma mark - UIAplicatin notifications
- (void)applicationDidEnterBackground:(NSNotification *) notification
{
if([self isLocationServiceAvailable]==YES){
[self startBackgroundTask];
}
}
- (void)applicationDidBecomeActive:(NSNotification *) notification
{
[self stopBackgroundTask];
if([self isLocationServiceAvailable]==NO){
NSError *error = [NSError errorWithDomain:#"your.domain" code:1 userInfo:[NSDictionary dictionaryWithObject:#"Authorization status denied" forKey:NSLocalizedDescriptionKey]];
if (self.delegate && [self.delegate respondsToSelector:#selector(scheduledLocationManageDidFailWithError:)]) {
[self.delegate scheduledLocationManageDidFailWithError:error];
}
}
}
#pragma mark - Helpers
-(BOOL)isLocationServiceAvailable
{
if([CLLocationManager locationServicesEnabled]==NO ||
[CLLocationManager authorizationStatus]==kCLAuthorizationStatusDenied ||
[CLLocationManager authorizationStatus]==kCLAuthorizationStatusRestricted){
return NO;
}else{
return YES;
}
}
#end
I tried your method but it didn't work on my side. Can you show me your code?
I actually found a solution to solve the location service problem in iOS 7.
In iOS 7, you can not start the location service in background. If you want the location service to keep running in the background, you have to start it in foreground and it will continue to run in the background.
If you were like me, stop the location service and use timer to re-start it in the background, it will NOT work in iOS 7.
For more detailed information, you can watch the first 8 minutes of video 307 from WWDC 2013: https://developer.apple.com/wwdc/videos/
Update: The location service can work in background as well. Please check Background Location Services not working in iOS 7 for the updated post with complete solution posted on Github and a blog post explaining the details.
Steps to get this implemented are as follows:
Add "App registers for location updates" at item 0 in "Required background modes" in info.plist of your project.
Write below code at application did finish launching.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(startFetchingLocationsContinously) name:START_FETCH_LOCATION object:nil];
Write below code from where you want to start tracking
[[NSNotificationCenter defaultCenter] postNotificationName:START_FETCH_LOCATION object:nil];
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[appDelegate startUpdatingDataBase];
Paste following code to AppDelegate.m
#pragma mark - Location Update
-(void)startFetchingLocationsContinously{
NSLog(#"start Fetching Locations");
self.locationUtil = [[LocationUtil alloc] init];
[self.locationUtil setDelegate:self];
[self.locationUtil startLocationManager];
}
-(void)locationRecievedSuccesfullyWithNewLocation:(CLLocation*)newLocation oldLocation:(CLLocation*)oldLocation{
NSLog(#"location received successfullly in app delegate for Laitude: %f and Longitude:%f, and Altitude:%f, and Vertical Accuracy: %f",newLocation.coordinate.latitude,newLocation.coordinate.longitude,newLocation.altitude,newLocation.verticalAccuracy);
}
-(void)startUpdatingDataBase{
UIApplication* app = [UIApplication sharedApplication];
bgTask = UIBackgroundTaskInvalid;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^(void){
[app endBackgroundTask:bgTask];
}];
SAVE_LOCATION_TIMER = [NSTimer scheduledTimerWithTimeInterval:300
target:self selector:#selector(startFetchingLocationsContinously) userInfo:nil repeats:YES];
}
Add a class by name "LocationUtil" and paste following code into the header file:
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#protocol LocationRecievedSuccessfully <NSObject>
#optional
-(void)locationRecievedSuccesfullyWithNewLocation:(CLLocation*)newLocation oldLocation:(CLLocation*)oldLocation;
-(void)addressParsedSuccessfully:(id)address;
#end
#interface LocationUtil : NSObject <CLLocationManagerDelegate> {
}
//Properties
#property (nonatomic,strong) id<LocationRecievedSuccessfully> delegate;
-(void)startLocationManager;
And paste following code in LocationUtil.m
-(void)startLocationManager{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
[locationManager setPausesLocationUpdatesAutomatically:YES]; //Utkarsh 20sep2013
//[locationManager setActivityType:CLActivityTypeFitness];
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
[locationManager startUpdatingLocation];
//Reverse Geocoding.
geoCoder=[[CLGeocoder alloc] init];
//set default values for reverse geo coding.
}
//for iOS<6
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
//call delegate Method
[delegate locationRecievedSuccesfullyWithNewLocation:newLocation oldLocation:oldLocation];
NSLog(#"did Update Location");
}
//for iOS>=6.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *newLocation = [locations objectAtIndex:0];
CLLocation *oldLocation = [locations objectAtIndex:0];
[delegate locationRecievedSuccesfullyWithNewLocation:newLocation oldLocation:oldLocation];
NSLog(#"did Update Locationsssssss");
}
Related
I'm trying to build an iOS where I can update my location every x seconds and send a notification to update the UI.
I get my location but the update is random. Any ideas how to add the interval ?
here is my code :
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *location = [locations lastObject];
[self sendNotification :#"long"
:[NSString stringWithFormat:#"%.8f",location.coordinate.longitude]];
[self sendNotification :#"lat"
:[NSString stringWithFormat:#"%.8f",location.coordinate.latitude]];
}
Try this :
declared in .h file
#property (strong, nonatomic) NSDate *lastTimestamp;
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *mostRecentLocation = locations.lastObject;
NSLog(#"Current location: %# %#", #(mostRecentLocation.coordinate.latitude), #(mostRecentLocation.coordinate.longitude));
NSDate *now = [NSDate date];
NSTimeInterval interval = self.lastTimestamp ? [now timeIntervalSinceDate:self.lastTimestamp] : 0;
if (!self.lastTimestamp || interval >= 5 * 60)
{
self.lastTimestamp = now;
NSLog(#"update your UI");
}
}
use NSTimer:
//declare global
NSTimer *ShareTimeCheck;
int ShareSecLeft;
implement this method:
-(void)shareTimeChecking{
if (ShareSecLeft==0) {
[ShareTimeCheck invalidate];
ShareTimeCheck=nil;
}else{
ShareSecLeft--;
if (ShareSecLeft==0) {
[ShareTimeCheck invalidate];
ShareTimeCheck=nil;
}
}
}
call this one in ur location update method:
if (ShareSecLeft==0) {
[ShareTimeCheck invalidate];
ShareTimeCheck=nil;
ShareSecLeft=5;
heduledTimerWithTimeInterval:1 target:self selector:#selector(shareTimeChecking) userInfo:nil repeats:YES];
//write ur code to update the ui. or update to server as per ur requirement.
}
In iOS natively we can’t change the interval at which the system updates the user location. IOS updates position regularly every second, if have GPS signal.
If the application is in the foreground, we could simply stop monitoring and again start it after interval, for example using NSTimer. In this case, we have to think about the life of the application. The application runs in the background and during idle stops working.
My final procedure is use NSTimer in the background by using UIApplication:beginBackgroundTaskWithExpirationHandler:. This timer triggers periodically while the application runs in the background. You can view the following example:
#import "AppDelegate.h"
#implementation AppDelegate
BOOL locationStarted = FALSE;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//set default value after application starts
locationStarted = FALSE;
//create CLLocationManager variable
locationManager = [[CLLocationManager alloc] init];
//set delegate
locationManager.delegate = self;
app = [UIApplication sharedApplication];
return YES;
}
//update location
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
NSLog(#"Location: %f, %f", newLocation.coordinates.longtitude, newLocation.coordinates.latitude);
}
//run background task
-(void)runBackgroundTask: (int) time{
//check if application is in background mode
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
//create UIBackgroundTaskIdentifier and create a background task, which starts after time
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(startTrackingBg) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
}
//starts when application switches to background
- (void)applicationDidEnterBackground:(UIApplication *)application
{
//check if application status is in background
if ( [UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
NSLog(#"start background tracking from appdelegate");
//start updating location with location manager
[locationManager startUpdatingLocation];
}
//change locationManager status after time
[self runBackgroundTask:20];
}
//starts with background task
-(void)startTrackingBg{
//write background time remaining
NSLog(#"backgroundTimeRemaining: %.0f", [[UIApplication sharedApplication] backgroundTimeRemaining]);
//set default time
int time = 60;
//if locationManager is ON
if (locationStarted == TRUE ) {
//stop update location
[locationManager stopUpdatingLocation];
locationStarted = FALSE;
}else{
//start updating location
[locationManager startUpdatingLocation];
locationStarted = TRUE;
//ime how long the application will update your location
time = 5;
}
[self runBackgroundTask:time];
}
//application switches back from background
- (void)applicationWillEnterForeground:(UIApplication *)application
{
locationStarted = FALSE;
//stop updating
[locationManager stopUpdatingLocation];
}
/*** other methods ***/
- (void)applicationWillResignActive:(UIApplication *)application{}
- (void)applicationDidBecomeActive:(UIApplication *)application{}
- (void)applicationWillTerminate:(UIApplication *)application{}
#end
A simpler solution might be to run the timer loop:
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(startTrackingBg) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
I wonder how to capture the location when the app is not running and save the database. Already followed several tutorials but none worked. Please, before scoring as duplicate, I ask you to read the complete question as it can not be.
I tried the following tutorials:
Getting Location Updates for iOS 7 and 8 when the App is Killed/Terminated/Suspended
Background Modes Tutorial: Getting Started
to run app continously in the background
Not exactly what I need, but still I tried
Assuming that I have no implementation for the capture location. I need every day at noon, capture the user's location and save the database then send to a web service.
This capture location must be made regardless of whether the app is active in the background or not running (two rings on the middle button and dragging).
I tried some tutorials found on the internet and even some suggested here by the community, but still rather not worked.
I have added background modes - Location.
I allowed to get location requestAlwaysAuthorization.
The language can be in objective-c or swift, Most importantly, I learn how to capture this location when app in all states.
OK i am going to make this as simple as possible..Enable Background Modes and tick background fetch like this
Follow this methods for every step
When app is TERMINATED
AppDelegate.h
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate,CLLocationManagerDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong,nonatomic) CLLocationManager *locationManager;
#end
AppDelegate.m
#define userDef [NSUserDefaults standardUserDefaults]
#define IS_OS_8_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
#import "AppDelegate.h"
#import <CoreLocation/CoreLocation.h>
#import "AFNetworking.h"
#import <GoogleMaps/GoogleMaps.h>
#implementation AppDelegate{
BOOL fromTerminated;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
fromTerminated = NO;
if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
fromTerminated = YES;
self.locationManager = [[CLLocationManager alloc]init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
self.locationManager.activityType = CLActivityTypeOtherNavigation;
[self.locationManager startUpdatingLocation];
}
return YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
if(self.locationManager){
[self.locationManager stopMonitoringSignificantLocationChanges];
self.locationManager = nil;
self.locationManager.delegate = nil;
}
self.locationManager = [[CLLocationManager alloc]init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
self.locationManager.activityType = CLActivityTypeOtherNavigation;
if(IS_OS_8_OR_LATER) {
[self.locationManager requestAlwaysAuthorization];
}
[self.locationManager startMonitoringSignificantLocationChanges];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
NSLog(#"locationManager didUpdateLocations: %#",locations);
if(fromTerminated){
CLLocation * newLocation = [locations lastObject];
CLLocationCoordinate2D theLocation = newLocation.coordinate;
CLLocationAccuracy theAccuracy = newLocation.horizontalAccuracy;
[userDef setObject:[NSString stringWithFormat:#"%f",theLocation.longitude] forKey:#"LONGITUDE"];
[userDef setObject:[NSString stringWithFormat:#"%f",theLocation.latitude] forKey:#"LATITUDE"];
self.myLocation = theLocation;
self.myLocationAccuracy = theAccuracy;
[self updateLocation];
}
}
-(void)updateLocation{
// call your webservice for updating
}
#end
This code will do the following-->
Background fetch will trigger a location change and will launch the application and didFInishLaunchingWithOptions will be called with a UIApplicationLaunchOptionsLocationKey in the option dictionary. If it finds this that means the application is TERMINATED and woke up for background fetch. Now you got 30 seconds or so to do your stuff. So you create a location manager object and start updating, which will trigger your didUpdateLocations delegate method and then you can call your method to trigger that changed location in your server or database.
In your normal VC create another Location Manager object just like you created in the didFinishiLaunchingWithOptions method and implement the delegate method didUpdateLocation this will run untill the application is in foreground or background. The app delegate method hack will trigger the application when its terminated.
Cheers :)
[EDIT]
When app is in foreground or Background
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#interface ViewController ()<CLLocationManagerDelegate>
#property (nonatomic,strong) CLLocationManager *locationManager;
#end
#implementation ViewController{
}
-(void)viewDidAppear:(BOOL)animated{
if(self.locationManager == nil){
_locationManager = [CLLocationManager new];
}
_locationManager.delegate = self;
_locationManager.distanceFilter = kCLDistanceFilterNone;
_locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0 && [CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorizedWhenInUse) {
[_locationManager requestAlwaysAuthorization];
} else {
[_locationManager startUpdatingLocation];
}
}
- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
_locationManager = nil;
CLLocation *location = [locations lastObject];
theUser.latitude = [NSString stringWithFormat:#"%f",location.coordinate.latitude];
theUser.longitude = [NSString stringWithFormat:#"%f",location.coordinate.longitude];
}
}
- (void)locationManager:(CLLocationManager*)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
switch (status) {
case kCLAuthorizationStatusNotDetermined: {
} break;
case kCLAuthorizationStatusDenied: {
} break;
case kCLAuthorizationStatusAuthorizedWhenInUse:
case kCLAuthorizationStatusAuthorizedAlways: {
[_locationManager startUpdatingLocation]; //Will update location immediately
} break;
default:
break;
}
}
#end
[EDIT]
TO check that app is getting launched after terminated state do this change and hit the run button and change the location of the device from the storyboard
Do this change, and Run the project (Do this in debug mode of a device)
and change location by this and then stick a breakpoint in applicationDidFinishLaunchingWithOptions and you will see the breakpoint is hit, meaning the app was in terminated state but this location change has triggered the OS to launch the application.
Hope could make you understand
I tackled an issue, that GPS services work perfectly on iOS 7, but on iOS 8 I never get the permission request and the method:
- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
never gets called.
My code is here:
#import "Locator.h"
#implementation Locator
- (instancetype) init {
if (self = [super init]) {
// Start up the location manager
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = 3;
// New property for iOS6
if ([self.locationManager respondsToSelector:#selector(activityType)]) {
self.locationManager.activityType = CLActivityTypeFitness;
}
// New method for iOS8
if ([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
}
return self;
}
- (void) startMonitoring:(LocationChangeCallback)callback {
if ([CLLocationManager locationServicesEnabled] && [CLLocationManager significantLocationChangeMonitoringAvailable]) {
// Register an observer for if/when this app goes into background & comes back to foreground
// NOTE: THIS CODE IS iOS4.0+ ONLY.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(switchToLowEnergyMode) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(switchToAccurateMode) name:UIApplicationDidFinishLaunchingNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(switchToAccurateMode) name:UIApplicationWillEnterForegroundNotification object:nil];
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
self.locationUpdateCallback = callback;
if (state == UIApplicationStateActive) {
[self switchToAccurateMode];
} else {
[self switchToLowEnergyMode];
}
}
}
- (void) switchToAccurateMode {
NSLog(#"Accurate");
[self.locationManager stopMonitoringSignificantLocationChanges];
// Find the current location
[self.locationManager startUpdatingLocation];
}
- (void) switchToLowEnergyMode {
NSLog(#"Low Energy");
[self.locationManager stopUpdatingLocation];
// Find the current location
[self.locationManager startMonitoringSignificantLocationChanges];
}
#pragma mark - CLLocationDelegate Methods
- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
// locations contains an array of recent locations, but this app only cares about the most recent
// which is also "manager.location"
if (self.locationUpdateCallback != nil) {
self.locationUpdateCallback(manager.location);
}
}
- (void) locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"Location manager failed with error: %#", error);
if ([error.domain isEqualToString:kCLErrorDomain] && error.code == kCLErrorDenied) {
//user denied location services so stop updating manager
[manager stopUpdatingLocation];
}
}
#end
I also checked under location settings and there was nothing behind my application name. Other applications have ("Always", "Never" or "While Using").
For IOS 8 you have to add NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription key to your plist. Otherwise it does not ask for permission.
add CLLocationManagerDelegate
#implementation Locator <CLLocationManagerDelegate>
- (instancetype) init {
//......
self.locationManager.requestAlwaysAuthorization();
}
I'm trying to get location updates while my app is inactive (user closed the app).
After 2 location updates, the location updates stops launching my app.
Indicator for this is the gray arrow in my app in location services settings.
What I'm trying is combination of startMonitoringSignificantLocationChanges & regionMonitoring.
I tested on iPhone 4 iOS 7.1.1 and location updates stops after 2 updates.
I tested in iPad mini WiFi+Cellular iOS 7.1.1 and location updates stops after 1 update and region monitoring send only 1 location.
Where I'm wrong?
My code:
AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
[RegionMonitoringService sharedInstance].launchOptions = launchOptions;
[[RegionMonitoringService sharedInstance] stopMonitoringAllRegions];
if ( [CLLocationManager significantLocationChangeMonitoringAvailable] ) {
[[RegionMonitoringService sharedInstance] startMonitoringSignificantLocationChanges];
} else {
NSLog(#"Significant location change service not available.");
}
if (launchOptions[UIApplicationLaunchOptionsLocationKey]) {
[self application:application handleNewLocationEvet:launchOptions]; // Handle new location event
UIViewController *controller = [[UIViewController alloc] init];
controller.view.frame = [UIScreen mainScreen].bounds;
UINavigationController *nvc = [[UINavigationController alloc] initWithRootViewController:controller];
dispatch_async(dispatch_get_main_queue(), ^{
appDelegate.window.rootViewController = nvc;
[appDelegate.window makeKeyAndVisible];
});
}
else {
// ...
}
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[defaults synchronize];
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
if ([RegionMonitoringService sharedInstance].launchOptions[UIApplicationLaunchOptionsLocationKey]) {
return;
}
}
- (void)application:(UIApplication *)application handleNewLocationEvet:(NSDictionary *)launchOptions
{
NSLog(#"%s, launchOptions: %#", __PRETTY_FUNCTION__, launchOptions);
if (![launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) return;
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) return;
SendLocalPushNotification(#"handleNewLocationEvet");
}
RegionMonitoringService.h:
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import "ServerApiManager.h"
#interface RegionMonitoringService : NSObject
#property (strong, nonatomic) CLLocationManager *locationManager;
#property (strong, nonatomic) NSDictionary *launchOptions;
#property (strong, nonatomic) NSDate *oldDate;
#property (strong, nonatomic) CLLocation *oldLocation;
+ (RegionMonitoringService *)sharedInstance;
- (void)startMonitoringForRegion:(CLRegion *)region;
- (void)startMonitoringRegionWithCoordinate:(CLLocationCoordinate2D)coordinate andRadius:(CLLocationDirection)radius;
- (void)stopMonitoringAllRegions;
- (void)startMonitoringSignificantLocationChanges;
- (void)stopMonitoringSignificantLocationChanges;
FOUNDATION_EXPORT NSString *NSStringFromCLRegionState(CLRegionState state);
#end
RegionMonitoringService.m:
#import "RegionMonitoringService.h"
static CLLocationDistance const kFixedRadius = 250.0;
#interface RegionMonitoringService () <CLLocationManagerDelegate>
- (NSString *)identifierForCoordinate:(CLLocationCoordinate2D)coordinate;
- (CLLocationDistance)getFixRadius:(CLLocationDistance)radius;
- (void)sortLastLocation:(CLLocation *)lastLocation;
#end
#implementation RegionMonitoringService
+ (RegionMonitoringService *)sharedInstance
{
static RegionMonitoringService *_sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
- (instancetype)init
{
self = [super init];
if (!self) {
return nil;
}
_locationManager = [[CLLocationManager alloc] init];
_locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
_locationManager.distanceFilter = kCLDistanceFilterNone;
// _locationManager.activityType = CLActivityTypeFitness;
_locationManager.delegate = self;
return self;
}
- (void)startMonitoringForRegion:(CLRegion *)region
{
NSLog(#"%s", __PRETTY_FUNCTION__);
[_locationManager startMonitoringForRegion:region];
}
- (void)startMonitoringRegionWithCoordinate:(CLLocationCoordinate2D)coordinate andRadius:(CLLocationDirection)radius
{
NSLog(#"%s", __PRETTY_FUNCTION__);
if (![CLLocationManager regionMonitoringAvailable]) {
NSLog(#"Warning: Region monitoring not supported on this device.");
return;
}
if (__iOS_6_And_Heigher) {
CLRegion *region = [[CLRegion alloc] initCircularRegionWithCenter:coordinate
radius:radius
identifier:[self identifierForCoordinate:coordinate]];
[_locationManager startMonitoringForRegion:region];
}
else {
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:coordinate
radius:radius
identifier:[self identifierForCoordinate:coordinate]];
[_locationManager startMonitoringForRegion:region];
}
SendLocalPushNotification([NSString stringWithFormat:#"StartMonitor: {%f, %f}", coordinate.latitude, coordinate.longitude]);
}
- (void)stopMonitoringAllRegions
{
NSLog(#"%s", __PRETTY_FUNCTION__);
if (_locationManager.monitoredRegions.allObjects.count > 1) {
for (int i=0; i<_locationManager.monitoredRegions.allObjects.count; i++) {
if (i == 0) {
NSLog(#"stop monitor region at index %d", i);
CLRegion *region = (CLRegion *)_locationManager.monitoredRegions.allObjects[i];
[_locationManager stopMonitoringForRegion:region];
}
}
}
}
- (void)startMonitoringSignificantLocationChanges
{
NSLog(#"%s", __PRETTY_FUNCTION__);
[_locationManager startMonitoringSignificantLocationChanges];
}
- (void)stopMonitoringSignificantLocationChanges
{
NSLog(#"%s", __PRETTY_FUNCTION__);
[_locationManager stopMonitoringSignificantLocationChanges];
}
- (NSString *)identifierForCoordinate:(CLLocationCoordinate2D)coordinate
{
NSLog(#"%s", __PRETTY_FUNCTION__);
return [NSString stringWithFormat:#"{%f, %f}", coordinate.latitude, coordinate.longitude];
}
FOUNDATION_EXPORT NSString *NSStringFromCLRegionState(CLRegionState state)
{
NSLog(#"%s", __PRETTY_FUNCTION__);
if (__iOS_6_And_Heigher) {
return #"Support only iOS 7 and later.";
}
if (state == CLRegionStateUnknown) {
return #"CLRegionStateUnknown";
} else if (state == CLRegionStateInside) {
return #"CLRegionStateInside";
} else if (state == CLRegionStateOutside) {
return #"CLRegionStateOutside";
} else {
return [NSString stringWithFormat:#"Undeterminded CLRegionState"];
}
}
- (CLLocationDistance)getFixRadius:(CLLocationDistance)radius
{
if (radius > _locationManager.maximumRegionMonitoringDistance) {
radius = _locationManager.maximumRegionMonitoringDistance;
}
return radius;
}
- (void)sortLastLocation:(CLLocation *)lastLocation
{
NSLog(#"%s, %#", __PRETTY_FUNCTION__, lastLocation);
self.oldDate = lastLocation.timestamp; // Get new date
NSTimeInterval seconds = fabs([self.oldLocation.timestamp timeIntervalSinceDate:self.oldDate]); // Calculate how seconds passed
NSInteger minutes = seconds * 60; // Calculate how minutes passed
if (lastLocation && self.oldLocation) { // New & old location are good
if ([lastLocation distanceFromLocation:self.oldLocation] >= 200 || minutes >= 30) { // Distance > 200 or 30 minutes passed
[[ServerApiManager sharedInstance] saveLocation:lastLocation]; // Send location to server
}
}
else { // We just starting location updates
[[ServerApiManager sharedInstance] saveLocation:lastLocation]; // Send new location to server
}
self.oldLocation = lastLocation; // Set old location
}
#pragma mark - CLLocationManagerDelegate Methods
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
NSLog(#"%s, %#", __PRETTY_FUNCTION__, locations);
CLLocation *lastLocation = (CLLocation *)locations.lastObject;
CLLocationCoordinate2D coordinate = lastLocation.coordinate;
if (lastLocation == nil || coordinate.latitude == 0.0 || coordinate.longitude == 0.0) {
return;
}
[self startMonitoringRegionWithCoordinate:coordinate andRadius:[self getFixRadius:kFixedRadius]];
[self sortLastLocation:lastLocation];
}
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
NSLog(#"%s, currentLocation: %#, regionState: %#, region: %#",
__PRETTY_FUNCTION__, manager.location, NSStringFromCLRegionState(state), region);
}
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
NSLog(#"%s, REGION: %#", __PRETTY_FUNCTION__, region);
[manager requestStateForRegion:region];
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(#"%s, REGION: %#", __PRETTY_FUNCTION__, region);
[self stopMonitoringAllRegions];
[self startMonitoringRegionWithCoordinate:manager.location.coordinate andRadius:[self getFixRadius:kFixedRadius]];
CLLocation *lastLocation = manager.location;
CLLocationCoordinate2D coordinate = lastLocation.coordinate;
if (lastLocation == nil || coordinate.latitude == 0.0 || coordinate.longitude == 0.0) {
return;
}
[self sortLastLocation:manager.location];
}
#end
EDIT 1:
I did a a lot of real time tests with car with several devices (iPhone 5s, iPad mini, iPhone 4) after several tests I came to this:
In one case, iPad mini & iPhone 4 stops updating location after several minutes when app is not running and the little arrow become gray.
When WiFi was off, the accuracy was terrible and locations updated rarely.
EDIT 2:
OK, after a lot of driving and walking around and testing it it works like a charm so far.
I managed to make it work, combining significantLocationChanges & region monitoring, always register a geofence around my current location and always starting significant location changes when new UIApplicationLaunchOptionsLocationKey come.
Note that turning off wifi make accuracy very low and even sometimes not working.
Any bugs in my code?
From the Apple docs it can be seen that updates are not sent more frequently than every 5 minutes and for 500 meters of location change:
Apps can expect a notification as soon as the device moves 500 meters or more from its previous notification. It should not expect notifications more frequently than once every five minutes. If the device is able to retrieve data from the network, the location manager is much more likely to deliver notifications in a timely manner.
You are going to receive the updates even when the app is inactive. Another hint for you might be that oyu can test the location in the simulator instead using a real device, this way you don't have to go outside for testing and can still check your logs too. In the simulator menu, chose Debug --> Location.
I need to keep track of the user location all the time (but not drain the battery).
I understand the only way to get updates after app is terminated is using startMonitoringSignificantLocationChanges.
From Apple's Location Awareness Programming Guide on startMonitoringSignificantLocationChanges:
If you start this service and your application is subsequently
terminated, the system automatically relaunches the application into
the background if a new event arrives. In such a case, the options
dictionary passed to the application:didFinishLaunchingWithOptions:
method of your application delegate contains the key
UIApplicationLaunchOptionsLocationKey to indicate that your
application was launched because of a location event. Upon relaunch,
you must still configure a location manager object and call this
method to continue receiving location events. When you restart
location services, the current event is delivered to your delegate
immediately. In addition, the location property of your location
manager object is populated with the most recent location object even
before you start location services.
I would be glad if someone could demonstrate in the code (give an example) which methods i should use
In the following code i'm tring to :
- start the location manager at appdelegate which strats the signinficant monitor changes update and startupdating.
- in didUpdateToLocation i'm calling stopupdating
- in didFinishLaunchingWithOptions when i check if i got a UIApplicationLaunchOptionsLocationKey in order to know if i'm in the background and launched due to siginificant monitor location update.
- if so, i call startMonitoringSignificantLocationChanges again (not sure why...)
and begin a UIBackgeoundTaskIdentifier for calling startupdating method.
LocationController.m :
+ (LocationController*)sharedInstance {
#synchronized(self) {
if (sharedCLDelegate == nil) {
[[self alloc] init];
}
}
return sharedCLDelegate;
}
- (id)init
{
self = [super init];
if (self != nil) {
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
[self.locationManager startUpdatingLocation];
[self.locationManager startMonitoringSignificantLocationChanges];
}
return self;
}
- (void) startMonitoringSignificantLocationChanges
{
[self.locationManager startMonitoringSignificantLocationChanges];
}
- (void) stopMonitoringSignificantLocationChanges
{
[self.locationManager stopMonitoringSignificantLocationChanges];
}
-(void) start{
[self.locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation{
if ( abs([newLocation.timestamp timeIntervalSinceDate: [NSDate date]]) < 30) {
self.lastLocation = newLocation;
[self updateLocation]; //sending location to server
[self.locationManager stopUpdatingLocation];
}
}
- (void)locationManager:(CLLocationManager*)manager
didFailWithError:(NSError*)error{
[self.locationManager stopUpdatingLocation];
}
AppDelegate.h :
#interface AppDelegate : NSObject <UIApplicationDelegate> {
UIBackgroundTaskIdentifier bgTask;
}
AppDelegate.m :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
id locationValue = [launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey];
if (locationValue) {
[[LocationController sharedInstance] startMonitoringSignificantLocationChanges];
UIApplication *app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
[[LocationController sharedInstance] start]; //startupdating
return YES;
}
else {
[[LocationController sharedInstance] init];
}
}
-(void) applicationDidEnterBackground:(UIApplication *) application
{
NSLog(#"entered background Mode");
}
-(void) applicationDidBecomeActive:(UIApplication *) application
{
NSLog(#"application Did Become Active");
}
Thank you.
Using your classes, this is what I would do.
In your AppDelegate.m, when your app is in the foreground or background, I'd move the CLLocationManager to run in the foreground / background to match. The reason I'm doing this is because if the CLLocationManager is not moved to the background when the app is in the background, no location updates are sent to the CLLocationManager's callback
- (void)applicationDidEnterBackground:(UIApplication *) application {
[[LocationController sharedInstance] stop];
[[LocationController sharedInstance] startMonitoringSignificantLocationChanges];
NSLog(#"entered background Mode");
}
- (void)applicationDidBecomeActive:(UIApplication *) application {
[[LocationController sharedInstance] stopMonitoringSignificantLocationChanges];
[[LocationController sharedInstance] start];
NSLog(#"application Did Become Active");
}
So lets say your app then moves to the background, and after awhile, iOS decides it's using too much memory and kills your app.
A few minutes later, iOS then receives a location update, and respawns your app, letting it know it respawned due to a location service. You then need to re-start the background location service, as it will be the only chance your app has to do so.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
[[LocationController sharedInstance] startMonitoringSignificantLocationChanges];
}
return YES;
}
Oh, and one last change, I'm not sure why in your locationManager:didUpdateToLocation:fromLocation: method you're stopping the location service, as when you do that no more updates come through. Just leave it running, then every time a location change comes through you can send that to the server.
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
if ( abs([newLocation.timestamp timeIntervalSinceDate: [NSDate date]]) < 30) {
self.lastLocation = newLocation;
[self updateLocation]; //sending location to server
}