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.
Related
I have a location app that needs to get accurate location periodically. Currently I am getting constantly getting location in didUpdateLocation but I only ever log the location every 5 seconds. I am interested in a solution that gets accurate location periodically or on signification change. I would like either or both of these scenarios:
(by very accurate, I need 10m of desired accuracy)
Get a very accurate location every 5 seconds
Notify/callback if user moves a threshold ( eg moves 5 - 10 meters)
The app needs to work when backgrounded as well and location must still be logged if user switches to another app.
I was considering turning on/off location every 5 seconds but was not sure if that is the best practice. I also know there is also allowDeferredLocationUpdatesUntilTraveled but I believe that only applied to backgrounded mode. I would appreciate a solution that saves battery when the app is in use and in background mode. Please share your solutions and best practices for my use case.
I did write an app using Location services, app must send location every 10s. And it worked very well.
Just use the "allowDeferredLocationUpdatesUntilTraveled:timeout" method, following Apple's doc.
Steps are as follows:
Required: Register background mode for update Location.
Create LocationManger and startUpdatingLocation, with accuracy and filteredDistance as whatever you want:
-(void) initLocationManager
{
// Create the manager object
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
_locationManager.delegate = self;
// This is the most important property to set for the manager. It ultimately determines how the manager will
// attempt to acquire location and thus, the amount of power that will be consumed.
_locationManager.desiredAccuracy = 45;
_locationManager.distanceFilter = 100;
// Once configured, the location manager must be "started".
[_locationManager startUpdatingLocation];
}
To keep app run forever using "allowDeferredLocationUpdatesUntilTraveled:timeout" method in background, you must restart updatingLocation with new parameter when app moves to background, like this:
- (void)applicationWillResignActive:(UIApplication *)application {
_isBackgroundMode = YES;
[_locationManager stopUpdatingLocation];
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[_locationManager setDistanceFilter:kCLDistanceFilterNone];
_locationManager.pausesLocationUpdatesAutomatically = NO;
_locationManager.activityType = CLActivityTypeAutomotiveNavigation;
[_locationManager startUpdatingLocation];
}
App gets updatedLocations as normal with "locationManager:didUpdateLocations:" callback:
-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
// store data
CLLocation *newLocation = [locations lastObject];
self.userLocation = newLocation;
//tell the centralManager that you want to deferred this updatedLocation
if (_isBackgroundMode && !_deferringUpdates)
{
_deferringUpdates = YES;
[self.locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:10];
}
}
But you should handle the data in then "locationManager:didFinishDeferredUpdatesWithError:" callback for your purpose
- (void) locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error {
_deferringUpdates = NO;
//do something
}
NOTE: I think we should reset parameters of LocationManager each time app switches between background/forgeround mode.
Hopefully this should help
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 three points, but really only the first one is the most important
In order to preserve battery life, I'm trying to have the locationManager turn on just long enough to get the user's location and then shut off. What is the best means to do so?
I don't always need to know the user's location, but having a relatively accurate location when the user hits search is important (within perhaps a couple dozen city blocks, 10,000 meters, maybe even less accurate).
I'm sending a request to a server and then getting the results, and after THAT delay, I need a more accurate version of the user location (within 100 meters is fine).
I don't know how much of this is too nitpicky, but if the last two points are possible/efficient battery-wise, then please let me know how to do it!
You can easily stop tracking location when your CLLocationManager's delegate receives a location update meeting your desired accuracy.
To start updating location, do something like:
CLLocationManager *locationManager = [CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
[locationManager startUpdatingLocation];
Then implement the appropriate CLLocationManagerDelegate method:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *foundLocation = [locations lastObject];
if (foundLocation && foundLocation.horizontalAccuracy < kYourDesiredAccuracyInMetres)
{
[manager stopUpdatingLocation];
//Do whatever else with the location you've established
}
}
You should be able to tweak this meet your requirements, by checking the accuracy of the returned locations returned and either stopping the updates or letting them continue (if you need better accuracy).
It's also a good idea to set a timer when you start updating location, and stop updates if you haven't found a location within a set amount of time. You should also implement the locationManager:didFailWithError: delegate method to check whether you can access location services at all.
LocationManager could be stopped after location information of user has been retrieved .
CLLocationManager* locationManager = [ [ CLLocationManager alloc] init];
locationManager.delegate = self; //we must implement the protocol
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.headingFilter = kCLHeadingFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
//To turn on gps (if it isn't on already)
[locationManager startUpdatingLocation];
//To turn gps off (if no other apps are listening)
[locationManager stopUpdatingLocation];
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.
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.