LocationManager crash when app wakes for update - ios

Although it works normal when app is active, it crashes when app is terminated and wakes for location update
My code to handle app wakes up for location update on didFinishLaunchingWithOptions
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var glat : String = ""
var glong : String = ""
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if launchOptions?[UIApplicationLaunchOptionsLocationKey] != nil {
let locationManager = CLLocationManager() //or without this line, both crashes
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
let status = CLLocationManager.authorizationStatus()
if (status == CLAuthorizationStatus.AuthorizedAlways) {
locationManager.startMonitoringSignificantLocationChanges()
}
}
return true
}
Here is the locationmanager delegate on AppDelegate
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let lat = manager.location?.coordinate.latitude,
let long = manager.location?.coordinate.longitude {
print(glat + " " + glong)
glat = String(lat)
glong = String(long)
//Line 339
updateloc(String(lat), long: String(long))
}
}
Function to send location info to server
func updateloc(lat : String, long : String) {
let session = NSURLSession.sharedSession()
//Line 354
let request = NSMutableURLRequest(URL: NSURL(string: "URLTO/updateloc.php")!)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.HTTPMethod = "POST"
let data = "lat=\(lat)&long=\(long)"
request.HTTPBody = data.dataUsingEncoding(NSASCIIStringEncoding)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let response = response {
let res = response as! NSHTTPURLResponse
dispatch_async(dispatch_get_main_queue(), {
if (res.statusCode >= 200 && res.statusCode < 300)
{
do{
let resultJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions())
var success = 0
if let dictJSON = resultJSON as? [String:AnyObject] {
if let successInteger = dictJSON["success"] as? Int {
success = successInteger
if success == 1
{
print("ok")
}
} else {
print("no 'success' key in the dictionary, or 'success' was not compatible with Int")
}
} else {
print("unknown JSON problem")
}
} catch _{
print("Received not-well-formatted JSON")
}
}
})
}
})
task.resume()
}
Here is the crash log
Crashed: com.apple.main-thread
0 0x10015d998 specialized AppDelegate.updateloc(String, long : String) -> () (AppDelegate.swift:354)
1 0x10015ddf8 specialized AppDelegate.locationManager(CLLocationManager, didUpdateLocations : [CLLocation]) -> () (AppDelegate.swift:339)
2 0x100159d0c #objc AppDelegate.locationManager(CLLocationManager, didUpdateLocations : [CLLocation]) -> () (AppDelegate.swift)
3 CoreLocation 0x1893d08b8 (null) + 21836
4 CoreLocation 0x1893ccaac (null) + 5952
5 CoreLocation 0x1893c6e48 (null) + 880
6 CoreFoundation 0x18262cf84 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 20
7 CoreFoundation 0x18262c8bc __CFRunLoopDoBlocks + 308
8 CoreFoundation 0x18262ad04 __CFRunLoopRun + 1960
9 CoreFoundation 0x182554c50 CFRunLoopRunSpecific + 384
10 GraphicsServices 0x183e3c088 GSEventRunModal + 180
11 UIKit 0x18783e088 UIApplicationMain + 204
12 0x10015a324 main (AppDelegate.swift:20)
App crashes when app wakes for a location update in startMonitoringSignificantLocationChanges mode
I really can't see any mistake here. Anyone can help me to fix it ?

