Location service going to “Inactive” state in iOS 8 - ios

I am creating an app which tracks user. When user login to app I have started significant Location updates. Then i put my application in background. and then started traveling with my car. After 500m-1km I got significant location change. On getting update I have stopped significant location updates and started standard GPS. it gives m location updates for 2-3 min after that i see this "Location icon should now be in state 'Inactive'" and my app does not get any location updates.
If app is foregrounded then again app starts getting location updates.
I am using shared location instance. I have iPhone 5 and iOS 8.1.3. I am using Swift.
Adding some code.. I have done like this
gpsManager = CLLocationManager()
gpsManager.delegate = self
gpsManager.desiredAccuracy = kaccuracy
gpsManager.distanceFilter = kdistanceFilter
gpsManager.pausesLocationUpdatesAutomatically = false
gpsManager.headingFilter = kheadingFilter
gpsManager.activityType = kactivityType
isTracking = false
isMonitoringSignificantChanges = false
//on login
var sharedGPSManager=GeoLocation.sharedInstance
sharedGPSManager.delegate=self
let status=sharedGPSManager.authorizationStatus()
if(status == 0)
{
NSLog("Allowed to track")
}
else
{
NSLog("Not allowed")
}
sharedGPSManager.startSignificantLocationTracking()
func gpsManager(manager:GeoLocation, didUpdateLocations locations:AnyObject)
{
var sharedGPSManager=GeoLocation.sharedInstance
sharedGPSManager.stopSignificantLocationTracking()
task=UIApplication.sharedApplication().beginBackgroundTaskWithName("wifiUpload", expirationHandler: { () -> Void in
}
sharedGPSManager.delegate = self
let status = sharedGPSManager.authorizationStatus()
if(status == 0)
{
NSLog("Allowed to track")
}
else
{
NSLog("Not allowed")
}
sharedGPSManager.startTracking()
//process location data
endBackgroundTask(task)
}
// Start location tracking
func startTracking(){
if(isMonitoringSignificantChanges){
gpsManager.stopMonitoringSignificantLocationChanges()
}
gpsManager.stopUpdatingLocation()
if CLLocationManager.headingAvailable()
{
gpsManager.startUpdatingHeading()
}
gpsManager.startUpdatingLocation()
isTracking=true
isMonitoringSignificantChanges=false
}

Finally I got the Answer on Apple developer forum.keeping significant Location always on worked for me....

You need to add Location Update capability in background mode
Project Target > Capabilities > Turn Background mode ON > Tick all which is applicable for your app

You have to enable Required background modes in info.plist as "App registers for location updates" then your application will give you location updates in background.
And you have to use self.locationManager.startUpdatingLocation() not the significant Location updates.

Related

Reason for my app going to suspended state?

I am created a location tracking ios app(using CocoaLumberjack library to write log file).So background location update is enabled and working for my testing(I nearly running of upto 8 hours in background). When app goes to live store. There is lot of issue occurred in our app. When app went to background location tracking is not working properly. It's not send user location to server for some period of time. So i get log file from client and reviewed there is a time gap in log file. i frequently getting user location(every one second). So i thought app went to suspended state at the time of gap occurs in log file? Why app goes into suspended state even i am getting frequently location in background? is there a reason for app going to suspended state? searched lot can't find any valid details?
func startTimer()
{
if bgTimer == nil
{
bgTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.startLocationChanges), userInfo: nil, repeats: true)
}
}
func stopTimer()
{
if bgTimer != nil
{
bgTimer?.invalidate()
bgTimer = nil
}
}
#objc func startLocationChanges() {
locationManager.delegate = self
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//let lastLocation = locations.last!
// Do something with the location.
/*print(lastLocation)
let logInfo = "BGLocationManager didUpdateLocations : " + "\(lastLocation)"
AppDelegate.appDelegate().writeLoggerStatement(strInfo: logInfo)*/
locationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
if let error = error as? CLError, error.code == .denied {
// Location updates are not authorized.
manager.stopMonitoringSignificantLocationChanges()
return
}
// Notify the user of any errors.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationDidEnterBackground: when the user quits.
self.writeLoggerStatement(strInfo: "applicationDidEnterBackground")
appstate = "Background"
if CoreDataUtils.isUserLoggedIn(entityName: "UserInfo") == true {
let user = CoreDataUtils.fetchCurrentUser(entityName: "UserInfo")
if user!.isGPSActive == "1"
{
if backgroundTaskIdentifier != nil
{
application.endBackgroundTask(backgroundTaskIdentifier!)
backgroundTaskIdentifier = UIBackgroundTaskInvalid
}
backgroundTaskIdentifier = application.beginBackgroundTask(expirationHandler: {
//UIApplication.shared.endBackgroundTask(self.backgroundTaskIdentifier!)
})
BGLocationManager.shared.startTimer()
let logInfo = String(format:"applicationDidEnterBackground backgroundTimeRemaining : %f",(Double)(application.backgroundTimeRemaining / 60))
self.writeLoggerStatement(strInfo: logInfo)
}
}
}
A few observations:
The beginBackgroundTask only buys you 30 seconds, not 8 hours. (In iOS versions prior to 13, this was 3 minutes, not 30 seconds, but the point still stands.) Bottom line, this is designed to allow you to finish some short, finite length task, not keeping the app running indefinitely. Worse, if you don’t call endBackgroundTask in its completion handler, the app will be unceremoniously terminated when the allotted time has expired.
There are two basic patterns to background location updates.
If the app is a navigation app, then you can keep the app running in the background. But keeping standard location services running in the background will kill the user’s battery in a matter of a few hours. So Apple will only authorize this if your app absolutely requires it (e.g. your app is an actual navigation app, not just an app that happens to want to keep track of locations for some other reason).
The other pattern is significant change service. With this service, your app will be suspended, but the OS will wake it to deliver location updates, and then let it be suspended again. See Handling Location Events in the Background. This isn’t as precise as the standard location services, but because the app isn’t constantly running and because it doesn’t have to spin up GPS hardware, it consumes far less power.
When testing these sorts of background interactions, you do not want to be attached to the Xcode debugger. Running it via the debugger actually changes the app lifecycle, preventing it from ever suspending.
As one doesn’t generally keep the app running in the background indefinitely, that means that you will want to remove that Timer related code.

