I'm making an app that needs to count laps for participants walking around a track. I thought that I could create a small geo-fence around the start point and have the OS let me know when the user entered it, however this doesn't seem to work as well as I was hoping. It seems that it isn't triggered reliably when the walker enters the fence or triggers in other areas around the track. (I'm testing without letting the device go to sleep at this time).
It also seems that the locationManager:didExitRegion method isn't called properly in my testing.
When the user taps the start button, the following method is called:
-(void)startLocationManager {
if ([CLLocationManager locationServicesEnabled] && usingLocationManager) {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.delegate = self;
self.locationManager.distanceFilter = kCLLocationAccuracyBest;
self.locationManager.activityType = CLActivityTypeFitness;
[self.locationManager startUpdatingLocation];
}
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *lastLocation = [locations lastObject];
if (lastLocation.horizontalAccuracy > 10) {
return;
}
// setup a geocode fence and count as user enters fence
self.startRegion = [[CLCircularRegion alloc] initWithCenter:lastLocation.coordinate radius:10 identifier:#"startPosition"];
[self.locationManager stopUpdatingLocation];
[self.locationManager startMonitoringForRegion:self.startRegion];
}
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
lapCount++;
[self updateLapDisplay];
}
Am I doing something wrong or can I not use CoreLocation in such a fashion?
A geofence won't work well for this. You can't rely on it to trigger accurately or reliably. Use a location manager and its delegate in the normal way and trigger manually when the user comes to within 10 or 20 meters, then reset the trigger when they're 50 or 100 meters away from it.
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 am trying to get user's location in app's terminated state. i am doing this by startMonitoringSignificantLocationChanges but it's giving location after 3km or after 5 min. so can't create a route properly. please guide me how to do this.
if (_anotherLocationManager)
[_anotherLocationManager stopMonitoringSignificantLocationChanges];
self.anotherLocationManager = [[CLLocationManager alloc]init];
_anotherLocationManager.delegate = self;
_anotherLocationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
_anotherLocationManager.activityType = CLActivityTypeOtherNavigation;
if(IS_OS_8_OR_LATER) {
[_anotherLocationManager requestAlwaysAuthorization];
}
[_anotherLocationManager startMonitoringSignificantLocationChanges];
I solved my query myself...
Create a locationManager object and alloc it like this
self.locationManager = [[CLLocationManager alloc]init]; // initializing locationManager
_locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation; // setting the accuracy
[self.locationManager requestAlwaysAuthorization];
if([self.locationManager respondsToSelector:#selector(allowsBackgroundLocationUpdates)]) {
[self.locationManager setAllowsBackgroundLocationUpdates: YES];
}
self.locationManager.distanceFilter = 20.0;
[self.locationManager startMonitoringSignificantLocationChanges];
self.locationManager.activityType=CLActivityTypeAutomotiveNavigation;
[self.locationManager startUpdatingLocation];
self.locationManager.pausesLocationUpdatesAutomatically = YES;
self.locationManager.delegate = self;
Now set location manager delegate method.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
{
if (newLocation.speed>7){
// here you got location, i settled speed 7 for my convenience.
}
if (newLocation.horizontalAccuracy <= self.locationManager.desiredAccuracy) {
//Desired location Found
[self.locationManager stopUpdatingLocation ];
}
}
You must have to write this stopUpdatingLocation, else your battery consumption will increase so high.
Use the Geofencing to achieve the same.
Create a Geofence of 200mtr radius.
By default, notifyOnExit would be true
Implement the delegate didExitRegion of LocationManager
For termination state, the app would be lunched with UIApplication.LaunchOptionsKey.location in didFinishLaunchingWithOptions.
Create an instance of Location Manager object on location launch key, you obtain the region at which the location exited and keep creating the 200mtr fence on every exit.
You're not going to get 200 meter granularity or continuous tracking with significant location monitoring.
Have you seen these notes in the docs:
The significant-change location service delivers updates only when
there has been a significant change in the device’s location, such as
500 meters or more.
If GPS-level accuracy isn’t critical for your app and you don’t need
continuous tracking, you can use the significant-change location
service.
I'm trying to make applications, which ranges CLBeaconRegion.
I watched video from WWDC and presenter said, that I should call startMonitoringForRegion and, then user is inside region, startRangingBeaconsInRegion. I tried.
if (!_locationManager) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
CLBeaconRegion *targetRegion = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:UUIDString] identifier:identifier];
targetRegion.notifyEntryStateOnDisplay = YES;
targetRegion.notifyOnEntry = YES;
targetRegion.notifyOnExit = YES;
[_locationManager startMonitoringForRegion:targetRegion];
[_locationManager startUpdatingLocation];
But it's not sending nothing to delegate. Beacons is working.
If I call just
[_locationManager startRangingBeaconsInRegion:region];
Applications finds all my beacons around me.
Should I just call second method or I'm incorrect?
Have you any suggestions?
The most likely explanation is that you are not waiting long enough to get a call to your delegate. When you are NOT ranging, it takes up to 15 minutes to get callbacks to the delegate methods below. When you are ranging, it takes only 1 second.
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
If you wait the 15 minutes, I suspect you will get the callbacks as expected. The reason it is so much faster when you are ranging is because iOS does constant bluetooth scans when ranging is enabled to look for iBeacons. When you are not ranging, it slows down these scans to save battery. See more info in the blog posts below:
http://developer.radiusnetworks.com/2013/11/13/ibeacon-monitoring-in-the-background-and-foreground.html
http://developer.radiusnetworks.com/2014/03/12/ios7-1-background-detection-times.html
Hi I am implementing Location services in my app. First I have to know my Coordinates to get the distance between some places that I have in a list and the device. Then if I go into a place I can make a check in, so, I need to get coordinates again, and the problem is here. Second time I try to get coordinates, the method -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations is not called.. and I can not get new Coordinates.
My manager is located in a NSObject sublcass with this code:
(id)init {
if ( self = [super init] ) {
if ([CLLocationManager locationServicesEnabled])
{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
[locationManager startUpdatingLocation];
}
}
return self;
}
-(void) checkLongLatitudeAgain {
[locationManager startUpdatingLocation];
}
#pragma mark Delegates de CLLocationManager
//
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
NSLog(#"LON%f", manager.location.coordinate.longitude);
NSLog(#"LAT:%f", manager.location.coordinate.latitude);
NSTimeInterval howRecentNewLocation = [newLocationeventDate timeIntervalSinceNow];
if (manager.location.horizontalAccuracy <= 100.0 && howRecentNewLocation < -0.0 && howRecentNewLocation > -20.0){
//Usar coordenada
[self.delegate getLocationForCheckIn:manager.location];
[self stopUpdatingLocation:#"Fins"];
}
}
// ---------------------------------------------------------------------------------------------------------------------
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
//
if ([error code] != kCLErrorLocationUnknown) {
[self stopUpdatingLocation:NSLocalizedString(#"Error", #"Error")];
}
//
}
// ---------------------------------------------------------------------------------------------------------------------
- (void)stopUpdatingLocation:(NSString *)state {
//Detenemos la lectura del GPS
[locationManager stopUpdatingLocation];
locationManager.delegate = nil;
NSLog(#"Stop gps");
//
}
I call the class when the list of places is open, and also when inside a place the user press checkIn button. Both times I do it with this code:
WPLocationManager *location = [[WPLocationManager alloc]init];
[location checkLongLatitudeAgain];
You are creating a new manager every time:
WPLocationManager *location = [[WPLocationManager alloc]init];
[location checkLongLatitudeAgain];
That new manager is not assigned to any delegate.
You need to use the previous manager you have created and assigned to your delegate, something like:
[locationManager checkLongLatitudeAgain];
You can check the documentation at http://developer.apple.com - https://developer.apple.com/library/ios/documentation/userexperience/conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html
In particular you can check the Starting the Standard Location Service and Starting the Significant-Change Location Service sections. You have to use the startMonitoringSignificantLocationChanges or startUpdatingLocation method of CLLocationManager, cache your location somewhere and update it only when a new location is received, otherwise like it is stated in the documentation: "If a location update has already been delivered, you can also get the most recent location data directly from the CLLocationManager object without waiting for a new event to be delivered".
i dont know why you are initiating your location manager again again, also even if you some how manage to solve current problem but it's not proper way of dealing with location manage based applications.I had been in trouble previously when i was working on location based app. the best approach for location based app is singleton.
apple forum discussion
you can find
this
and this very helpful.
just an advice, :)
Thanks.
In iOS8 for me I had to call [locationManager stopUpdatingLocation]; before calling [locationManager startUpdatingLocation] to start getting updates second time and it works for me.
{
...
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
NSLog(#"myLocation1: %#",[locations lastObject]);
myLocation = [locations lastObject];
NSLog(#"myLocation2: %#",[locations lastObject]);
[manager stopUpdatingLocation];
[self doSomethingWithLocation];
}
Currently I'm in the location 40.000,40.000.
I'm closing my app and change location to 10.000,10.000
When entering the app again and running [locationManager startUpdatingLocation]; my log will show:
myLocation1: <+40.00000000,+40.00000000>
myLocation2: <+40.00000000,+40.00000000>
If I'll trigger [locationManager startUpdatingLocation]; again my log will show:
myLocation1: <+10.00000000,+10.00000000>
myLocation2: <+10.00000000,+10.00000000>
How can I call didUpdateLocations once and still get the current location?
Should I use another delegate?
I guess I could place stopUpdatingLocation inside doSomethingWithLocation and run doSomethingWithLocation after some sort of delay in order for the right location to be updated but I'm sure that's not the way it's meant to be.
Thanks
Leave the location manager running for a while (e.g. 30 seconds), setting a timer to tell it to stop. The location manager updates are like pancakes, the first one you get isn't always the best.
The first update you are seeing is likely a "stale" location, which was determined many minutes ago when location services were last powered up. Or it may be a very inaccurate location determined using cell-tower positioning, for example. If you just need to get the device's current location, using Core Location directly requires a good deal of code because you must handle these cases. (The CLLocationManager API appears to be built for apps that need continuous location updates, like turn-by-turn GPS navigation apps.)
Instead of using CLLocationManager directly, I suggest you take a look at using an open source component such as INTULocationManager which will handle all of this work for you and make it trivially simple to request one or more discrete requests for the device's current location.
In this case you should check timestamp of location. User does not move on such distances so quickly.
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *location = [locations lastObject];
if(fabs([location.timestamp timeIntervalSinceNow]) < 5)//seconds
{
myLocation = location;
manager.delegate = nil;
[manager stopUpdatingLocation];
[self doSomethingWithLocation];
}
}