locationManager: didEnterRegion: triggered without location change - ios

I have an app that monitors some regions (geofencing) that each region represent a store, the app notifies the user whenever he approaches to a store.
For some reason, the app sends a notification every around 20 minutes when the user is already inside the region's circle.
It's all working fine, but when the user is inside a region for a long time the app will keep notifying him until he'll exit the region.
Any idea why is it happening? Thank you!

When you create a locationManager and call startUpdatingLocation, it starts giving you co-ordinates of your simulator or device continuously, with a very slight differences in the co-ordinate's values. You need to call stopUpdatingLocation.
For your case you need to stopUpdatingLocation when user exist the region or you can record the last location like this:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *newLocation = [locations lastObject];
[locationManager stopUpdatingLocation];
}

We have the same thing happening. We just keep track of the region coordinate and when the new one comes in we have a method to determine if the new coordinate is 'significantly different' from the region we are in. If it is not, then we ignore it. If it is, then we take our next appropriate action. I hope that helps!
* UPDATE *
Here is how we determine that there was enough change to take action:
- (BOOL) radiiAreSignificantlyDifferent:(CLLocationDistance) newRadius oldRadius:(CLLocationDistance)oldRadius
{
// radii are in kilometers
return (newRadius > oldRadius + 1.5 || newRadius < oldRadius - 1.5) ? YES : NO;
}
- (BOOL) locationsAreSignificantlyDifferent:(CLLocation*) newLocation oldLocation:(CLLocation*)oldLocation
{
BOOL different = NO;
if (oldLocation == nil) {
different = YES;
}else{
// have we moved at least a quarter mile?
different = ([newLocation distanceFromLocation:oldLocation] > 400.0)? YES : NO;
}
return different;
}

Related

How can I get high accuracy location every 5 seconds or when user moves a threshold without draining the battery?

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

CLLocationManager location monitoring not working properly

I have an app that will notify the user every time he approaches to one of my client's stores. There are more than 20 stores, so I have a function that takes the user's location and finds the 20 nearest stores to him and start monitoring the location of these stores, every time the user moves, the app finds the 20 nearest stores again, removes the previous stores from monitoring and start monitoring the new ones.
For some reason, it doesn't work, I'll be happy if one of you (or more :)) will help me to find the problem, Thanks!!
myCode (scroll to see the full code):
Note: the CLLocationManager created on the AppDelegate.m and it's delegate is this class (UIViewController).
-(void)sortClosestStores
{
[self.allStores sortUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
CLLocation *location1=[[CLLocation alloc] initWithLatitude:((Store*)obj1).geoPoint.latitude longitude:((Store*)obj1).geoPoint.longitude];
CLLocation *location2=[[CLLocation alloc] initWithLatitude:((Store*)obj2).geoPoint.latitude longitude:((Store*)obj2).geoPoint.longitude];
float dist1 =[location1 distanceFromLocation:self.locationManager.location];
float dist2 = [location2 distanceFromLocation:self.locationManager.location];
if (dist1 == dist2) {
return NSOrderedSame;
}
else if (dist1 < dist2) {
return NSOrderedAscending;
}
else {
return NSOrderedDescending;
}
}];
if (self.twentyClosestStores==nil) {
self.twentyClosestStores=[NSMutableArray array];
}
if (self.previousTwentyStores==nil) {
self.previousTwentyStores=[NSMutableArray array];
}
self.previousTwentyStores=self.twentyClosestStores;
self.twentyClosestStores=[NSMutableArray array];
for (int i = 0; i < 20; i++) {
[self.twentyClosestStores addObject:[self.allStores objectAtIndex:i]];
}
}
-(void)startMonitoringClosestStores
{
if (![CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]]) {
NSLog(#"Monitoring is not available for CLCircularRegion class");
}
for (Store *currentStore in self.twentyClosestStores) {
CLCircularRegion *region=[currentStore createCircularRegion];
[self.locationManager startMonitoringForRegion:region];
}
}
-(void)stopMonitoringStores
{
for (Store *currentStore in self.previousTwentyStores) {
CLCircularRegion *region=[currentStore createCircularRegion];
[self.locationManager stopMonitoringForRegion:region];
}
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
if (self.allStores!=nil) {
[self sortClosestStores];
[self stopMonitoringStores];
[self startMonitoringClosestStores];
}
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"Entered"); //Not called even when the user enters one of the regions.
}
Can you please help me? Thanks!
I'm pretty new at CoreLocation myself but I would think that it is not a good idea to call stopMonitoringForRegions and startMonitoringForRegions in didUpdateLocations.
Since you're monitoring regions, the didEnterRegion delegate is what you will be interested in. That will give you the 'hey, I arrived at the X store' event, and in there is where you would probably want to call the code that you currently have in your didUpdateLocations.
You will want to setup CoreLocation probably in your AppDelegate, so you might have something like (sorry about it being Swift, that's what I'm working in right now):
locationManager.delegate = self
// auths:
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
// config:
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.allowsBackgroundLocationUpdates = true
locationManager.activityType = CLActivityType.AutomotiveNavigation
// start:
locationManager.startMonitoringSignificantLocationChanges()
Then you would have your code:
if (self.allStores!=nil) {
[self sortClosestStores];
[self stopMonitoringStores];
[self startMonitoringClosestStores];
}
Note: I don't think it matters if you call startMonitoringSignificantLocationChanges before or after adding monitored regions, I haven't gotten quite that far in my code yet.
didUpdateLocations is more for when you want to track location e.g. tracking a bicycle ride or jogging session.
Additional explanation:
OK, I think I understand the issue now. There are two aspects to what you want to accomplish:
being notified when the user enters a store's region
dynamically recalculating the nearest N stores as the device moves
My previous answer was geared towards the first issue.
Regarding the second issue, dynamically recalulating nearest N, the code in your didUpdateLocations will not be called unless you tell the location manager to startUpdatingLocation. Just off the top of my head:
// configure the location manager in AppDelegate:
// You will need to experiment with these two properties,
// you probably don't want to use kCLLocationAccuracyBestForNavigation.
// Maybe kCLLocationAccuracyKilometer would be sufficient.
locmgr.distanceFilter = n
locmgr.desiredAccuracy = m
locmgr.pausesLocationUpdatesAutomatically = true
locmgr.startUpdatingLocation()
// this is the delegate that will receive the events due to locmgr.startUpdatingLocation
locationManager:didUpdateLocation {
// Unless you have a specific need for it, I would refactor so that
// you don't need self.PreviousTwentyStores:
[self stopMonitoringStores];
[self sortClosestStores];
[self startMonitoringClosestStores];
}
locationManager:didEnterRegion {
// you are in the region surrounding one of the stores.
}
Alternately, consider just setting a timer and waking the app every N seconds or minutes to recalculate the nearest stores.
As I understand the various aspects of CoreLocation
startUpdatingLocation -> didUpdateLocations -> stopUpdatingLocation
is (in one sense) entirely separate from:
startMonitoringForRegion -> didEnterRegion | didExitRegion -> stopMonitoringForRegion
Additionally, startUpdatingLocation was never called so your didUpdateLocation was never called.