Background location service stops working in a period of time

I'm developing an iOS application that users can save their traveling route to server (by posting their locations through API). The issue I am struggling with is several users reported that the route they saved is interrupted in the middle of the trip. In detail, when users review their saved route on map, there is a part of route being just a straight line because somehow locations of that route section are not sent to server or just because the device can not receive locations at that time.
The weird thing is that the rest of route was recorded normally so seems like the location service stopped working for a period of time but after that it started again so my app could record it fine.
And the most frustrating thing is that I can not reproduce this issue.
Here are circumstances of the issue that user reported:
- User started the app then locked device screen and put it in their pocket, they did not touch it in the whole journey. No battery drain or crash happened.
- After driving about 8-9km and everything worked fine, route recording was interrupted in the next ~ 65km, then well-recorded again in the rest ~ 80km.
Below is my project setup:
- Background Modes in ON in Capabilities with Location updates.
- Locations received from location service are filtered based on timestamp and accuracy and saved to core data with a “isSent” flag marking if a location is sent successfully to server. This way my app can cover the case when network connection is down.
- Locations marked with false “isSent” flag will be sent to server every 30 seconds.
My LocationManager code:
class LocationManager: NSObject, CLLocationManagerDelegate {
var locationManager: CLLocationManager = {
var _locationManager = CLLocationManager()
_locationManager.delegate = self
_locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
_locationManager.activityType = .automotiveNavigation
_locationManager.allowsBackgroundLocationUpdates = true
_locationManager.pausesLocationUpdatesAutomatically = false
return _locationManager
}()
func startLocationService() {
locationManager.startUpdatingLocation()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
var positionsToSavedToDb: [DbPositionModel] = []
for location in locations {
let howRecent = location.timestamp.timeIntervalSinceNow
guard abs(location.timestamp.timeIntervalSinceNow) < 10 && location.horizontalAccuracy > 0 && location.horizontalAccuracy < 33 else {
continue
}
if self.locations.count > 0 {
let distanceSinceLastLocation = location.distance(from: self.locations.last!)
let timeSinceLastLocation = location.timestamp.timeIntervalSince(self.locations.last!.timestamp)
if (distanceSinceLastLocation < 5 && abs(timeSinceLastLocation) < 10) {
continue
}
}
// Create DbPositionModel from location and append to positionsToSavedToDb
}
// Save positionsToSavedToDb to core data
}
#objc func each30Seconds(_ timer: Timer) {
// Select in database DbPositionModel objects with false “isSent” flag to send to server
}
}
Can you guys help me find out black holes in my code or anything I can do to reproduce / fix this issue? Thanks a lot!!!
Your setup looks fine to me. Just one question. When you say it didn't work for 60km and then it started working for 80km, was that all while in background? I mean the user didn't need to enter foreground for it to start working again did they?
Your limit for location.horizontalAccuracy is 33. You're thinking that it's going to be very accurate. I'm not sure, maybe the device/city are a bad combination, and then you're returning early. I suggest that you log the reason why you exit early. Their city might be different from yours. Also I've heard that the GPS of the iPhoneX even though has the correct location, it returns a high number for its horizontalAccuracy. Is this happening mostly for iPhoneX users?
enum LocationAccuracyError: Error {
case stale(secondsOld: Double)
case invalid
case lowAccuracy(metersOff: Double)
}
extension LocationAccuracyError: LocalizedError{
var errorDescription: String? {
switch self {
case .stale(let seconds):
return NSLocalizedString("location was stale by: \(seconds) seconds", comment: "")
case .invalid:
return NSLocalizedString("location was invalid)", comment: "")
case .lowAccuracy(let metersOff):
return NSLocalizedString("location's horizontal Accuracy was off by likely more than: \(metersOff) meters" , comment: "")
}
}
}
And then have a function like this to check each location.
private func checkLocationAccuracy(from location: CLLocation) throws {
let ageOfLocation = -location.timestamp.timeIntervalSinceNow
if ageOfLocation >= maximumAcceptedStale {
throw LocationAccuracyError.stale(secondsOld: ageOfLocation)
}
if location.horizontalAccuracy <= 0 {
throw LocationAccuracyError.invalid
}
if location.horizontalAccuracy > MaximumAcceptedHorizontalAccuracy{
throw LocationAccuracyError.lowAccuracy(metersOff: location.horizontalAccuracy)
}
}
Your end usage would be like:
do {
try checkLocationAccuracy(from: location)
} catch let error {
writelog("Bad Location: \(error.localizedDescription)")
}
I'd also add logs around your app state as well e.g. add a log to capture didEnterBackground

