CLLocationManager stopUpdatingLocation doesn't stop - ios

Below are some snippets of my code
BOOL checkingForLocation;
.
.
.
- (IBAction)LocationButtonTapped:(id)sender {
[locationManager startUpdatingLocation];
checkingForLocation = YES;
}
.
.
.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
[locationManager stopUpdatingLocation];
// locationManager = nil;
if (checkingForLocation) {
checkingForLocation = NO;
NSLog(#"here");
// fetch data from server using location and present view controllers
}
}
didUpdateLocations is called multiple times because locationManager is continually updating the location per startUpdatingLocation. My app has a bug where the code within if(checkingForLocation) runs multiple times and renders the view multiple times causing undesired results.
1) Why is it that stopUpdatingLocation doesn't stop further calls to didUpdateLocations?
2) Furthermore, if the checkingForLocation has already been set to NO why does the subsequent calls to didUpdateLocations still treat checkingForLocation as YES. Is there a race condition there?
Through searching other SO questions, I found that setting locationManager = nil will stop the extra calls to didUpdateLocations, which it does fix the issue in my case, but no real explanation. It seems that stopUpdatingLocation should take care of that. Any insight is much appreciated.
Thank you,

didUpdateLocations: can be called frequently and at any time because of its async nature and optimisations :
Because it can take several seconds to return an initial location, the
location manager typically delivers the previously cached location
data immediately and then delivers more up-to-date location data as it
becomes available. Therefore it is always a good idea to check the
timestamp of any location object before taking any actions. If both
location services are enabled simultaneously, they deliver events
using the same set of delegate methods.
Actually, if you uncomment locationManager = nil; it should help, but if you want to use your checkingForLocation property, you should make the assignment synchronised. #synchronized(self) is the easiest way in this case.

Related

Latidude and Longitude are incorrect first time app is launched ios

I am using CLLocationManager to get the user's current location. I need the user's current location because I am using the Open Weather Api to display the weather data wherever the user is. My problem is that the first time I open the app the lat and long value are 0, 0. But then if I run the app again it works fine. So only on the first launch of the app incorrect lat and long values are used. I do all my set up in viewDiDLoad, here is the code...
locationManager = [[CLLocationManager alloc] init];
[locationManager requestAlwaysAuthorization];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
[self->locationManager requestWhenInUseAuthorization];
[locationManager startUpdatingLocation];
I don't know what I am doing wrong. Is it something to do with getting the lat and long in the view controller instead of App Delegate?
Here is how I am getting the location values:
locationManager.location.coordinate.latitude
locationManager.location.coordinate.longitude
It's happening because when you launch your app first time Location manager asks for user permission to access current location and when user allows - It takes little time in process of fetching current location and you're accessing coordinates before completion of that process.
When you launch your app again Location manager detects that you already gave the permission so it gives you current location coordinate very quickly.
So the solution is - you've to wait until it completes location update opt. You can put some kinda delay there using dispatch OR can use Location manager delegate method didUpdateUserLocation OR if your UIViewController isn't the initial view controller - in this case you should implement this process in AppDelegate, use your code in didFinishLaunching, so when you'll open your view controller (whatever navigation flow you're using) location details will be there cause in meantime location manager will track your location.
Hope it'll help you!
You should check horizontalAccuracy of the CLLocation object for a negative value and do not use the lat/lon value if that is the case. It is highly likely that when you see the lat/lon values are 0/0, the GPS hasn't secured a location lock.

locationManagerShouldDisplayHeadingCalibration not called

