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.
Related
In my iOS app I turned on location services in background and also set on always. I also set up region monitoring for every 500 meters so if iOS kills my app in background then it will wake up using region monitoring.
But I found one major issue in updating location. I disabled location services of iOS and re enable it, my app is still in background but it's location icon showing disable and other app which is not even in background shows enable. Please see attached screenshot.
If any one knows about this then please guide me.
I think you have wrong concept, the arrow unfilled means that the app is using geofences, if you scroll to the bottom in that list you can read the legend as you can se in this screenshot
Hope this helps you
In app delegate.m,
- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions
{
[self setUpLocationManager];
return YES;
}
#pragma mark - CLLocationManager Delegate
-(void)setUpLocationManager
{
LocationManager=[[CLLocationManager alloc]init];
LocationManager.delegate=self;
LocationManager.desiredAccuracy=kCLLocationAccuracyBest;
[LocationManager requestWhenInUseAuthorization];
[LocationManager startUpdatingLocation];
}
-(void)locationManager:(CLLocationManager )manager didUpdateLocations:(NSArray<CLLocation > *)locations
{
CLGeocoder *GioCoder=[[CLGeocoder alloc]init];
[GioCoder reverseGeocodeLocation:LocationManager.location completionHandler:^(NSArray<CLPlacemark > Nullable placemarks, NSError * Nullable error)
{
if (error !=nil)
{
}
else
{
CLPlacemark *place=[placemarks lastObject];
NSLog(#"%#",place);
NSString *Path=[[NSBundle mainBundle]pathForResource:#"countries" ofType:#"json"];
NSError *Error;
NSData *JsonData=[[NSData alloc]initWithContentsOfFile:Path options:NSDataReadingMappedAlways error:&Error];
NSMutableArray *DataArray=[[NSMutableArray alloc]init];
if (Error==nil)
{
NSError *err;
DataArray=[NSJSONSerialization JSONObjectWithData:JsonData options:NSJSONReadingMutableContainers error:&err];
}
for (int i=0; i<DataArray.count;i++)
{
if ([[[DataArray objectAtIndex:i]valueForKey:#"code"] isEqualToString:place.ISOcountryCode])
{
// Insert your code here
[LocationManager stopUpdatingLocation];
break;
}
}
}
}];
}
-(void)locationManager:(CLLocationManager )manager didFailWithError:(NSError )error
{
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusRestricted)
{
NSLog(#"Location not enabled in your device");
}
else
{
}
}
Just as Reiner said in his answer, your understanding is wrong. If you have an empty purple arrow it means your geofence is set correctly.
If another app is staying filled purple. It could be because of multiple reasons:
It has pausesLocationUpdatesAutomatically set to false and is always tracking.
It has pausesLocationUpdatesAutomatically set to true but hasn't yet paused long enough at a location to trigger a pause which would technically turn that filled purple into either gray or empty purple (which means the app is continuing tracking using significant location changes or regionMonitoring or visits monitoring)
AFAIK if you region is setup for 500meters then it's better to use SignificantLocation Changes. Because you can use cellTower information. For regionMonitoring app turns it's location Tracking on and off every few minutes to make sure it's been exited the app or not.
VERY IMPORTANT NOTE
Apple documents on this are very confusing. It took me a whole week to figure it out!
Region Monitoring or significant location Tracking or visit Monitoring will only launch app in the background if and only if your app was terminated. If your app is launched again then right from the DidFinishLaunching you can startLocationUpdates.
If your app was suspended or for some reason still in the background you would only get the didExitRegion callback. And AFIAK from the callback you're not able to startUpdateLocations. This is a very vague text from WWDC.
Finally, you must start your location updates while in the
foreground. If you don’t start your updates in the foreground, you
won’t get this special behavior. So what happens if you do start your
updates in the background? Well, first off, your app will probably
need AlwaysAuthorization since your app won’t be considered in use at
that time. Furthermore, Core Location won’t take any action to
ensure your app continues to run. So if you have background runtime
for some reason, and you decide to start a location session, you
might get some updates, but you might also get suspended before you receive all the information you had hoped to receive.
WWDC 2016: Session 716
I don't know why Apple is saying might...it could be something that varies based on battery/cell coverage/ usage patterns / iOS version. It's obviously something they don't want us developers know how. They want to force us to go for other ways.
The only way for you to get your app into terminated state is either if you user manually kills the app or you wait for a very very long time ie wait for any or all other background tasks to finish + then once app is suspended wait for it to become terminated and then exit the region to launch app and be able to start tracking Locations
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.
I am trying to get a users current location using CLLocationManager. It is all working fine however there is a bit of a delay while the app is finding the location and I need to wait for it to complete before allowing the user to send it. Is there some sort of completion handler for [locationManager startUpdatingLocation]; of should I just use a BOOL variable to keep track of it?
You can use CLLocationManagerDelegate method called
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations
in my IOS app I am implementing geofencing. In the current implementation I am using code like this:
CLRegion* region3 = [[CLRegion alloc] initCircularRegionWithCenter:coordinates radius:100 identifier:#"region3"];
[self.locationManager startMonitoringForRegion:region desiredAccuracy:kCLLocationAccuracyHundredMeters];
and then I am using these delegate methods:
(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
NSLog(#"didenterregion");
}
(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{
NSLog(#"didexitregion");
}
(void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error
{NSLog(#"monitoringDidFailForRegion");}
However, this code works fine only for radius bigger than 100m.
Here are some questions:
Apple says that in ios6 and above a radius between 1 and 400m is supported for devices 4s and above. Since I do not care how long it will take for the message to be viewed (like I do not care to see the message when entering the region but I do care to see at a latter if I have passed from that region once) can I use smaller radius? I am interested in something like 50m radius or smaller? (in some regions even 20m will be needed for my case).
I have also think that. Apple says that up to 20 regions could be supported. What are the advantages/ disadvantages of a sollution like this (I haven't implemented it yet but I want your opinion).
The pseudocode would be like this:
Declare the regions - save them in an array
Do not call start monitoring
And then in the delegate method:
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
for loop in all my regions {
if ([region containsCoordinate: newLocation.coordinate])
code for entering region
}
}
Would it be slower?
Would it consume more battery? (I think monitoring for regions is not battery consuming)?
Could it be more accurate?
Can I have more than 20 regions since I am not registering for monitor?
Thanks in advance.
1.
I suspect the second (didUpdateToLocation:-based) implementation would be more expensive (in terms of battery life) versus the first implementation, simply because you would only run the code in the first (startMonitoringForRegion:-based) implementation if and only if the device came within the radius of one of the (maximum of 20) regions you're tracking.
Whereas in the second implementation, code has to run each time there's a "didUpdateToLocation:" delegate call (which will happen fairly often) and then the code inside the delegate method will get run.
B.T.W., you say the code works fine for a radius of 100m of above, but then the Apple documentation says it should work in iOS6 with "a radius between 1 and 400m is supported for devices 4s and above."
Is your "100m" number your practical result or is it a limitation of the device you're using (something older than an iPhone 4s or an older iOS version)?
2.
Doing anything in the background does consume battery but Apple has optimized CoreLocation for this somewhat (provided you set the correct flag in your app's info.plist file)
3.
I think both will be about equally accurate, except for the fact it may take up to a few minutes for "startMonitoringForRegion:" to report that the region was entered or exited.
4.
And yeah, in the second implementation, you can have as many regions as you want to track. However, the more code you run in the background, the hotter the battery gets and the more likely it is you're draining the battery faster.
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.