Swift CoreMotion background saving battery

I need to get users activity on background using Core Motion. I was able to reach this goal by adding the locations update as background mode and enable the background location fetch. But as I'm not interest in the user location, this results in waste of battery and a blue sign ( iPhone X ) that indicates the app is updating your locations in background.
Is possible to run core motion in background without update the location of the user in order to use less battery and not show the blue sign to the user??
Thank you really much in advance!!
EDIT
Example function code:
private func startTrackingActivityType() {
activityManager.startActivityUpdates(to: OperationQueue.main) {
[weak self] (activity: CMMotionActivity?) in
guard let activity = activity else { return }
DispatchQueue.main.async {
if activity.walking {
self?.activityTypeLabel.text = "Walking"
} else if activity.stationary {
self?.activityTypeLabel.text = "Stationary"
} else if activity.running {
self?.activityTypeLabel.text = "Running"
} else if activity.automotive {
self?.activityTypeLabel.text = "Automotive"
}
}
}
}
You don't need to be in background at all. Next time your app will launch, you can retrieve history for MotionActivity. In addition you can also get raw historical sensor data. But you should schedule it.
Details: https://developer.apple.com/documentation/coremotion/cmmotionactivitymanager?language=objc

How Can I Continue updating my location even when app is killed or removed from background in ios?

