location manager - only getting 1 update per second - ios

I'm timing speed runs in cars. I'm using the usual location manager code.
viewDidLoad
CLLocationManager *locMgr;
if (locMgr == nil)
locMgr = [[CLLocationManager alloc] init];
[locMgr setDelegate:self];
[locMgr requestWhenInUseAuthorization];
[locMgr setDistanceFilter:kCLDistanceFilterNone];
[locMgr setDesiredAccuracy:kCLLocationAccuracyBestForNavigation];
[locMgr startUpdatingLocation];
DELEGATE
- (void)locationManager:(CLLocationManager *) manager didUpdateLocations:(NSArray<CLLocation *> *) locations
{
(process update)
}
QUESTION
Even though I'm using the most precise DistanceFilter and Desired Accuracy, I'm only getting one update per second while the car is moving, even at 60 mph. I checked the number of locations returned to the delegate every second and it's always one. Can anyone explain this? It seems to me that I should be getting a constant stream of updates at 60 mph. Any help would be greatly appreciated.
Thank you.

I'm new to iOS development as I'm just starting to port my speedometer app from Android, and this question came up in my search for locations update rate. Hopefully it's ok that I can't answer the question but merely add info I've learned from Android.
So, iOS is probably different but Android sends out location updates whenever the (for example) GPS service gets a new location, and from experience 99.99% of Android phones and tablets have sensors that send updates once per second. If sensors of the same quality and energy efficiency are used by Apple (idk) then I'd expect 1Hz updates are the best we'll get.
My Android app can use Bluetooth GPS receivers at ~10Hz so I want my app to report the latest location whenever it's received, so timers won't really work for me.
Your question has helped me as I now know when location is updated.

Related

iOS GPS tracking app that runs all the time