I have a simple iOS app which uses the heading. However as the accuracy is very bad (10 to 25 degrees), I implemented the locationManagerShouldDisplayHeadingCalibration function. However it never gets called. Here is my code:
...
locmanager = [CLLocationManager new];
[locmanager setDelegate:self];
[locmanager setDesiredAccuracy:kCLLocationAccuracyBest];
[locmanager setHeadingFilter:kCLHeadingFilterNone];
[locmanager setHeadingOrientation:CLDeviceOrientationLandscapeLeft|CLDeviceOrientationLandscapeRight];
if ([CLLocationManager locationServicesEnabled])
{
[locmanager startUpdatingLocation];
[locmanager startUpdatingHeading];
}
...
- (void)locationManager:(CLLocationManager*)manager didUpdateHeading:(CLHeading*)newHeading
{
// This one gets called
}
- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager
{
// This one does NOT get called
NSLog ("Here");
return YES;
}
What should I do to get the compass calibration ? Thanks.
Apple Official Documentation:
Core Location may call this method in an effort to calibrate the onboard hardware used to determine heading values. Typically, Core Location calls this method at the following times:
The first time heading updates are ever requested
When Core Location observes a significant change in magnitude or inclination of the observed magnetic field
So it's entirely possible that your calibration if already fine and need not be set. Note that this method will never get called in simulator.
Try using MKMapView with MKUserTrackingMode.FollowWithHeading and see if calibration window appears. Your delegate might still not get called, because MKMapView calls it through it's own private location manager delegate.
You may also try turning off Compass Calibration in Settings -> Location Services-> System Services on your device.
see CLLocationManager requestAlwaysAuthorization method
add required keys to your info.plist file (now required)
NSLocationAlwaysUsageDescription
bla bla why we use GPS.
NSLocationWhenInUseUsageDescription
bla bla why we use GPS.
must be setup on main UI thread !!

When should I stop updating location manager?

I have an app that makes a call to get the user's location:
-(void)getLocation{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
}
//SET USER LOCATION
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
self.userLocation = [locations lastObject];
NSLog(#"location in IntroVC %f, %f", self.userLocation.coordinate.latitude, self.userLocation.coordinate.longitude);
}
My question is, because that NSLog keeps spitting out a new location infinitely, when should I stop calling the location? Well I guess its really up to my app's functionality, but doesnt this cause battery drain? If so, I should really look into the best way of stopping the updates.
Your Distance filter of the location manager is set to be kCLDistanceFilterNone. This causes the didUpdateLocations method to be called infinite time.
locationManager.distanceFilter = kCLDistanceFilterNone;
Change this line as
locationManager.distanceFilter = 10;
and try again. Change the value as needed.
So now the didUpdateLocation will not be called infinite times. :)
Hopefully this helps.
Depending on the nature of your app, you may well want to turn location services off when you go into the background or when the screen is locked. These notifications hooks are provided generally in your app delegate file (.m). Yes you are right, location services significantly drain battery and it a highly recommended practice for iOS apps using location services to use it cautiously.
Apple seems to have thought about this and has provided an API that notifies the app only if the user has moved "significantly" Apple the significant change location service. The definition of "moved significantly" varies on various aspects depending on WiFi availability, cell tower availability, GPS availability etc. Luckily all this is obfuscated within this API.
I call this method after updating the location in didupdateloactions, so when my app goes in background it remove location icon from top status bar.

Location-aware iOS app with background support design issue

I have location services enabled for updating locations when app in background, and I also listen for location updates when app in foreground, displaying a map. What should be the best way to design this scenario in iOS? I've thought about some options:
1) Having an instance of a class with a locationManager member that is its delegate itself. Then, in the body of the didUpdateToLocation delegate method, something like:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
if (!background) {
// Perform some processing and notify the view controller which displays the map
// by means of Notification Center
}
else {
appDelegate.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:appDelegate.bgTask];
appDelegate.bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Perform some data processing
// Close background task
if (appDelegate.bgTask != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:appDelegate.bgTask];
appDelegate.bgTask = UIBackgroundTaskInvalid;
}
});
}
}
(Note: I'm not sure if having location services enabled as background service, it is needed to perform the processing of locations as if it were a finite background task...). The instance of such class could be a member of AppDelegate, and start listening for locations when entering background or calling the instance from the viewModel displaying the map. So, AppDelegate would have a reference to the instance managing the locationManager, and the map viewController would have a reference to AppDelegate, or communicate also by means of Notification Center.
2) Having the locationManager member directly in AppDelegate, and being the delegate itself.
Would it be better to encapsulate de locations listening and management in a different class, or handle it directly in AppDelegate? Having into account that I have to be able to listen for locations and perform some tasks both in foreground and in background.
Thanks in advance
I think your best strategy is to have a singleton instance of a class that is a locationManager delegate. This instance would be responsible to filter whatever comes back from locationManager and ultimately assigns the location to one of its properties, let's call it myLoc.
This solves your centralized logic issues where the location needs post-processing after being acquired.
Second, in order to get the correct UIViewControllers and other class instances to do what they need when the location is updated, I suggest you use Key-Value Observing (KVO) on myLoc. I assume you know how that works. The reason I suggest KVO is that you need to trigger changes in multiple areas of the code based on the location being changed, and KVO is made for that kind of pattern.
I would like to add more to #Rikkles answer by saying that you can create a BaseViewController, which is a subclass of UIViewController and has an instance of your singleton locationManager class too.
This approach allows us to move all the redundant code like, checking reachability, showing activity indicator,getting user location, etc to only one place and will be available to all other classes implementing it.