My app requires to update location continuously, even if app is killed or removed from background. It works fine in foreground and even in background mode.but not working when app is killed.
I've tried some code.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.startLocationService()
if ((launchOptions?[UIApplicationLaunchOptionsLocationKey]) != nil) {
self.locationmanager = CLLocationManager()
self.startLocationService()
}
print("Location Updates started")
return true
}
func startLocationService()
{
self.locationmanager.requestWhenInUseAuthorization()
self.locationmanager.desiredAccuracy = kCLLocationAccuracyBest
self.locationmanager.allowsBackgroundLocationUpdates = true
self.locationmanager.requestAlwaysAuthorization()
self.locationmanager.delegate = self
if UIApplication.sharedApplication().backgroundRefreshStatus == .Available {
print("Background updates are available for the app.")
}
else if UIApplication.sharedApplication().backgroundRefreshStatus == .Denied {
print("The user explicitly disabled background behavior for this app or for the whole system.")
}
else if UIApplication.sharedApplication().backgroundRefreshStatus == .Restricted {
print("Background updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user.")
}
else
{
print("nothing")
}
self.locationmanager.startUpdatingLocation()
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
// locationManager = CLLocationManager()
}
func applicationDidBecomeActive(application: UIApplication) {
self.startLocationService()
}
func applicationWillTerminate(application: UIApplication) {
self.locationmanager.startMonitoringSignificantLocationChanges()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("did update locateion called-->..")
let location:CLLocation = locations[locations.count-1]
print(location.coordinate.longitude)
print(location.coordinate.latitude)
let newTime : NSDate = NSDate()
let calendar: NSCalendar = NSCalendar.currentCalendar()
let flags = NSCalendarUnit.Second
let components = calendar.components(flags, fromDate: oldTime, toDate: newTime, options: [])
//Update locations to server
self.call_service_insertLocation("location", loc: location)
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print(error)
}
I hope this might help you.
Your app can be awaken when it is terminated by some reason (Memory pressure).
But there's limitation on background location update when app is terminated.
This is note from Apple's Location and Map programming guide.
If your app is terminated either by a user or by the system, the system doesn't automatically restart your app when new location updates arrive. A user must explicitly relaunch your app before the delivery of location updates resumes. The only way to have your app relaunched automatically is to use region monitoring or significant-change location service.
However, when a user disables the Background App Refresh setting either globally or specifically for your app, the system doesn't relaunch your app for any location events, including significant change or region monitoring events. Further, while Background App Refresh is off your app won't receive significant change or region monitoring events even when it's in the foreground.
So to receive location update in background, you should turn on Background App Refresh setting for your app (You can see the setting in Settings/Your App/Background App Refresh in your iPhone.)
Also only significant changes will be delivered when app is terminated and like location changes you mentioned here (I mean kCLLocationAccuracyBest) probably will not wake your app.
Further more, you should restart the location manager in your app delegate
s application:didFinishLaunching:withOptions method to retrieve next significant location changes. Also make sure return as soon as possible when location is retrieved in background mode or use background task mechanism to make app working more a few minutes in background. (2~3 mins).
Its possible, but you'll have to jump through a few hoops.
The only way to send location updates when killed is by using Region Monitoring (https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/LocationAwarenessPG/RegionMonitoring/RegionMonitoring.html).
When setup up, your app would be opened by the OS then you have a few seconds to process information before the app is killed. Its enough time to send location updates to your server/local storage.
There is also another API called CLVisit, however, its completely controlled by the operating system and not reliable.

CLError.DeferredFailed Error (kCLErrorDomain Code=11) Deferred GPS Updates Failing