I am trying to make an app to track the user GPS all the time, this app is a kind of car GPS tracker to get the location of driver all the time and send it to server.
I have tried to add "location updates" to the "background modes" but the app will automatically suspends after 10 mins when going into background.
Is there a way to make this app run all the time and get the GPS location?
You have two options here:
1) Regular location tracking. This type of tracking works with kCLAuthorizationStatusAuthorizedWhenInUse and kCLAuthorizationStatusAuthorizedAlways authorizations. When CLLocationManager started tracking location once it will receive location updates in delegate method locationManager:didUpdateLocations:. App can go to suspended state, but when location manager receive new location app goes to background state and handles new location in delegate method. How to setup location manager:
- (void)viewDidLoad {
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
// Setup location tracker accuracy
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
// Distance filter
self.locationManager.distanceFilter = 50.f;
// Assign location tracker delegate
self.locationManager.delegate = self;
// This setup pauses location manager if location wasn't changed
[self.locationManager setPausesLocationUpdatesAutomatically:YES];
// For iOS9 we have to call this method if we want to receive location updates in background mode
if([self.locationManager respondsToSelector:#selector(allowsBackgroundLocationUpdates)]){
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
}
[self.locationManager startUpdatingLocation];
}
2) Signification location changes tracking. This type of tracking works only with kCLAuthorizationStatusAuthorizedAlways authorization. It receives new location only each 500 meters, so distance filter and desiredAccuracy don't work here. App can go to suspended state, and even can be terminated by system, but when location updates app goes to background state and receives location in delegate method locationManager:didUpdateLocations:.If app was terminated by system, it will be relaunched in background with UIApplicationLaunchOptionsLocationKey key in launch options in didFinishLaunchingWithOptions app delegate method. How to setup this type on tracking:
- (void)viewDidLoad {
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
// Assign location tracker delegate
self.locationManager.delegate = self;
// For iOS9 we have to call this method if we want to receive location updates in background mode
if([self.locationManager respondsToSelector:#selector(allowsBackgroundLocationUpdates)]){
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
}
[self.locationManager startMonitoringSignificantLocationChanges];
}
You should notice that both of these methods does not guarantee that your application does not go to suspended state.
Also, if app was terminated by user (for example from app switcher by swipe) location tracking in background will not work.
UPDATE (corresponding to comments)
Here is my code examples that work for me:
For Regular tracking. Run the example, provide access to user location, tap Start button to start location updates. To test locations in simulator choose in simulator menu Debug > Location > Freeway Drive. Now you can push app to background by home button (Command+Shift+H). Leave simulator for more than 10 minutes, and all this time app will receive locations. When you return to app you will see red pins on the map.
For Significant changes. Run the app and test by the same way as for previous example.
Monitoring Significant changes can be started only by method [self.locationManager startMonitoringSignificantLocationChanges];
UPDATE (iOS 11)
Changes to location tracking in iOS 11
iOS 11 also makes some major changes to existing APIs. One of the affected areas is location tracking. If your app only uses location while the app is in the foreground, as most apps do, you might not have to change anything at all; however, if it’s one of those apps that continuously track user’s location throughout the day, you should probably book some time this summer for making some changes in how you do the tracking and testing possible usage scenarios.
follow this link: https://mackuba.eu/2017/07/13/changes-to-location-tracking-in-ios-11/
I am sure it is useful for the author because the question was asked in Feb 2016 and I am giving an answer in June 2019. This answer maybe is useful for other users.
Recently, I was working with the same requirement. After 2-3 week hard work, I did it. For other users, I create a helper class for it. Which is available in GitHub.
Please use HSLocationManager for your requirement. I have achieved the same requirements in one of my project
Location manager that allows getting background location updates every
n seconds with desired location accuracy.
Advantage:
OS will never kill our app if the location manager is currently
running.
Give periodically location update when it required(range is between 2 -
170 seconds (limited by max allowed background task time))
Customizable location accuracy and time period.
Low memory consumption(Singleton class)
In reply to comment 1 in the solution (I can't comment anywhere yet): you didn't seem to solve the problem as your app gets suspended and doesn't update the location any more after 10 minutes.
I had the same issue: I had set setAllowsBackgroundLocationUpdates to YES, and I had the NSLocationAlwaysUsageDescription key in my Info.plist, but my App also used to stop tracking location after 10 minutes.
I solved it by adding both NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription to the Info.plist file so it looks like this:
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs to use your location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs to use your location</string>
set it.it take power of battery but your application run in Background.OS does not Suspend your App
[self.locationManager setPausesLocationUpdatesAutomatically:NO];

iOS iBeacon Monitoring triggered randomly

I have a very strange behavior in my iOS application. While the beacon monitoring is working fine in most cases, there are sometimes hours where the didEnterRegion and didExitRegion events are fired multiple times in a row. The beacon itself is right next to the phone (about 15cm), so there shouldn't be an didExitRegion at all. I know that it is possible to loose a beacon signal what triggers an exit, but it's triggered about 5 times in 3 minutes (both enter and exit - so 10 calls). This is a very rare behavior and seems to be randomly.
The beacons are from Estimote and set via Estimote App to a broadcasting power of -20dBm what should be about 3.5 meters/ 12ft (according to Estimote App) and an advertising interval of 2000ms.
My init of CLLocationManager
if (! _locationManager) {
_locationManager = [[CLLocationManager alloc] init];
if ([_locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
// Not available in iOS 8
_locationManager.allowsBackgroundLocationUpdates = YES;
}
// For iOS 8
if ([_locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[_locationManager requestAlwaysAuthorization];
}
_locationManager.pausesLocationUpdatesAutomatically = NO;
_locationManager.delegate = self;
}
That's how i start monitoring
- (void)startMonitoringForBeaconRegions {
for (CLBeaconRegion *currentBeaconRegion in _beaconRegions) {
//default should be YES
currentBeaconRegion.notifyOnEntry = YES;
currentBeaconRegion.notifyOnExit = YES;
[_locationManager startMonitoringForRegion:currentBeaconRegion];
}
}
Does anyone have a similar behavior with their beacons and maybe a solution ??
Kind regards,
Kyaak
I observed a very occasional similar behavior in my demo/test apps. I experimented with setting beacons to max Tx power and 100 ms advertising interval, and it didn't help, which leads me to believe it might be a bug in Core Location itself. I've seen this erratic behavior with both Estimote SDK (which builds on top of Core Location), but also in pure Core Location.
It ordinarily takes 30 seconds of not "hearing" a beacon for an exit event to trigger, but in situations like these, I was observing enters and exits happening in rapid succession, like it completely ignored the 30-second timer, so I believe this is not actually caused by iOS not being able to "hear" the beacon.
I've also experimented with monitoring for CLCircularRegion instead of CLBeaconRegion (so, a GPS geofence instead of a beacon geofence) set up around my apartment and office, and I've observed the same, sporadic, erratic enters and exits.
Unfortunately, I haven't managed to figure out a fix/workaround for that yet. Maybe if we collectively start opening bug reports with Apple, they'll look into that.

ios deferred location updates fail to defer

I am looking into using deferred location updates for an iOS activity tracker, which allows location services in background. I've implemented the suggested code snippets (see below). In Xcode debugging, deferred locations attempt to start a few times until location data comes in at about 1 per second. After that, it claims to succeed in starting deferrals, and the callback for the finish trigger also succeeds after the specified time period expires. However during the time, the location handler still runs once per second. I've read that this is because the phone hasn't deemed itself ready to enter the background, and that testing in Xcode does this. Note, AppDelegate's "didEnterBackground" eventhandler got called immediately when turning off the screen, and resumed when reopening app.
I ran the same code with the phone disconnected as another test, near the window with GPS, screen off, or switching to entirely different apps, and it still never actually defers the updates. I can tell because the networked update still comes in once every 30 seconds, instead of the interval of 120 seconds which is desired in the code sample below.
What else is needed to actually get deferrals to work, since there is no error occurring in starting them and they do get their finish callback? Why do location updates continue at 1 per second even when the app goes to background?
Iphone 5s, IOS 7.1.1
// .h file (partial)
#interface MotionTracker : NSObject<CLLocationManagerDelegate, UIAccelerometerDelegate>
#property (strong, nonatomic) CLLocationManager *locationManager;
#end
// .m file (parial)
- (id) init {
if(self = [super init]){
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.distanceFilter = kCLDistanceFilterNone;
_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
// if set to YES (default), app stops logging location at some point and doesn't resume in any timely fashion, all data points lost in between
_locationManager.pausesLocationUpdatesAutomatically = NO;
_locationManager.activityType = CLActivityTypeFitness;
}
return self;
}
// called early in program after login confirmed
- (void) startCollectingLocation {
[_locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations {
// logs to file when device is not in debug
// always returns 1
NSLog(#"Location update count: %d",[locations count]);
// some code here to handle location updates
// - collect key location day in NSDictionary
// - every N seconds send Network call to server to save (have tried 30 seconds, 15 minutes, 30 minute network intervals). Have also tried turning off network calls completely.
// deferred updates starter
if (!self.deferringUpdates) {
if([CLLocationManager deferredLocationUpdatesAvailable]){
[_locationManager allowDeferredLocationUpdatesUntilTraveled:500 timeout:(NSTimeInterval)120]; // (have also tried large numbers, and "Infinite"
self.deferringUpdates = YES;
NSLog(#"Deferred updates start");
} else {
NSLog(#"Deferred updates not available");
}
}
}
- (void)locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error {
if(!error){
_deferringUpdates = NO;
NSLog(#"Deferred updates: finished");
} else {
_deferringUpdates = NO;
NSLog(#"Deferred updates: %#", [error localizedDescription]);
}
}
If the device is connected to a debugger or on a charger, the device will remain powered (not sleep) and therefore will not enter deferred mode. Deferred mode is a power optimization allowing the device to sleep. If the device is not scheduled to sleep for other reasons, enabling deferred mode will not force it to sleep otherwise. Try your test by ensuring no other apps are using location services, and disconnecting it from a charger with the screen off. After running for some time, plug back in and check your logs, you should see that the device slept and deferred updates.
From Apple's
allowDeferredLocationUpdatesUntilTraveled:timeout: documentation:
Deferred updates are delivered only when the system enters a low power
state. Deferred updates do not occur during debugging because Xcode
prevents your app from sleeping and thus prevents the system from
entering that low power state.
It is also worth noting that deferred updates are only available when locationManager.desiredAccuracy is set to kCLLocationAccuracyBest OR kCLLocationAccuracyBest; locationManager.distanceFilter must also be set to kCLDistanceFilterNone.
From Apple's documentation:
...the location manager allows deferred updates only when GPS hardware is available on the device and when the desired accuracy is set to kCLLocationAccuracyBest or kCLLocationAccuracyBestForNavigation.
and
...the distanceFilter property of the location manager must be set to kCLDistanceFilterNone.
I have been struggling with the same issue, and I may have found an answer that solves this problem for many - at least it solves my problem and gets deferred updates working consistently for me.
I followed all of the steps in this list and no matter what I did, location updates would not defer. It occurred to me that I might have other apps running that were not allowing the system to sleep, so I killed all other apps in the multitasking tray. I ran my sample app again and ... it worked!
But the story doesn't end there. I tried again a little later, and even though there were no other apps running in the multitasking tray I couldn't get location services to defer. Then it occurred to me that I have an app on my phone called "Moves" which manages to keep itself alive even after you manually kill it. I'm not entirely sure how Moves comes magically back to life when you kill it, but it does (perhaps using bluetooth and the app preservation / restoration service). Even though it is alive and tracking your location it doesn't appear in the multitasking tray. I think that only apps that are manually launched appear in the tray - if the OS launches an app it doesn't appear in the tray. But I digress ...
I was able to get deferred location services to work consistently in my app by disallowing Moves to use location services. When I did, Moves complained even though it wasn't in multitasking tray. It seems that if another app is using location services (and not deferring) your app won't defer either.
Hi recently with the iOS 9 GM seed version out,I have seen location update(allowDeferredLocationUpdatesUntilTraveled:timeout:) not getting deferred.The same code used to work in iOS 8.4 and below versions.Its draining my device's battery by a huge margin.
Is there anything we need to explicitly set or mention for iOS 9?Didn't find anything from Apple documentation
Here is the code that I implemented.
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
if (!self.deferringUpdates) {
[self.locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:30];
self.deferringUpdates = YES;
} }
-(void)locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error { // Stop deferring updates self.deferringUpdates = NO;
}
I also set the allowsBackgroundLocationUpdates property but even that didn't help. self.locationManager.allowsBackgroundLocationUpdates=YES;
In iOS 9 and later, regardless of deployment target, you must also set the allowsBackgroundLocationUpdatesproperty of the location manager object to YES in order to receive background location updates. By default, this property is NO, and it should remain this way until a time when your app actively requires background location updates.
https://developer.apple.com/library/prerelease/ios/documentation/Performance/Conceptual/EnergyGuide-iOS/LocationBestPractices.html
Please let me know what additional I need to make
Thanks

When to call [clLocationManager stopUpdatingLocation]

To save power in my app I have decided to use a mix of startUpdatingLocation when the app is active and go into startMonitoringSignificantLocationChanges mode when the app is in the background. Basically I do the following when the app goes into the background:
-(void)applicationDidEnterBackground:(UIApplication *)application{
[myLocationManager startMonitoringSignificantLocationChanges];
}
And when the app comes back into the foreground I do the following:
-(void)applicationDidBecomeActive:(UIApplication *)application{
//Other irrelevant code
[myLocationManager stopMonitoringSignificantLocationChanges];
[myLocationManager startUpdatingLocation];
}
This seems logical, to me anyways. My question is, should I be calling the stopUpdatingLocation method in the applicationDidEnterBackground event? Like so:
-(void)applicationDidEnterBackground:(UIApplication *)application{
[myLocationManager stopUpdatingLocation];
[myLocationManager startMonitoringSignificantLocationChanges];
}
Where exactly should I be calling the stopUpdatingLocation method?? Please tell me if there is more than one place where this should be done. I'm assuming any error event should stop the updating?
I don't see anything wrong with what you are doing. Note that I have a commercial app that heavily uses location services, and I'm in the midst of rewriting it to improve it's performance and minimize battery usage.
My released version uses sigLocationChanges predominantly (in background & foreground), but switches to using startUpdatingLocation when unhappy with the quality of the location sigLocationChanges gives me, since my UI has to display the users location roughly accurately. I call stopUpdatingLocation immediately after each event to minimize battery drain. In my shipping version this seems to work okay, but my log files have found a tiny subset of users who seem to constantly get poor locations and I'm spinning up the GPS hardware more than I like.
Also in Privacy Settings, the type of location icon displayed for your app will be determined by when you last used the full GPS location mode. Mine always shows the location icon that indicates a heavy battery impact, even if I'm only using startUpdatingLocation briefly a couple times per day, which can make my users paranoid about how my app affects their battery life.
In my new release, to minimize the battery drain of using of startUpdatingLocation, I've cut it's use back to hopefully nil. When the app activates, I now get the current location directly from the location manager, cLLocMgr.location. Typically that's an accurate location, and my UI can be instantly drawn (or refreshed) correctly. I also check it again when certain views are activated to ensure if the user is moving while keeping my app open the display keeps up. Now I only spin up the GPS hardware if the phone has a bad location in a specific situation where a good location is absolutely required in the app. In that case, I limit it's use to 2 minutes (I'm assuming 2 minutes is long enough to get best location from GPS hardware), and wait at least 10 minutes before allowing it's use again.
Your question doesn't give me enough info to tell how accurate you need to be and how dynamic your location display is. But unless you need super accuracy and dynamic display, you should consider just using the current location without spinning up the GPS hardware to save battery.
Edit: Here is the actual code I used for Jeraldo, cleaned up a bit. Note it's been a year since I touched it, so I'm a little rusty on it, hope I didn't clean up anything.
// Called at start to ask user to authorize location data access.
- (void) requestIOSLocationMonitoring {
#if TARGET_IPHONE_SIMULATOR
// If running in siumaltor turn on continuous updating (GPS mode)
// This is for testing as significant change isn't useful in simulator
// Set a movement threshold for new events. This is only used by continiuous mode, not sig change events
// Keep it as low as possible,but not so low as to generate spurious movements.
cLLocMgr.distanceFilter = 30;
// Use continuous location events in simulator.
// desired accuracy only works in continuious (high power) mode.
cLLocMgr.desiredAccuracy = kCLLocationAccuracyBest;
[cLLocMgr startUpdatingLocation];
#else
// If not in simulator app's default is significant change monitoring
[cLLocMgr startMonitoringSignificantLocationChanges];
#endif //TARGET_IPHONE_SIMULATOR
}
// Toggle back and forth between continius updates (GPS on) and
// significant change monitoring
- (void) setGPSMode: (bool) useGPSMode {
// Keep track of time since we last changed GPS mode
NSTimeInterval secsInThisMode = [[NSDate date] timeIntervalSinceDate: lastModeChange];
// inGPSMode is an object instance variable app uses to track mode it is in.
if (inGPSMode != useGPSMode) {
lastModeChange = [NSDate date];
if (!useGPSMode) {
// Tell app to operate as if continuous updating is off
inGPSMode = false;
#if TARGET_IPHONE_SIMULATOR
// But keep using continuous location events in simulator for testing.
cLLocMgr.distanceFilter = 30;
#else
// Turn off continious updates for app on actual devices
[cLLocMgr stopUpdatingLocation];
#endif
} else if (secsInThisMode > cMinGPSModeBreak) {
// Only turn on continuous updating again if it's been off long enough
// Prevents GPS mode from running continiously and killing battery life
inGPSMode = true;
cLLocMgr.desiredAccuracy = kCLLocationAccuracyBest;
cLLocMgr.distanceFilter = kCLDistanceFilterNone;
[cLLocMgr startUpdatingLocation];
}
}
}

Using startMonitoringSignificantLocationChanges with NSTimer to improve battery usage

I'm currently working in an iOS app that other developer started. The app needs to monitor location changes because it needs to know the user position with low precision (hundred meters). The previous implementation of the location stuff was done using an NSTimer and startUpdatingLocation. The execution goes like this:
// Fire each 10 seconds start updating location
self.timerPosition = [NSTimer scheduledTimerWithTimeInterval:ti
target:self
selector:#selector(location)
userInfo:nil
repeats:YES];
[self.timerPosition fire];
Location selector does this
// Configure & check location services enabled
...
self.locman.delegate = self;
self.locman.desiredAccuracy = kCLLocationAccuracyHundredMeters;
[self.locman startUpdatingLocation];
And then in the location manager delegate
[manager stopUpdatingLocation];
But reading about getting the user location in Apple docs, it seems that the right way to get the location with low-power consumption is to use startMonitoringSignificantLocationChanges.
My question is, is a good decision to keep the location timer in combination with startMonitoringSignificantLocationChanges instead of startUpdatingLocation, or it's a nonsense approach?
I do not need to get location when the app is in the background, but I want to know when the user has changed it's position when the app is active.
I can tell you that the timer can't and won't be needed when you are using the low-power -startMonitoringSignificantLocationChanges. That method only responds to callbacks from the delegate when the device detects a change. This check for location is not using GPS, it uses Wifi and cell-tower triangulation which is already happening. So by using this method, there is no need to slow it down to save battery life. Just set up the delegate methods and respond accordingly.
I'm not sure what your implementation of location is for, but the region monitoring is also another great way to get location updates using little to no battery. Region monitoring is much more helpful when you have specific locations to monitor rather than just general user location. Hope this clears things up.

Resources