As a starting point, I would suggest moving your location manager functionality to a separate class, and having your location class subscribe to the UIApplicationDidFinishLaunchingNotification notification to handle what happens when the app is restarted.
A way to do this would be to have your class a Singleton and have it relay the location updates through the NSNotificationCenter.
startMonitoringSignificantLocationChanges will wake up your app in the background if its enabled when the app gets terminated by the OS.
Depending on what you want to achieve, you could subscribe to two different events and start stop the relevant location services base on the app delegate events.
As a broad (shot in the dark) example:
class LocationCommander {
let locationManager = CLLocationManager()
let defaultCenter = NSNotificationCenter.defaultCenter()
//- NSUserDefaults - LocationServicesControl_KEY to be set to TRUE when user has enabled location services.
let UserDefaults = NSUserDefaults.standardUserDefaults()
let LocationServicesControl_KEY = "LocationServices"
init(){
defaultCenter.addObserver(self, selector: #selector(self.appWillTerminate), name: UIApplicationWillTerminateNotification, object: nil)
defaultCenter.addObserver(self, selector: #selector(self.appIsRelaunched), name: UIApplicationDidFinishLaunchingNotification, object: nil)
}
func appIsRelaunched (notification: NSNotification) {
//- Stops Significant Location Changes services when app is relaunched
self.locationManager.stopMonitoringSignificantLocationChanges()
let ServicesEnabled = self.UserDefaults.boolForKey(self.LocationServicesControl_KEY)
//- Re-Starts Standard Location Services if they have been enabled by the user
if (ServicesEnabled) {
self.updateLocation()
}
}
func appWillTerminate (notification: NSNotification){
let ServicesEnabled = self.UserDefaults.boolForKey(self.LocationServicesControl_KEY)
//- Stops Standard Location Services if they have been enabled
if ServicesEnabled {
self.locationManager.stopUpdatingLocation()
//- Start Significant Location Changes to restart the app
self.locationManager.startMonitoringSignificantLocationChanges()
}
NSUserDefaults.standardUserDefaults().synchronize()
}
func updateLocation () {
if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.Authorized){
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.distanceFilter = kCLDistanceFilterNone
self.locationManager.startUpdatingLocation()
//- Save Location Services ENABLED to NSUserDefaults
self.UserDefaults.setBool(true, forKey: self.LocationServicesControl_KEY)
} else {
//- Unauthorized, requests permissions
}
}
}
Please keep in mind that this code has not been tested, I've extracted segments of an old project which might have some syntax errors due to the updates to the language.
Overall we are doing this:
Subscribe to relevant notifications
When app is about to terminate, check if we are getting location updates, if we are, stop the standard service and start the significant changes service to wake up the app when a new update is available.
When the app wakes up, it will notify the class which will decide if we continue to track notification updates or not (Based on a value stored on NSUserDefaults).
Your app might be crashing due to instantiating a new location manager which then tries to restart location services that might already be running.
I hope this helps!

The documentation says
For services that relaunch the app, the system adds the UIApplicationLaunchOptionsLocationKey key to the options dictionary passed to the app delegate at launch time. When this key is present, you should restart your app’s location services right away. The options dictionary does not include information about the location event itself. You must configure a new location manager object and delegate and start your location services again to receive any pending events.
Furthermore it says:
Calling this method [means startMonitoringSignificantLocationChanges] several times in succession does not automatically result in new events being generated. Calling stopMonitoringSignificantLocationChanges in between, however, does cause a new initial event to be sent the next time you call this method.
This implies more than might be clear on first glance. First, the fact that a location update triggered your app's relaunch doesn't mean you immediately get it via the delegate's locationManager:didUpdateLocations: method. Second, if you are in background (and thus not relaunched, but just became active!), things are yet different again. In fact, a common problem I've seen people encounter is that they don't know whether they're in background (inactive) or terminated/just relaunched. You can't even easily test/see this, because an app that was terminated by the system still shows up when pressing the home button twice. Check the list shown under Attach to Process... in XCode's Debug menu to see whether your app is still running (and in background) or not (btw, terminating an app via the Stop button in XCode results in this inconsistency if you wanna play around with the various ways you can become active or relaunched). I just mention this here because a lot of questions ask "How come X happens when my app gets active again?" and the people answering assume different things than what the asking person meant by that.
Anyways, if your App was in fact relaunched, your locationManager variable contains a brand new instance now, and you should get an update right after calling startMonitoringSignificantLocationChanges. I'll get to the problem you might still encounter then below.
If your App just became active again, you should call stopMonitoringSignificantLocationChanges, then startMonitoringSignificantLocationChanges again to get an update immediately (in short: always stop then start to properly restart, it doesn't hurt even if you weren't running already).
Now to the problem I think that results (in part) from this mess with Apple's way of relaunching after location updates. I'm not entirely sure what's happening without playing around with your code, but I hope this is of some help:
In your application:didFinishLaunchingWithOptions: method you set a local constant with let locationManager = CLLocationManager(). This one then is restarted and triggers location updates. However, it might get deallocated before the delegate's locationManager:didUpdateLocations: method is called, after all, the callback happens asynchronously (and application:didFinishLaunchingWithOptions: might return before that is called). I have no idea how or why this even works and gets as far as you say it does, maybe it's luck, or it has to do with how CLLocationManager internally works. Anyways, what I think then happens is that in locationManager:didUpdateLocations:, you don't get meaningful location data anymore, because the CLLocationManager instance responsible for the call is not valid anymore. lat and long are empty or even nil and your request's URL (which you haven't shown) is invalid.
I would suggest to first:
Ensure that your class's constant locationManager is properly set in application:didFinishLaunchingWithOptions: (right after you checked the presence of the UIApplicationLaunchOptionsLocationKey flag). Get rid of the local constant of the same name.
Stop and then restart the location updates. This should call your delegate's locationManager:didUpdateLocations: method immediately with the latest location data (the one that triggered your app's relaunch or becoming active again).
Ensure you get meaningful data in your locationManager:didUpdateLocations: method. Don't call updateloc if that's not the case (you might also want to move the line print(glat + " " + glong) to after you set the class's variables to the new values, so you see on the console whether what you get makes sense).
There"s one small thing I can't tell on the top of my head: If your app is just getting active again (and not relaunched) I'm not sure whether the UIApplicationLaunchOptionsLocationKey is even set at all. You might wanna investigate that one, too. If becoming active isn't a "relaunch" in that sense, though, your locationManager should still be happy an working anyways, at least that's what I get from the documentation (and it's been a while since I've tried that myself). Let me/us know if you're still having problems, I'm curious how it'll turn out. :)

Try this
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_DEFAULT.rawValue), 0)) {
updateloc(String(lat), long: String(long))
}

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