I have an app which does periodic location updates to map the users path.
I am using deferred updates.
if (CLLocationManager.deferredLocationUpdatesAvailable() == true && _isDeferingUpdates == false)
{
print("Doing refresh")
_isDeferingUpdates = true
_locationManager.allowDeferredLocationUpdatesUntilTraveled(C_GPS.ACTIVITY_UPDATE_DISTANCE, timeout: C_GPS.ACTIVITY_UPDATE_TIME)
} else
{
print("Could not refresh")
// iPhone 4S does not have deferring so must keep it always on
_locationManager.startUpdatingLocation()
}
When the app is open I get the "doing refresh" call every second.
My setup:
Have the 2 keys on my pList NSLocationWhenInUseUsageDescription && NSLocationAlwaysUsageDescription
Have background modes turned on, for Remote Notifcations and Location Updates.
Have Maps setup to use Bike and Pedestrian.
Have all the permissions on my phone set to yes.
Do you know any other reason why my deferred update is failing when my app goes to the background?
Its never working and apples documentation is less than helpful
DeferredFailed The location manager did not enter deferred mode for an
unknown reason. This error can occur if GPS is unavailable, not
active, or is temporarily interrupted. If you get this error on a
device that has GPS hardware, the solution is to try again.
Here is my error handler:
func locationManager(manager: CLLocationManager, didFinishDeferredUpdatesWithError error: NSError?)
{
print("FINISHED BACKGROUND UPDATE with error \(error)")
if (error != nil)
{
print("ERROR IS VALID as CLError")
if (error!.code == CLError.LocationUnknown.rawValue)
{
print("Error: Location Unknown")
} else if (error!.code == CLError.DeferredAccuracyTooLow.rawValue)
{
print("Error: Accuracy too low")
} else if (error!.code == CLError.DeferredFailed.rawValue)
{
print("Error: Deferring Failed")
} else if (error!.code == CLError.Denied.rawValue)
{
print("Error: Denied")
} else
{
print("Error not handled")
}
}
_isDeferingUpdates = false
}
iOS 9.2, Xcode 7.2, ARC enabled
The problem is most likely associated with the distance and time interval you have chosen for your deferral. Try to take advantage of CLLocationDistanceMaxand CLTimeIntervalMax to troubleshoot the function call. For example, set the distance to CLLocationDistanceMax and then vary the time interval, then try vice versa.
But there are other things I see that you might want to change...
Get rid of CLLocationManager.deferredLocationUpdatesAvailable() == true condition in the if statements to allow deferred location updates.
Deferred updates were available iOS6.0+, most iPhone 4S can be updated to iOS7.2.1 depending on the hardware. You do not need to separately call _locationManager.startUpdatingLocation().
Make sure that if you are testing in iOS9.0+ that you have the allowsBackgroundLocationUpdates set for the location manager.
Make sure that the initial _locationManager.startUpdatingLocation() is made in the - (void)applicationWillResignActive:(UIApplication *)application method equivalent in Swift and NOT in the - (void)applicationDidEnterBackground:(UIApplication *)application method.
Make sure that you are calling _locationManager.allowDeferredLocationUpdatesUntilTraveled(C_GPS.ACTIVITY_UPDATE_DISTANCE, timeout: C_GPS.ACTIVITY_UPDATE_TIME) in the equivalent of the - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations method for Swift.
Also, note that you are printing the error before you check if the error exists, this might lead to some incorrect error reporting.
Hope this helps and I am not too late! Cheers.
P.S. If this doesn't work, then please post more of your code, i.e. where you are calling these functions in relation to the app. delegate *.m file.
The error was misleading. The issue is the 4S does not support background updates. As such, for the 4S I was manually refereshing the updates like so:
private func refreshUpdateDeferral()
{
if (CLLocationManager.deferredLocationUpdatesAvailable() == false && _isDeferingUpdates == false)
{
print("Doing deferred referral refresh")
_isDeferingUpdates = true
_locationManager.allowDeferredLocationUpdatesUntilTraveled(C_GPS.ACTIVITY_UPDATE_DISTANCE, timeout: C_GPS.ACTIVITY_UPDATE_TIME)
}
}
The problem was I had CLLocationManager.deferredLocationUpdatesAvailable() == true which mean I was deferring updates when deferring was already allowed. This seems to cause the error above.
So if you get this error, check that you aren't allowDeferredLocationUpdatesUntilTraveled while deferredUpdates is already active!
You can run into trouble with deferred updates if your accuracy and distance filter are not set correctly. Assuming your device supports deferred updates (CLLocationManager.deferredLocationUpdatesAvailable), you must set your location manager to the following first:
desiredAccuracy = kCLLocationAccuracyBest
distanceFilter = kCLDistanceFilterNone
Then you can start the location manager and allow deferred updates.

Resources