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.
Related
Still very new to Swift. I have come from an Android background where there is BroadcastReceiver that can deliver location info to a service even though the app isn't running.
So I was looking for something similar in iOS/Swift and it appears that before this wasn't possible but it may be now. I am developing for iOS 10 but would be great if it was backwards compatible.
I found
startMonitoringSignificantLocationChanges
which I can execute to start delivering location updates, although this raises a few questions. Once I call this and my app is NOT running, are the updates still being sent ? And how would the app wake up to respond ?
Also restarting the phone and when it return, does this mean I still need call startMonitoringSignificantLocationChanges again meaning that I would have to wait for the user to execute my app. Or does it remember the setting after reboot ?
Still a little confused how to get around this, here's a brief explanation of what I am trying to do.
I would like to update the location of the phone even though the app is not running, this would be sent to a rest service every so often.
This way on the backend services I could determine if somebody is within X meters of somebody also and send them a push notification.
It may or may not be a good solution but if I were you I would have used both startMonitoringSignificantLocationChanges and regionMonitoring.
Here is the sample I made which worked well with iOS 13.
Lets take regionMonitoring first. We have certainly no problems when the app is in foreground state and we can use the CLLocationManager's didUpdate delegate to get the location and send it to the server.
Keep latest current location in AppDelegate's property, lets say:
var lastLocation:CLLocation?
//And a location manager
var locationManager = CLLocationManager()
We have two UIApplicationDelegates
func applicationDidEnterBackground(_ application: UIApplication) {
//Create a region
}
func applicationWillTerminate(_ application: UIApplication) {
//Create a region
}
So whenever the user kills the app or makes the app go to background, we can certainly create a region around the latest current location fetched. Here is an example to create a region.
func createRegion(location:CLLocation?) {
if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
let coordinate = CLLocationCoordinate2DMake((location?.coordinate.latitude)!, (location?.coordinate.longitude)!)
let regionRadius = 50.0
let region = CLCircularRegion(center: CLLocationCoordinate2D(
latitude: coordinate.latitude,
longitude: coordinate.longitude),
radius: regionRadius,
identifier: "aabb")
region.notifyOnExit = true
region.notifyOnEntry = true
//Send your fetched location to server
//Stop your location manager for updating location and start regionMonitoring
self.locationManager?.stopUpdatingLocation()
self.locationManager?.startMonitoring(for: region)
}
else {
print("System can't track regions")
}
}
Make use of RegionDelegates
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("Entered Region")
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("Exited Region")
locationManager?.stopMonitoring(for: region)
//Start location manager and fetch current location
locationManager?.startUpdatingLocation()
}
Grab the location from didUpdate method
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if UIApplication.shared.applicationState == .active {
} else {
//App is in BG/ Killed or suspended state
//send location to server
// create a New Region with current fetched location
let location = locations.last
lastLocation = location
//Make region and again the same cycle continues.
self.createRegion(location: lastLocation)
}
}
Here I have made a 50m region radius circle. I have tested this and it is called generally after crossing 100m from your center point.
Now the second approach can me using significantLocationChanges
On making the app go background or terminated, we can just stop location manager for further updating locations and can call the startMonitoringSignificantLocationChanges
self.locationManager?.stopUpdatingLocation()
self.locationManager?.startMonitoringSignificantLocationChanges()
When the app is killed, the location is grabbed from didFinishLaunching method's launchOptions?[UIApplicationLaunchOptionsKey.location]
if launchOptions?[UIApplicationLaunchOptionsKey.location] != nil {
//You have a location when app is in killed/ not running state
}
Make sure to keep BackgroundModes On for Location Updates
Also make sure to ask for locationManager?.requestAlwaysAuthorization() by using the key
<key>NSLocationAlwaysUsageDescription</key>
<string>Allow location</string>
in your Info.plist
There can be a third solution by taking 2 LocationManagers simultaneously.
For region
Significant Location Changes
As using significantLocationChanges
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.
as per the give Apple Doc
So it totally depends on your requirements as the location fetching depends on many factors like the number of apps opened, battery power, signal strength etc when the app is not running.
Also keep in mind to always setup a region with good accuracy.
I know that this will not solve your problem completely but you will get an idea to move forward as per your requirements.
I'm developing an app which connects to the beacons. I'm able to run the app and also to detect beacons when app is in background (I send local notifications in the didRangeBeacons method and I receive them). I need to run a piece of code in the background when a beacon is detected. How can I do? I tried to write my Alamofire call exactly after sending the local notification, but nothing happens. Some suggestions?
When an app is in the background and it gets a didRangeBeacons callback, it only gets 5 seconds to run by the operating system before it is suspended. This will close any web service connections that are open at that time. You can extend this background running time from 5 seconds to 180 seconds upon request. Below is an example in Swift 3 that shows how to do that.
var threadStarted = false
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
func extendBackgroundRunningTime() {
if (self.backgroundTask != UIBackgroundTaskInvalid) {
// if we are in here, that means the background task is already running.
// don't restart it.
return
}
print("Attempting to extend background running time")
self.backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "DummyTask", expirationHandler: {
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
})
if threadStarted {
print("Background task thread already started.")
}
else {
threadStarted = true
DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default).async {
while (true) {
// A dummy tasks must be running otherwise iOS suspends immediately
Thread.sleep(forTimeInterval: 1);
}
}
}
}
By adding code like this, it is much more likely that your web service call will complete before iOS suspends your app.
You can call the extendBackgroundRunningTime() from your didRangeBeacons or didEnterRegion methods.
You only have a limited time for doing stuff in background when you get the didEnter/didRange callback.
You should checkout background tasks to get more time in background to call your server.
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.
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.
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))
}