Send request in applicationWillTerminate

In my app I need to send some instructions to server when the user terminated an app. In applicationWillTerminate func I tried to send it, but it never came to server. I tried to use Alamofire and native URLSession but it doesn't work. Does anybody know how can I send it?
I use this code
let request = "\(requestPrefix)setDriverOrderStatus"
if let url = URL(string:request) {
var parameters : [String : String] = [:]
parameters["access_token"] = UserSession.accessToken
parameters["driver_id"] = UserSession.userID
parameters["status"] = status
var req = URLRequest(url: url)
req.httpMethod = HTTPMethod.put.rawValue
do {
req.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
}
_ = URLSession.shared.dataTask(with: req, completionHandler: { data, response, error in
guard error == nil else {
print(error ?? "error")
return
}
guard let data = data else {
print("Data is empty")
return
}
let json = try! JSONSerialization.jsonObject(with: data, options: [])
print(json)
}).resume
}
One solution that worked for me is to add sleep at the end of the applicationWillTerminate function like this :
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
// HERE YOU will make you HTTP request asynchronously
self.postLogoutHistory()
// 3 is the number of seconds in which you estimate your request
// will be finished before system terminate the app process
sleep(3)
print("applicationWillTerminate")
// self.saveContext()
}
put breakpoint in applicationWillTerminate and check that, function is getting called or not because applicationWillTerminate is not called everytime when application is getting terminated, especially when user quit application manually from multitasking window, applicationWillTerminate will not get called! When system terminates the application at that time applicationWillTerminate will get called and you will got approximately five seconds to complete your task!! So, it is not good idea to perform network related task on applicationWillTerminate!!
Refer Apple Documentation for applicationWillTerminate, It states,
This method lets your app know that it is about to be terminated and
purged from memory entirely. You should use this method to perform any
final clean-up tasks for your app, such as freeing shared resources,
saving user data, and invalidating timers. Your implementation of this
method has approximately five seconds to perform any tasks and return.
If the method does not return before time expires, the system may kill
the process altogether.
For apps that do not support background execution or are linked
against iOS 3.x or earlier, this method is always called when the user
quits the app. For apps that support background execution, this method
is generally not called when the user quits the app because the app
simply moves to the background in that case. However, this method may
be called in situations where the app is running in the background
(not suspended) and the system needs to terminate it for some reason.
After calling this method, the app also posts a
UIApplicationWillTerminate notification to give interested objects a
chance to respond to the transition.

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.

iOS swift backgound location update when killed/suspended with minimum data loss

