I am working on the Location update in my application
When the app is with internet connection location update (didUpdateLocation) is called. But when the app goes offline, Location update delegate is not called in iOS 14 . I am not getting the latitude and longitude.
Any idea why this is happening or is this the process of location manager?
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
if([locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)]){
[locationManager requestWhenInUseAuthorization];
}else{
[locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations
{
CLLocation *location = [locations lastObject];
lattitude = [NSString stringWithFormat:#"%f", location.coordinate.latitude] ;
longitude = [NSString stringWithFormat:#"%f",location.coordinate.longitude];
[locationManager stopUpdatingLocation];
}
How to get continuous location details when app is killed/Terminated ? startMonitoringSignificantLocationChanges method firing the didUpdateLocations delegate method after 500 meters. But i want location updates in kill mode for every 10 meters. Or i want to relaunch the application in background automatically and start the location updates in background.Currently i have the working code in background.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
[[LocationManager sharedLocarionManager] updateAccuracy:YES];
[[LocationManager sharedLocarionManager].locationManager requestAlwaysAuthorization];
[[LocationManager sharedLocarionManager].locationManager stopUpdatingLocation];
[[LocationManager sharedLocarionManager].locationManager startUpdatingLocation];
[[LocationManager sharedLocarionManager].locationManager stopMonitoringSignificantLocationChanges];
[[LocationManager sharedLocarionManager].locationManager startMonitoringSignificantLocationChanges];
}
return YES;
}
LocationManager.m
- (id)init {
self = [super init];
if(self != nil) {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
self.locationManager.distanceFilter = 100; // meters
self.locationManager.delegate = self;
}
return self;
}
-(void)updateAccuracy:(BOOL)trackingAccuracy {
if (trackingAccuracy) {
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = 10.0 ; // meters
self.locationManager.allowsBackgroundLocationUpdates = YES;
} else {
self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
self.locationManager.distanceFilter = 100; // meters
}
[ self.locationManager startUpdatingLocation];
[self.locationManager startMonitoringSignificantLocationChanges];
}
There is a work around. Though you cannot get continuous location when your app is killed but you can send silent push notification via server to your user to get the update about its location. You will get 30 second window when the app receives silent push notification. In that short time span, you have to perform your task.
How to fetch the location for only one time in ios app.
After getting your location, stop update location manager and also release locationManager because you don't need locationManager anymore.
[self.locationManager stopUpdatingLocation];
self.locationManager.delegate = nil;
self.locationManager = nil;
In ios 9 and above we have a method [locationManagerInstance requestLocation]. But this will take almost ten secs to call back the delegate methods since the location is latest and best.
In another way (for earlier ios9 versions), you could still use the old method [locationManagerInstance startUpdatingLocation] to get the immediate location and also you could validate the timestamp for the best accuracy.
Here is the code that I'm using for getting the location. Create a global reference for locationManager instance.
#interface ViewController ()
{
CLLocationManager *locationManager;
}
Implement these utility methods in your.m file
-(BOOL)canUseLocationManager{
if(([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse) || ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedAlways)){
return YES;
}
return NO;
}
-(void) getCurrentLocation {
locationManager.delegate = self;
// ios 9 and above versions only
//[locationManager requestLocation]; // This may take more time when compare to alternate method
// ios 2 and later versions can use this method
[locationManager startUpdatingLocation];
}
-(void) stopGettingCurrentLocation {
[locationManager stopUpdatingLocation];
}
Implement location service delegates
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{
if([CLLocationManager authorizationStatus] != kCLAuthorizationStatusNotDetermined){
[self doneCheckingAccessStatusForLocation];
}
}
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
[manager stopUpdatingLocation];
locationManager.delegate = nil;
/* Clean the locationManager instance if you don't need*/
//locationManager = nil;
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
[manager stopUpdatingLocation];
locationManager.delegate = nil;
CLLocation* location = [locations lastObject];
NSDate* eventDate = location.timestamp;
/* Implement your business logics here */
/* Clean the locationManager instance if you don't need*/
//locationManager = nil;
}
Final methods to create the location manager instance and initiate fetching location
-(void)checkForLocationService{
if(!locationManager){
locationManager = [[CLLocationManager alloc] init];
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
locationManager.delegate = self;
if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined){
[locationManager requestWhenInUseAuthorization];
}else{
[self doneCheckingAccessStatusForLocation];
}
}
-(void)doneCheckingAccessStatusForLocation{
if([self canUseLocationManager]){
[self getCurrentLocation];
}
}
call the checkForLocationService method and implement your logics on the success and failure delegate methods.
[self checkForLocationService];
This is my code in viewDidLoad:
_locationManager = [[CLLocationManager alloc] init];
[_locationManager setDelegate:self];
if ([[UIDevice currentDevice].systemVersion hasPrefix:#"8"]) {
[_locationManager requestAlwaysAuthorization];
}
[_locationManager startMonitoringSignificantLocationChanges];
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBestForNavigation];
[_locationManager setDistanceFilter:50];
[_locationManager startUpdatingLocation];
[_locationManager startUpdatingHeading];
This is how my didUpdateLocations method looks like:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation* newLocation = [locations lastObject];
CLLocation* location = [locations lastObject];
NSDate* eventDate = location.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
if (abs(howRecent) < 50.0 && newLocation.horizontalAccuracy > 0) {
// If the event is recent, do something with it.
NSLog(#"latitude %+.6f, longitude %+.6f\n",
location.coordinate.latitude,
location.coordinate.longitude);
}
}
The problem is that the blue current location dot moves around inaccurately and doesn't stay still.I observe this behavior when I am stationary and not moving around. I am trying to filter the locations in the didUpdatelocations method but I don't know why it is not working. Please guide me as to how should I go about filtering it. Or do you think this wrong behavior is because of something else?
Have you tried reducing setDistanceFilter:50? Per the framework reference, this means that the location only updates after the device has moved 50 meters. The default value is kCLDistanceFilterNone to be notified of all movements.
https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/index.html#//apple_ref/occ/instp/CLLocationManager/distanceFilter
This my code......
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
location_updated = [locations lastObject];
NSLog(#"updated coordinate are %#",location_updated);
latitude1 = location_updated.coordinate.latitude;
longitude1 = location_updated.coordinate.longitude;
self.lblLat.text = [NSString stringWithFormat:#"%f",latitude1];
self.lblLon.text = [NSString stringWithFormat:#"%f",longitude1];
NSString *str = [NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/geocode/json?latlng=%f,%f&sensor=false",latitude1,longitude1];
url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
connection = [NSURLConnection connectionWithRequest:request delegate:self];
if (connection)
{
webData1 = [[NSMutableData alloc]init];
}
GMSMarker *marker = [[GMSMarker alloc] init];
marker.position = CLLocationCoordinate2DMake(latitude1,longitude1);
marker.title = formattedAddress;
marker.icon = [UIImage imageNamed:#"m2.png"];
marker.map = mapView_;
marker.draggable = YES;
}
This method is call multiple times which i don't want.....
While allocating your LocationManager object you can set the distanceFilter property of the LocationManager. Distance filter property is a CLLocationDistance value which can be set to notify the location manager about the distance moved in meters. You can set the distance filter as follows:
LocationManager *locationManger = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.distanceFilter = 100.0; // Will notify the LocationManager every 100 meters
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
The easiest way:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
[manager stopUpdatingLocation];
manager.delegate = nil;
//...... do something
}
The manager can't find your didUpdateLocations method without the delegate reference :-D
But don't forget to set it again before using startUpdatingLocation
Add some restriction there. For timespan between locations and accuracy
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *newLocation = locations.lastObject;
NSTimeInterval locationAge = -[newLocation.timestamp timeIntervalSinceNow];
if (locationAge > 5.0) return;
if (newLocation.horizontalAccuracy < 0) return;
// Needed to filter cached and too old locations
//NSLog(#"Location updated to = %#", newLocation);
CLLocation *loc1 = [[CLLocation alloc] initWithLatitude:_currentLocation.coordinate.latitude longitude:_currentLocation.coordinate.longitude];
CLLocation *loc2 = [[CLLocation alloc] initWithLatitude:newLocation.coordinate.latitude longitude:newLocation.coordinate.longitude];
double distance = [loc1 distanceFromLocation:loc2];
if(distance > 20)
{
_currentLocation = newLocation;
//significant location update
}
//location updated
}
I have similar situation. You can use dispatch_once:
static dispatch_once_t predicate;
- (void)update
{
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined &&
[_locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)]) {
[_locationManager requestWhenInUseAuthorization];
}
_locationManager.delegate = self;
_locationManager.distanceFilter = kCLDistanceFilterNone;
_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
predicate = 0;
[_locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
[manager stopUpdatingLocation];
manager = nil;
dispatch_once(&predicate, ^{
//your code here
});
}
locationManager.startUpdatingLocation() fetch location continuously and didUpdateLocations method calls several times,
Just set the value for locationManager.distanceFilter value before calling locationManager.startUpdatingLocation().
As I set 200 meters(you can change as your requirement) working fine
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 200
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
You can use a static variable to store the latest location timestamp and then compare it to the newest one, like this:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
[manager stopUpdatingLocation];
static NSDate *previousLocationTimestamp;
CLLocation *location = [locations lastObject];
if (previousLocationTimestamp && [location.timestamp timeIntervalSinceDate:previousLocationTimestamp] < 2.0) {
NSLog(#"didUpdateLocations GIVE UP");
return;
}
previousLocationTimestamp = location.timestamp;
NSLog(#"didUpdateLocations GOOD");
// Do your code here
}
Swift 5 :
If you are looking for a solution in swift.
I tried the accepted answer but it didn't work for me. I tried the below solution by checking the time duration between locations. if it is less than 10 seconds then it will return and the location handler will not update.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
let locationAge = -location.timestamp.timeIntervalSinceNow
if locationAge > 10.0 { //10 seconds
return
}
if location.horizontalAccuracy < 0 {
return
}
self.currentLocation = location
print("Location :- \(location.coordinate)")
//location updated
}
Write this method when ever you want to stop updating location manager
[locationManager stopUpdatingLocation];
for the time constraint, i did not understand code from accepted answer, posting a different approach. as Rob points out "When you first start location services, you may see it called multiple times". the code below acts on the first location, and ignores the updated locations for first 120 seconds. it is one way to address orginal question "How to stop multiple times method calling of didUpdateLocations".
in .h file:
#property(strong,nonatomic) CLLocation* firstLocation;
in .m file:
// is this the first location?
CLLocation* newLocation = locations.lastObject;
if (self.firstLocation) {
// app already has a location
NSTimeInterval locationAge = [newLocation.timestamp timeIntervalSinceDate:self.firstLocation.timestamp];
NSLog(#"locationAge: %f",locationAge);
if (locationAge < 120.0) { // 120 is in seconds or milliseconds?
return;
}
} else {
self.firstLocation = newLocation;
}
// do something with location
You could set a flag (Bool). When you instantiate your locationsManager set flag = true then when locationManager:didUpdateLocations returns inside a code block
that you want to run only once set flag = false. This way it will only be run the once.
if flag == true {
flag = false
...some code probably network call you only want to run the once
}
locations manager will be called multiple times but the code you want to execute only once, and I think that is what you are trying to achieve?
you can write :
[manager stopUpdatingLocation];
manager = nil;
in didupdatelocation delegate
A few things worked for me:
setting location manager nil while didUpdateLocations after calling manager.stopUpdatingLocation()
I will suggest setting the location manager to nil is not a good approach.because CLLocationManager sometimes give accurate location in 3-4 times.
What I will suggest change accuracy to kilometers that's worked for me
clLocationManager?.distanceFilter = 1000
clLocationManager?.desiredAccuracy = kCLLocationAccuracyKilometer