I am creating an app that tells the user whether they are near the destination.
I am calculating the distance between the currentLocation and the destination. I'm doing the calculation inside the didUpdateLocations. It is working but I've seen that there are methods that can deal with that without the need of doing any math.
I am registering the region in the CLLocationManager; however it seems that the methods didExitRegion and didEnterRegion are not been called.
Here are the part of the code where I register the region:
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[self.locationManager startUpdatingLocation];
[self.mySearchBar resignFirstResponder];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
self.distToRemind = 0;
[worldMap removeAnnotations:[worldMap annotations]];
NSLog(#"executou de primeira");
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:[self.mySearchBar text] completionHandler:^(NSArray *placemarks, NSError *error)
{
CLPlacemark *placemark = [placemarks lastObject];
//test
//DefaultAnnotation *annot = [[DefaultAnnotation alloc] initWithCoordinate:placemark.location.coordinate andTitle:#""];
CLRegion *newRegion = [[CLRegion alloc] initCircularRegionWithCenter:placemark.location.coordinate radius:10.0 identifier:#"RegionBoundary"];
DefaultAnnotation *regionAnnotation = [[DefaultAnnotation alloc] initWithCoordinate:newRegion.center andTitle:#""];
[self identifyPlacemark:placemark andSetAnnotation:regionAnnotation];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(regionAnnotation.coordinate, 250, 250);
[worldMap addAnnotation:regionAnnotation];
[worldMap setRegion:region animated:YES];
[self.locationManager startMonitoringForRegion:newRegion];
if (self.boolPushButtonTapped) {
[self pushButtonTapped];
}
}
];
}
Am I doing something wrong here?
Ok, a few things to keep in mind when using the region monitoring ability in iOS.
Regions are going to default to the minimum size no matter what you set the initial radius to. An Apple engineer told me it is 100M for GPS enabled devices. 450M for Wifi only devices that support region monitoring (iPad 3 and new iPod Touches)
Regions you can monitor are a limited commodity. The total number that can be monitored on a single device is limited. Again, an Apple engineer told me it is around 100 regions. Use the delegate methods to make sure your region was added good or bad.
Regions are very useful and have minimal impact on battery life. They also get their own location icon in the status bar. (hollow purple location arrow)
They work very much like every other location API, you need to respond to the delegate methods correctly to interpret the actions that are happening.
Your code looks good but is missing the logic surrounding your CLLocationManagerDelegate. Without a proper delegate to handle the callbacks, you are likely just missing the callbacks ( -didExitRegion/-didEnterRegion).
In my experience, I create a singleton class to handle all of my location manager delegate methods. Make sure you sign up to listen for them. If you include some more code surrounding those delegate calls, I'd be glad to help you more. There are plenty of tutorials out there that should document how to set them up correctly. Good luck.
*Note: I spoke to a location engineer at WWDC this year about a lot of these unknowns surrounding min region size and number of regions. I can confirm the min region size at 100, but not the max number of regions. I haven't had the need as of yet.
Related
I'm trying to test region monitoring, for that I'm getting current location like this:
- (void)startLocationTracking
{
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
// Start location manager
if ([CLLocationManager locationServicesEnabled] && [CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorized) {
locationManager = [LocationTracker sharedLocationManager];
locationManager.delegate = self;
[locationManager startMonitoringSignificantLocationChanges];
}
}
And tracking first location with region monitoring like this:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CLRegion *region = [[CLRegion alloc] initCircularRegionWithCenter:manager.location.coordinate radius:300 identifier:#"first location initializer"];
NSLog(#"0: %#", manager.location);
NSLog(#"1: %#", region);
[manager startMonitoringForRegion:region];
NSLog(#"[locationManager startMonitoringForRegion:%#];", region);
});
}
Then in every exit from current region I'm monitoring the new location like this:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"%s, %#", __PRETTY_FUNCTION__, region);
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(#"%s, %#", __PRETTY_FUNCTION__, region);
NSArray *allRegions = manager.monitoredRegions.allObjects;
if (allRegions.count > 0) {
for (CLRegion *reg in allRegions) {
[manager stopMonitoringForRegion:reg];
}
}
CLLocationCoordinate2D cord = CLLocationCoordinate2DMake(manager.location.coordinate.latitude, manager.location.coordinate.longitude);
CLRegion *regionNew = [[CLRegion alloc] initCircularRegionWithCenter:cord radius:300 identifier:#"new region"];
NSLog(#"region: %#", region);
NSLog(#"regionNew: %#", regionNew);
[manager startMonitoringForRegion:regionNew];
}
I'll explain what I expect to happen:
Register current location in region monitoring.
Notify user exit current region.
On exit log and register again the current location as region.
This doesn't happen.
Where I'm wrong?
I tried on simulator with 'Freeway Drive'.
UPDATE:
Tested and work, due to Apple bug in geofencing, app will support only 7.1+, pretty bad but I don't have an another idea.
I think the way you implement the region monitoring might cause some problems.
Here are the reasons:-
Inside the startLocationTracking method, your locationManager is a local object that does not extend over the life cycle of that method. It also means that every time you call startLocationTracking, there will be a new locationManagerobject that is allocated with a new block of memory.
To solve this problem: You should use a singleton locationManager that is a shared locationManager for the entire life cycle of the Application.
I believe you should not startMonitoringForRegion inside the delegate method -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:. The reason is, if you call startLocationTracking more once, there will be more than one locationManager. Multiple locationManagers could monitor the same region which may cause multiple notifications.
After you call [manager startMonitoringForRegion:region];, the region will not be monitored immediately. If you do not believe believe me, try the follow code:-
[locationManager startMonitoringForRegion:region];
NSLog(#"%#",locationManager.monitoredRegions);
You will find out that the region that you just monitored will not be inside the locationManager.monitoredRegions. Since this is handled on the iOS level, so, I think it might need a few minutes for the region to be ready to be monitored.
You should also understand other limitations for Region Monitoring in iOS:-
https://developer.apple.com/library/mac/documentation/CoreLocation/Reference/CLLocationManager_Class/CLLocationManager/CLLocationManager.html
An app can register up to 20 regions at a time. In order to report
region changes in a timely manner, the region monitoring service
requires network connectivity.
In iOS 6, regions with a radius between 1 and 400 meters work better
on iPhone 4S or later devices. (In iOS 5, regions with a radius
between 1 and 150 meters work better on iPhone 4S and later devices.)
On these devices, an app can expect to receive the appropriate region
entered or region exited notification within 3 to 5 minutes on
average, if not sooner.
Note: 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.
I don't know what your app is about, I believe you should redesign the flow of your app. You should try to monitor the region outside of the delegate methods.
For more information about the Singleton LocationManager, you may check out this answer: Background Location Services not working in iOS 7. There is a complete project on GitHub that contains a Singleton LocationManager class that I named as LocationTracker.
You might also want to check out a glitch for Region Monitoring in iOS 7 that I found out a month ago (with a workaround to solve the glitch): Region Monitoring Glitch on iOS 7 - Multiple Notifications at the same time
The most satisfactory answer to the delegates methods (didEnterRegion and didExitRegion) not being called is, apple docs says you will be notified after 3 to 5 minutes after the entry or exit, but in actual testing what I found is you have to wait for approx 7 to 8 minutes.
I tested it for 10 to 15 times and my region radius was around 80 meters. I was scratching my head to see what went wrong. I left the simulator open with the location tracking on (Used a gpx file for location simulation). After 8 minutes didExitRegion was called.
Also if you want to do this in background, you must enable background modes on your target.
I have been developing region monitoring on iOS for about 2 months. Recently we have found a glitch where all the regions within a certain radius (about 4.7KM or 4700 meters) triggered at the same time. The current location of the device is not even close to any of the regions. I am not sure what could have triggered that event. I have searched through StackOverFlow, Apple Developer forums and etc, I haven't find any similar issue with what I am facing.
In the app that I am developing, we are monitoring 8 regions within the City (Kuala Lumpur). On one instance, my colleague found out that there were 4 regions notification triggered on his phone at the same time. Below is the map showing his location, all the monitored regions, the potential radius that triggered the 4 region notifications.
The green marker is the location of the device when receiving the notification.
The blue circle is the potential radius of the device (about 4700 meters) which is covering 4 regions that send the notification to the device.
The red circle is the radius for each of the region.
There are 2 other regions on the map which never send notifications (never cover under blue circle)
Screen shot of the triggered notifications:
Here is my code for location Manager:-
CLLocationManager *locationManager = [LocationTracker sharedLocationManager];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
locationManager.distanceFilter = kCLDistanceFilterNone;
Here is my code for didEnterRegion:-
-(void)locationManager:(CLLocationManager *)manager
didEnterRegion:(CLRegion *)region{
NSString* message = [NSString stringWithFormat:#"Message"];
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (state == UIApplicationStateBackground || state == UIApplicationStateInactive)
{
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.fireDate = [NSDate date];
NSTimeZone* timezone = [NSTimeZone defaultTimeZone];
notification.timeZone = timezone;
notification.alertBody = message;
notification.alertAction = #"Show";
notification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
}
Note: This issue does not happen every time, it only happens once in a while. The 4700 meters is the radius that I came out with after analysing the location of the 4 triggered regions. I am not sure if this is a glitch on my code, on the iOS or there is a problem on the local telco in my country. In the latest version of the app, I am adjusting the distanceFiter to 10 and we are testing it right now to see if this will solve the issue.
//locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.distanceFilter = 10;
The method didEnterRegion never return the location of the user, I am not able to filter out the potential bad location with a big radius like the example I show above. What I can do to solve this glitch?
Any developer who is facing the similar issue, please come forward and share your experience and solution to solve this issue (if there is any). Thanks.
I have found a fix for this strange bug. We have tested for over 1 week and so far we haven't see the same bug again. Here is the solution:-
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{
NSLog(#"didEnterRegion");
CLLocation * lastLocation = [manager location];
BOOL doesItContainMyPoint;
if(lastLocation==nil)
doesItContainMyPoint = NO;
else{
CLLocationCoordinate2D theLocationCoordinate = lastLocation.coordinate;
CLCircularRegion * theRegion = (CLCircularRegion*)region;
doesItContainMyPoint = [theRegion containsCoordinate:theLocationCoordinate];
}
if(doesItContainMyPoint){
NSString* message = [NSString stringWithFormat:#"You are now in this region:%#",region.identifier];
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (state == UIApplicationStateBackground || state == UIApplicationStateInactive)
{
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.fireDate = [NSDate date];
NSTimeZone* timezone = [NSTimeZone defaultTimeZone];
notification.timeZone = timezone;
notification.alertBody = message;
notification.alertAction = #"Show";
notification.soundName = UILocalNotificationDefaultSoundName;
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
}
}
I use CLLocation * lastLocation = [manager location]; to get the latest location from the device and use this coordinate to see if it is inside the triggered region with containsCoordinate: method. If it is inside, then only the local notification will trigger.
For the details explanation on this bug and the way to fix it, you may visit: iOS Region Monitoring and Location Manager
HERE is full proof working solution/fix for iOS geo-fencing glitch !
It will both solve problem of receiving wrong multiple events on network switch like wifi to mobile data and vice versa..also it gives you accurate results/notifications with this logic/fix
Basically, LocationManager should be taken as singleton and distanceFilter should be kCLDistanceFilterNone and desiredAccuracy should be kCLLocationAccuracyBestForNavigation (We have taken this for best possible accuracy as we need real background monitoring of location very accurate for real time geofence entry notifications to send to users)
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
CLLocation * lastLocation = [manager location];
BOOL doesItContainMyPoint;
if(lastLocation==nil)
doesItContainMyPoint = NO;
else{
CLLocationCoordinate2D theLocationCoordinate = lastLocation.coordinate;
CLCircularRegion * theRegion = (CLCircularRegion*)region;
//we need to take new instance of given region for which the iOS triggers entry event..Adding 50.0 meters is needed just to make sure that containsCoordinate method of CLCircularRegion works well.Because there is lag/difference measured in our real time field testing that brought us to this conclusion and it works like a charm.If we do not add this 50.0 meters in the fence for which we are getting this event then there is a chance that containsCoordinate might miss the recent point/coordinate to consider in given region...
CLCircularRegion * theCircularRegion = [[CLCircularRegion alloc]initWithCenter:theRegion.center radius:theRegion.radius+50.0 identifier:theRegion.identifier];
doesItContainMyPoint = [theCircularRegion containsCoordinate:theLocationCoordinate];
}
if(doesItContainMyPoint){
NSLog(#"ItContainMyPoint");
//trigger local notification...
}else{
NSLog(#"ItDoesNotContainMyPoint");
//do not trigger local notification...because it is triggered due to switching network(wifi to mobile data and vice versa) Currently user is not at all in the region for which we are getting event of entry
return;
}
}
It's an old question, but let my share my experience with Region Monitoring concerning inaccurate didExit- didEnterRegion calls:
One thing to note is that if you have Wifi turned off on the device, the reliability of geofence on iOS devices deteriorates a lot.
In the case of the asker, you should't just check the .coordinate of the last location to debug, but also the .horizontalAccuracy of the CLLocation. That might reveal a very low accuracy (big possible area) if wifi and GPS is turned off. In the case that you also run GPS location tracking, the accepted answer is actually quite a good answer. If not, the .horizontalAccuracy might be a very large number. Region Monitoring does not seem (in my experience) to listen to GPS updates, so you can't enhance its reliability that way. But having WiFi enabled can. In an urban area that is.
Without Wifi and a GPS trick like accepted answer uses, all the device has is cell tower location. I have found that didExitRegion and didEnterRegion may be unreliable if all the device has to get it's location, are cell towers. This is because the accuracy of cell tower location is of course much lower than wifi. In that case, the low accuracy (big possible area) may mean it's in any of the four regions of the asker. It may even be called multiple times for a single region because it may suddenly think (based on cell towers) that it's a kilometer away, then realise it's not, which can cause didEnterRegion to be called multiple times.
The same goes for didExitRegion. If the accuracy is really low, the system cannot be sure you left the region until several kilometers away (which is one of the commenters's problem)
So if you are not sure your users are using your app having wifi always turned on (which you can't check or assure on iOS), make sure you don't make the regions too small or use the accepted answer with GPS ([locationManager startUpdatingLocation]) to ensure that you're really in that region. In that case, also check that the location is accurate and young enough. Something like:
CLLocation * lastLocation = [manager location];
NSTimeInterval locationAge = -[lastLocation.timestamp timeIntervalSinceNow];
if (lastLocation != nil && locationAge < MAX_AGE && self.currentLocation.horizontalAccuracy <= MAX_ACCURACY) {
CLLocationCoordinate2D theLocationCoordinate = lastLocation.coordinate;
CLCircularRegion * theRegion = (CLCircularRegion*)region;
doesItContainMyPoint = [theRegion containsCoordinate:theLocationCoordinate];
}
But if there is any way you can convince the user to always have Wifi enabled when using the app, do!! It makes it so much more reliable.
I have a class that launches an instance of CLLocationManager. The intent is to use it to get a decent, one-time fix on app launch and then stop location services for the duration (or until some other condition is met which requires a new fix...but that part isn't yet written).
For some reason, even though I call [stopUpdatingLocation], my app still seems to be keeping location services active indefinitely, whether in the background or not. My delegate doesn't receive updates anymore, as expected, but the arrow stays on the status bar. I turned off location services for all other apps to verify that mine was the culprit, and then killed my app manually (which immediately dismissed the arrow).
My code is based on what you find in the Apple docs, with some things added for my purposes. I've read through all the pertinent Apple docs and just can't figure out what I'm doing wrong. All the other answers on the subject address people who are using MKMapView and forget to set "showUserLocation" to NO...I'm not using an MKMapView at all, so that's not my issue. What the heck. Why won't it die?
(_locationManager is, of course my instance of CLLocationManager).
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:
(NSArray*)locations
{
CLLocation* lastLocation = [locations lastObject];
// Ignore old (cached) location
NSDate *date = lastLocation.timestamp;
NSTimeInterval howRecent = [date timeIntervalSinceNow];
if (abs(howRecent) > 30.0)
{
DLog(#"Ignoring cached fix");
return;
}
// Wait for better fix if we have GPS
if (lastLocation.horizontalAccuracy > 60 && _GPSEnabled)
{
DLog(#"Ignoring inaccurate fix on GPS-enabled device");
return;
}
// Accept mediocre fix if we don't have GPS
if (lastLocation.horizontalAccuracy < 300 && _GPSEnabled == NO)
{
_hasPos = YES;
_lastPos = lastLocation.coordinate;
DLog(#"Best fix we're likely to get without GPS: %f %f", _lastPos.latitude,
_lastPos.longitude);
[self stopTracking];
return;
}
// If we have GPS, accept a fix with <60m accuracy
_hasPos = YES;
_lastPos = lastLocation.coordinate;
DLog(#"Geolocator has good fix: %f %f", _lastPos.latitude, _lastPos.longitude);
[self stopTracking];
}
-(void)stopTracking
{
DLog(#"Stopped updating locations");
[_locationManager stopUpdatingLocation];
_locationManager.delegate = nil;
}
Finally found the answer in another question...reprinting it here because it took me a while to stumble on it.
I had to call this:
[_locationManager stopMonitoringSignificantLocationChanges];
Even though I never called startMonitoringSignificantLocationChanges in the first place, seems I had to "un-call" it...strange that it works this way, but as soon as I added that line, location services shut down promptly. Hope this helps someone else.
I´m currently working the the region-feature of the location-manager.
Unfortunately I am unable to get notifications for any border-crossings, which are smaller than 100m.
I created regions with the radius of 20m / 50m or 70m and I always only get a notification, as soon as I cross the 100m-border.
But I would like to have a finer radius - e.g. 20m and also receive a notification for that. Everything works quite well, if I have a range, which is greater than 100m - e.g.
150m. In this case I receive a notification as soon as I enter 150m as I would expect it.
I tried playing around with the "kCLLocationAccuracy"-settings on the LocationManager and the creation of CLRegions, but both does not
seem to have any effect.
This is my code I use:
- (void) initializeLocationManager
{
// Check to ensure location services are enabled
if(![CLLocationManager locationServicesEnabled]) {
[self showAlertWithMessage:#"You need to enable location services to use this app."];
return;
}
if(![CLLocationManager regionMonitoringAvailable]) {
[self showAlertWithMessage:#"This app requires region monitoring features which are unavailable on this device."];
return;
}
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = kCLDistanceFilterNone; // whenever we move
}
- (void) setupGeoFence:(Station*)station
{
CLLocationCoordinate2D centerCoordinate = CLLocationCoordinate2DMake([station.gpsPosition.latitude doubleValue], [station.gpsPosition.longitude doubleValue]);
CLRegion *geofence = [[CLRegion alloc] initCircularRegionWithCenter:centerCoordinate radius:[range doubleValue] identifier:station.name];
[self.locationManager startMonitoringForRegion:geofence desiredAccuracy:kCLLocationAccuracyBest];
}
Does anyone have an idea how to receive notification on a closer boundary? Any help would be highly appriciated.
Thanks
Hamtho
Regions at that size are just not really possible. The location events are primarily triggered by the Wifi surrounding the device. GPS isn't really used unless absolutely necessary. So regions under 50M are really a crap shoot. I have spoken with CoreLocation engineers about this (along with some other odd behaviors surrounding region monitoring) and because of the dependency on Wifi, sizes that small are not recommended. They actually recommend 150M, even though it isn't documented.
If you really need location updates that fine grained, you may need to implement your own checks or fire up the GPS to get a very accurate reading. This comes along with a battery hit, so your mileage may vary. Good luck.
I've been having nothing but problems trying to do region monitoring on iPhone. First of all I have never (not even once) had either didEnterRegion or didExitRegion fire on my iPhone 4 device.
I decided to test on the simulator instead of wasting work hours walking around downtown (although I would like to see this work in a real scenario). First of all, the simulator is way off in terms of accuracy, you'd think it'd be pretty accurate (or am I wrong to have assumed that?).
The following was done in the simulator by changing the location under the Debug menu
I finally got didEnterRegion to fire, though I was about 8 city blocks away from the circle in question. When I exited the region didExitRegion fired about 200 times in a row. Is this a simulator bug? I'm almost ready to give up on this and start calculating this stuff myself because it's getting ridiculous.
Any ideas as to why the accuracy is so bad and why my methods are being fired so many times?
Here's the relevant code:
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *) launchOptions
{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
CLLocationDegrees latitude = 45.50568;
CLLocationDegrees longitude = -73.57033;
CLLocationCoordinates2D centerCoordinate = CLLocationCoordinate2DMake(latitude, longitude);
CLLocationDistance regionRadius = 200.0;
CLRegion *myRegion = [[CLRegion alloc] initCircularRegionWithCenter:centerCoordinate radius:regionRadius identifier:#"aroundWork"];
[locationManager startMonitoringForRegion:myRegion];
return YES:
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"Entered region: %#", region.identifier);
}
-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(#"Exited region: %#", region.identifier);
}
First of all I assume you are testing it on the simulator with a GPX file, as any other way would be wrong.
Now, because I have done extensive research on Region Monitoring:
Region monitoring is not very effective.
It can miss a region enter or exit easily.
We have done a lot of tests and a lot of walks and driven with our cars through the city multiple times to test.
Sometimes it works beautifully, sometimes it fails miserably.
I suggest you play around a bit with the region border (radius) settings and do REAL tests (walk or drive around certain points) to see which behaves better.
Our tests showed that smaller values behave better (100 to 200 meters radius) and also that the region monitoring behaves better when you are actually driving, rather than walking.
Remember it uses the cell tower antennas to position you, which is far less accurate than gps.