My app needs to record (as precisely as possible) the user's location and movement over time. If I let the device sleep, the GPS accuracy seems to be automatically reduced. I can stop it from sleeping but obviously that is a further drain on the battery. I would like to allow the display to sleep, but maintain the max accuracy for the locationmanager. I have Location updates ticked in the Background Modes section. My code looks like this ...
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.distanceFilter = 1
locationManager.activityType = .Fitness
locationManager.requestAlwaysAuthorization()
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startUpdatingLocation()
UIApplication.sharedApplication().idleTimerDisabled = true
I would really like to remove that last line, but all the testing I have done on a 4s suggests that as soon as the device sleeps it loses accuracy.
Assuming it is iOS 9 you also need this line:
locationManager.allowsBackgroundLocationUpdates = true;
By default this var is set to false, and that means iOS will not deliver location updates to a suspended app.
Related
I would like to know how to limit the time my app is running in background.
The purpose is to show the LaunchScreen again and start the app from initial state after 5 or 10 minutes in background run.
thanks in advance
Here are some ideas and thoughts to achieve what you want.
First, when your app moves into the background, it makes a call to applicationDidEnterBackground(_:) in your App Delegate. If you are using scenes with a Scene Delegate then you should perform any last logic in sceneDidEnterBackground(_:)
As per the docs, you have 5 seconds to perform any task once the above functions have been called
applicationDidEnterBackground(_:)
Return from applicationDidEnterBackground(_:) as quickly as possible.
Your implementation of this method has approximately five seconds to
perform any tasks and return.
After this, your app goes into a suspended state and if you need more than 5 seconds, you can request more by calling beginBackgroundTask(withName:expirationHandler:) but for your purposes I don't think you need this.
More on the above topics here
You have little to no control on when your app gets to run on the background again, that is moved from a suspended to a background state.
There is a property for BGTaskRequest called earliestBeginDate but as per the docs:
earliestBeginDate
Specify nil for no start delay.
Setting the property indicates that the background task shouldn’t
start any earlier than this date. However, the system doesn’t
guarantee launching the task at the specified date, but only that it
won’t begin sooner.
So long story short, you can't really tell when your app will get to run on the background again and you can't decide how much time it will be given to run.
What you could do is implement either applicationDidEnterBackground(_:) or sceneDidEnterBackground(_:) if you are using scenes to track at what time the app goes into the background like so:
func sceneDidEnterBackground(_ scene: UIScene)
{
// Get the current date / time
let currentDate = Date()
let userDefaults = UserDefaults.standard
// Start tracking time as soon as the app goes
// into the background
userDefaults.setValue(currentDate,
forKey: "PreviousLaunchDate")
// Just for testing, remove from production app
print("start tracking")
}
And then when your app comes into the the foreground, check how much time has elapsed and react accordingly:
func sceneWillEnterForeground(_ scene: UIScene)
{
let userDefaults = UserDefaults.standard
let currentDate = Date()
// Check if you have saved a date before
if let previousLaunchDate
= userDefaults.object(forKey: "PreviousLaunchDate") as? Date
{
// Retrieve the minutes elapsed
// Check if minutes elapsed is greater than 10 minutes or as you wish
if let minutesElapsed = Calendar.current.dateComponents([.minute],
from: previousLaunchDate,
to: currentDate).minute,
minutesElapsed > 10
{
// Reset your window's root view controller to start again
// showing the splash view or reset your storyboard etc
// For example
// let splashVC = SplashViewController()
// window?.rootViewController = splashVC
// just for testing, remove from production app
print("show splash vc")
return
}
// Do nothing since 10 minutes haven't elapsed so you can
// show the current state of the app to the user
// just for testing, remove from production app
print("do nothing")
}
}
I think this should get you close to what you are looking for.
I am making an application which tracks the user. I have notice when the application goes in the background and then when you open the app it's shows the wrong current location for the user until about 5 seconds. Is there to fix that because that 5 seconds delay ruins the tracking results ( it's adds three extra miles for no reason ).
Edit: The issue wasn't actually a "bug". I have to set in my Info.plist that I want background pocessing and boom the application tracking is super Accurate. A little tutorial to do that:
Go to Info.plist
Add an New Row called "Required background modes"
Then add again a new row called "App registers for location updates"
We are Done :)
One thing you can do is check the horizontalAccuracy property on the CLLocation that you're being returned. If this is above a certain threshold then you could throw away the result and wait for a more accurate one. If it is a number of miles out then I would expect the accuracy figure to be quite large. It's most likely using a cell site to determine location rather than GPS, and the margin of error would be much greater.
In your CLLocationManagerDelegate's locationManager:didUpdateLocations: method you could do the following:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
if ([locations count] > 0) {
CLLocation *lastUpdatedLocation = [locations lastObject];
CLLocationAccuracy desiredAccuracy = 1000; // 1km accuracy
if (lastUpdatedLocation.horizontalAccuracy > desiredAccuracy) {
// This location is inaccurate. Throw it away and wait for the next call to the delegate.
return;
}
// This is where you do something with your location that's accurate enough.
}
}
I have a scenario where i search for the current location.Once the current location search begins i use "startMonitoringSignificantLocationChanges" i get didUpdateToLocation delegate called and i get a location value.
After the search is over i use "stopMonitoringSignificantLocationChanges" to turn off the GPS.
My problem is i may to the surrent location search after some time,when its done that is when i again start searching for current location after sometime GPS turns on but "didUpdateToLocation" is not called.
I am doing all these in a view controller.
I have initialized the location manager and have set the desiredAccuracy to "kCLLocationAccuracyNearestTenMeters" and distanceFilter to "10.0f".
Why i am not able to call "didUpdateToLocation" delegate on stooping the location updates and starting it again.
Please any body help me...
Expecting for a positive response from any of you.
Thank you.
You should use startUpdatingLocation instead of startMonitoringSignificantLocationChanges. startMonitoringSignificantLocationChanges calls didUpdateToLocation only when location is significantly changed.
if (locationManager == nill)
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
locationManager.distanceFilter = 10;
[locationManager startUpdatingLocation];
Sometimes I receive the wrong value for my current location, an error of up to 2 miles. Am I doing everything correctly?
self.myLocation = [[CLLocationManager alloc] init];
myLocation.delegate = self;
myLocation.desiredAccuracy = kCLLocationAccuracyBest;
[myLocation startUpdatingLocation];
What's the most accurate method to receive GPS location?
You get a stream of location updates from the device when you start monitoring location change and so you need to consider both the timestamp of the location update and its horizontal accuracy.
Use the timestamp to reject old values (for example when you first start monitoring, you will be sent an accurate (at the time) but old cached value).
Use the horizontal accuracy to reject values that are outside of your desired range.
I'm not using this for driving directions or similar. I have a few annotations and want a trigger when user is in the vicinity of one of those, so I can alert the user.
It seems didUpdateToLocation is called only once per startUpdatingLocation call? At least when I NSLog within that method, I only get one line in console. No, I'm not walking around with the iphone.
So: What is the correct way to set up a continuous monitoring of userLocation and get a "callback" (either when 3-4 seconds have passed or when user has moved say, 10 meters)?
As you probably know, this is the code to initialize and start the location manager:
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
[locationManager startUpdatingLocation];
And implement didUpdateToLocation like this:
- (void) locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*) oldLocation
{
// This will be called every time the device has any new location information.
}
The system will call didUpdateToLocation every time there is an update to the location. If the system does not detect a change in location didUpdateToLocation will not be called. The only thing you can do is to set the distanceFilter and desiredAccuracy like i did in the example to give you the highest accuracy.
Update
Use kCLLocationAccuracyBest instead of kCLLocationAccuracyNearestTenMeters for more accuracy.