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.
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'm using the delegate method:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
To get the current and old location, I used:
CLLocation *newLocation = [locations lastObject];
self.currentUserLocation = newLocation;
if(self.oldLocation == nil)
{
self.oldLocation = newLocation;
return;
}
EDIT 1: I can get the old location now.
But I always have a negative speed when I use [newLocation speed]; = -1
The device used is an iPhone 4s. Do you have an idea ?
Also, for the locationManager, I used kCLLocationAccuracyBestForNavigation and kCLDistanceFilterNone. I can see on the map my current location moving.
EDIT 2:
I finally achieve the issue with the speed using this method:
- (CLLocationSpeed)speedTravelledFromLocation:(CLLocation*)fromLocation;
{
NSTimeInterval tInterval = [self.timestamp timeIntervalSinceDate:fromLocation.timestamp];
double distance = [self distanceFromLocation:fromLocation];
double speed = (distance / tInterval);
return speed;
}
This method returns the speed calculated from the distance and time deltas between self and fromLocation.
I found this method in this repository: https://github.com/100grams/CoreLocationUtils
Hope it will help someone ;)
EDIT 3
Got it! It was because I'm testing on simulator! I tested on a device and the speed using [newLocation speed] is correct!
Thank you for your help.
Regards,
Lapinou.
Negative speed usually means you don't have a GPS signal.
Why are you expecting the location callback to have 2 or more locations?
This method:
(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
Usually delivers you just one location, except if for some reason multiple locations arrived before that method was called, but you usually will get just one location.
The problem is that you are assuming that that method gives you your previous location along with the new one, and it's not like that.
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 am using CLLocationManager to monitor regions in iOS. I set up the delegate in the normal way:
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
self.locationManager.delegate = self;
and implement several delegate functions including:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"GeoFence: didEnterRegion");
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(#"GeoFence: didExitRegion");
}
Then I start monitoring a region with:
CLLocationCoordinate2D coordinates = CLLocationCoordinate2DMake(lat, lng);
CLRegion *grRegion = [[CLRegion alloc] initCircularRegionWithCenter:coordinates radius:radius identifier:[NSString stringWithFormat:#"grRegion%i", 1]];
CLLocationAccuracy acc = kCLLocationAccuracyNearestTenMeters;
[self.locationManager startMonitoringForRegion:grRegion desiredAccuracy:acc];
with appropriate values for lat (51.116261), lng (-0.85300) and radius (200). After building and running the app in the iPhone 5.1 simulator, I use the location injection tool in Xcode 4.3 to move between a distant location to my target location and back again. Under this procedure, the didEnterRegion event correctly triggers but the didExitRegion never does. I also receive other delegate callbacks such as didUpdateToLocation. I have varied the pattern of simulated movements, used the simulator's location setting tool rather than Xcode's, and tried the same tests running the app on an iPhone 4S with iOS 5.1. All tests have the same result.
I have ensured WiFi is enabled as suggested in other threads and looked through other questions on related topics in vain. Any suggestions as why one of the region transition events would fire, but not the other?
I had this same issue in 5.1. I filed a radar with Apple as a bug. It would trip the first event, in or out, but no other events after that.
From what I can see, this issue has been resolved in the iOS 6 beta. So look for it to be resolved when iOS 6 goes live.
Radar: 11715223