iOS: When I try to get CLLocation by click, there is most of the time a "big" lag

I'm first using the CoreLocation framework. I have a table and by button click a new location should be added and the distance to all entries in the table should be shown and updated all the time. That is why I have a BOOL saveNewLocation which is set to Yes when the button is clicked. Because the updates need to happen still all the time in the background, but when the button is clicked only a new entry is added.
At the moment I have this in my viewDidLoad:
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
// Check for iOS 8. Without this guard the code will crash with "unknown selector" on iOS 7.
if ([self.locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)]) {
[self.locationManager requestWhenInUseAuthorization];
}
[self.locationManager startUpdatingLocation];
And this is my delegate method:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
self.currentLocation = newLocation;
if(self.saveNewLocation){
[PointOfInterest addPointOfInterest:newLocation withAddress:#"" andNotes:#"" inManagedObjectContext:self.cdh.context];
self.saveNewLocation = NO;
}
[self updateAllDistances];
}
And this my button:
- (IBAction)addLocationClicked:(id)sender {
self.saveNewLocation = YES;
}
But the problem at the moment is that when you click this button, there is sometimes a big lag and nothing happens. Sometimes immediately a new location is added. How can I avoid this lag and instantly add a new location by click?
The time interval between update calls to the location manager delegate is variable, so the behavior you're experiencing is expected.
CLLocationManager has a property called location which returns the last known location of the user (or nil if you've never used the Location Manager in the app).
Instead of waiting for the LocationManager to update, grab the last known location of the user instead:
- (IBAction)addLocationClicked:(id)sender {
CLLocation *location = self.locationManager.location;
if (location && [NSDate timeIntervalSinceReferenceDate] - location.timeStamp.timeIntervalSinceReferenceDate < 60 * 10){
//Do something with the location if the location manager returns a location within the last 10 minutes
} else {
self.saveNewLocation = YES;
}
}
If the app has never asked for it's location, you may get nil, in which case you'll have to wait for the locationManager to update. But otherwise, you can just grab the last known location. You can also check whether the location was update recently by checking the timeStamp on the location object.
You also may want to set a state flag indicating that the app should wait for a location update when the location manager is first used. When you first start up the LocationManager, you can't really know how up-to-date that location is. But once the manager begins updating the delegate, you can be reasonably certain the location manager holds a fairly up-to-date location.

Always negative speed iOS

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.

Can't get CLLocationManager to stop tracking

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.

Resources