Why is my CLLocationManager not responding to startMonitoringForRegion?

At the bottom (in AppDelegate.m) you can see my CLLocationManager delegate methods, none of them get called. I'm using a GPX file, but even if the regions do not get entered or exited, the delegate method didStartMonitoringForRegion should be called.
SomeOtherClass.m
AppDelegate appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.locationManager startMonitoringForRegion:regionToMonitor desiredAccuracy:kCLLocationAccuracyNearestTenMeters];
AppDelegate.h
#interface AppDelegate : UIResponder <UIApplicationDelegate,CLLocationManagerDelegate>
#property (nonatomic, retain) CLLocationManager *locationManager;
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if (locationManager==nil) {
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
}
return YES;
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(#"Did enter region");
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(#"Did exit region");
}
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"Fail");
NSLog(#"%#", [error description]);
}
-(void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
NSLog(#"Did start monitoring for region: %#", region.identifier);
}
First of all you should add the following Location manager delegate method, and see if for some reason the region monitoring registration failed:
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
NSLog(#"%#",error);
}
Second, the region monitoring is a system shared resource.
The documentation states that it allows you limited number of regions to monitor (not specifying any number unfortunately) and mentioning that if another app register additional regions to monitor, some of your apps monitored regions, might be discarded.
Third, region monitoring is not using any GPS technology. It only uses the cellular antenna of your network operator and whenever you change a cell tower, it fires an system event that loops through all monitored regions and see if a region is within the new area you are now located.
This means that you should expect less accuracy in the service and therefore you should increase the radius you are setting for a region.
Finally if your app is completely terminated and NOT suspended, then your app will receive in the app delegate inside the:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
a UIApplicationLaunchOptionsLocationKey in the launchOptions dictionary.
It's your responsibility then to reinitialize your location manager, get the current location and fire a local push notification to the user, within the limited time your application live state has.
As the app is not going to run normally at that stage, but in a limited background mode state.
Also if you want to test the app in the simulator, you should add a track.gpx file to your project and set the simulator to track mode.
Make sure that the 2 locations you put in the simulator are not very distant (as it might take quite long time for the tracking to complete) and set them in a path, where it will enter your monitored region.
Then see if you get any callbacks.
Do not test it with your device, as you need to actually walk a couple of blocks to see any real interaction with the device :-)
Not entirely sure why it wouldn't receive your callbacks. I'll cover a few things I'm noticing and you can see if they help.
You are not setting the desiredAccuracy or distanceFilter in your location manager. They should default to something, but if your locations in the GPX aren't within the accuracy of the regions, it could just be not getting close enough to trigger.
The method you are using to start monitoring has been deprecated in iOS 6. You can add the accuracy to the location manager and leave that off your call.
It would be helpful to see how you are creating your CLRegion to monitor, regionToMonitor. If it is being created ok as soon as you start monitoring, you should see the hollow purple location arrow show up. You should also receive the delegate call -didStartMonitoringForRegion. If neither of these are showing up, then you probably just have an issue with your location manager setup.
One suggestion would be to create your own location manager class and turn it into a singleton. This will prevent you from accidentally initializing multiple delegates and getting multiple calls. It also gives you one clean class to contain all your callback methods.
I don't see anything wrong with the code you've included, so I'm guessing the problem lies in the code you haven't included. Check to make sure your location manager code is being initialized and make sure your CLRegion is being created correctly. Hope this helps. I'll be happy to update my answer if you include more code and we find out what the true issue is.

Resources