I've several questions to ask regarding the location updates in background in swift language.
Let me explain what I'm doing in the app. I'm developing an app which regularly monitors users location (as all of you do) and updates it to the server, so the users movement is tracked and saved for future reference by the user.
Questions
What is the difference between using startMonitoringSignificantLocationChanges Vs startUpdatingLocation?
1.1 If we use startUpdatingLocation does it impact on publishing the app to the App Store?
When the app is killed/suspended (Force closed by the user) it takes some time to restart the location manager from the AppDelegate which causes loss of location data for a period of time. Any possible solution to overcome this?
2.1 The difference of time for restarting is around 30 sec to nearly 1 min, which doesn't triggers the location update and hence the route is not perfect as shown in the image
Output of the App where due to restart the locations are not recieved and hence the route goes over the road.
Code for reference
import UIKit
import GoogleMaps
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
let DBName = "test"
var logFile: FileUtils?
var viewController:ViewController?
var count = 0
var appOpenCount = 0
let totalPath = GMSMutablePath()
var leaveCoordinates = 0
var previousLocation: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0)
var locationManager: CLLocationManager?
var significatLocationManager : CLLocationManager?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
GMSServices.provideAPIKey("*********************")
logFile = FileUtils(fileName: "\(DBName).txt")
logFile!.appendFile("\n\nlaunchOptions : \(launchOptions)")
let defaults = NSUserDefaults.standardUserDefaults()
count = defaults.integerForKey("Enabled")
appOpenCount = defaults.integerForKey("appOpenCount")
if(UIApplication.sharedApplication().backgroundRefreshStatus == UIBackgroundRefreshStatus.Available){
logFile!.appendFile("\nYessss")
} else {
logFile!.appendFile("\nNooo")
}
appOpenCount += 1
defaults.setValue(appOpenCount, forKey: "appOpenCount")
defaults.synchronize()
if count == 0 {
count += 1
defaults.setValue(count, forKey: "Enabled")
defaults.synchronize()
Util.copyFile(count)
}
if let launchOpt = launchOptions{
if (launchOpt[UIApplicationLaunchOptionsLocationKey] != nil) {
logFile!.appendFile("\nExecuted on : significatLocationManager")
self.significatLocationManager = CLLocationManager()
self.significatLocationManager?.desiredAccuracy = kCLLocationAccuracyBest
self.significatLocationManager?.delegate = self
self.significatLocationManager?.requestAlwaysAuthorization()
if #available(iOS 9.0, *) {
self.significatLocationManager!.allowsBackgroundLocationUpdates = true
}
self.significatLocationManager?.startUpdatingLocation()
}else{
logFile!.appendFile("\nExecuted on : locationManager1")
self.locationManager = CLLocationManager()
self.locationManager?.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager?.delegate = self
self.locationManager?.requestAlwaysAuthorization()
if #available(iOS 9.0, *) {
self.locationManager!.allowsBackgroundLocationUpdates = true
}
self.locationManager?.startUpdatingLocation()
}
} else {
logFile!.appendFile("\nExecuted on : locationManager2")
self.locationManager = CLLocationManager()
self.locationManager?.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager?.delegate = self
self.locationManager?.requestAlwaysAuthorization()
if #available(iOS 9.0, *) {
self.locationManager!.allowsBackgroundLocationUpdates = true
}
self.locationManager?.startUpdatingLocation()
}
return true
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let locationArray = locations as NSArray
let newLocation = locationArray.lastObject as! CLLocation
let coordinate = newLocation.coordinate
let tempCoor = CLLocationCoordinate2D(latitude: coordinate.latitude, longitude: coordinate.longitude)
let lat = tempCoor.latitude
let lon = tempCoor.longitude
insert(lat, lon: lon)
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
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.
if self.significatLocationManager != nil {
self.significatLocationManager?.startMonitoringSignificantLocationChanges()
}else{
self.locationManager?.startMonitoringSignificantLocationChanges()
}
logFile!.appendFile("\napplicationDidEnterBackground")
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func insert(lat: Double, lon: Double){
let locationInfo: LocationDetails = LocationDetails()
locationInfo.latitude = lat
locationInfo.longitude = lon
locationInfo.time = NSDate()
let db = "\(DBName)\(count).sqlite"
ModelManager.getInstance(db).addLocationData(locationInfo)
}
}
question-1
What is the difference between using startMonitoringSignificantLocationChanges Vs startUpdatingLocation?
startUpdatingLocation updates the location when it is called first time and then when the distance filter value exceeds.
it uses the GPS when its available, if you use in continuously it Drain your power/battery
Discussion
This method returns immediately. Calling this method causes the
location manager to obtain an initial location fix (which may take
several seconds) and notify your delegate by calling its
locationManager:didUpdateLocations: method. After that, the receiver generates update events primarily when the value in the distanceFilter property is exceeded. Updates may be delivered in other situations though. For example, the receiver may send another notification if the hardware gathers a more accurate location reading.
Calling this method several times in succession does not automatically
result in new events being generated. Calling stopUpdatingLocation in
between, however, does cause a new initial event to be sent the next
time you call this method.
If you start this service and your application is suspended, the
system stops the delivery of events until your application starts
running again (either in the foreground or background). If your
application is terminated, the delivery of new location events stops
altogether. Therefore, if your application needs to receive location
events while in the background, it must include the UIBackgroundModes
key (with the location value) in its Info.plist file.
In addition to your delegate object implementing the
locationManager:didUpdateLocations: method, it should also implement
the locationManager:didFailWithError: method to respond to potential
errors.
startMonitoringSignificantLocationChanges when a significant change in position occurs.
it uses cellular or wifi, when it work the location is changed or its called in the particular Time interval.
Discussion
This method initiates the delivery of location events asynchronously,
returning shortly after you call it. Location events are delivered to
your delegate’s locationManager:didUpdateLocations: method. The first
event to be delivered is usually the most recently cached location
event (if any) but may be a newer event in some circumstances.
Obtaining a current location fix may take several additional seconds,
so be sure to check the timestamps on the location events in your
delegate method.
After returning a current location fix, the receiver generates update
events only when a significant change in the user’s location is
detected. For example, it might generate a new event when the device
becomes associated with a different cell tower. It does not rely on
the value in the distanceFilter property to generate events. Calling
this method several times in succession does not automatically result
in new events being generated. Calling stopMonitoringSignificantLocationChanges in between, however, does
cause a new initial event to be sent the next time you call this
method.
If you start this service and your application is subsequently
terminated, the system automatically relaunches the application into
the background if a new event arrives. In such a case, the options
dictionary passed to the locationManager:didUpdateLocations: method of
your application delegate contains the key
UIApplicationLaunchOptionsLocationKey to indicate that your
application was launched because of a location event. Upon relaunch,
you must still configure a location manager object and call this
method to continue receiving location events. When you restart
location services, the current event is delivered to your delegate
immediately. In addition, the location property of your location
manager object is populated with the most recent location object even
before you start location services.
Note:
Apps can expect a notification as soon as the device moves 500
meters or more from its previous notification. It should not expect
notifications more frequently than once every five minutes. If the
device is able to retrieve data from the network, the location manager
is much more likely to deliver notifications in a timely manner.
for differenciate purpose I taken from here
question-2
If we use startUpdatingLocation does it impact on publishing the app to the App Store?
One of the possible reasons for 2.16 rejection is the absence of GPS battery warning in your app description on the app meta in iTunesConnect - "The continued use of GPS may decrease battery life" or something like that.
for More information related to this
Question-3
When the app is killed/suspended (Force closed by the user) it takes some time to restart the location manager from the AppDelegate which causes loss of location data for a period of time. Any possible solution to overcome this
NO, we Can't Over Come, reason the memory newly Initiated.
startMonitoringSignificantLocationChanges
uses cellular/wifi
not accurate but very energy efficient
iOS wakes up your app at least every 15 mins or so to give location update (https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html)
startUpdatingLocation
uses GPS when available
very accurate but eats so much power
Usually, you can submit app using startUpdatingLocation without any problem as long as you state 'Continued use of GPS running in the background can dramatically decrease battery life.' on the app descriptions. And if you put background mode as 'location', use it for location only and not to do anything else.
You don't seem to make use of beginBackgroundTaskWithExpirationHandler, I suggest you use that when your app is in the background.
Obviously, if user does not want your app to get location at all, they can turn off backgroundFetch from the device settings, and touch luck in that case.

Resources