4

I want to get user location even when the user does not use the app.now i can get location after press home button and application goes to background state, but after a few second location update stoped. And when I'm killing the app location update against stoped. this is my code in app delegate.

 let locationManager = CLLocationManager()
  var LatitudeGPS = String()
  var LongitudeGPS = String()
  var speedGPS = String()
  var Course = String()
  var Altitude = String()
  var bgtimer = Timer()

func applicationDidEnterBackground(_ application: UIApplication) {

    self.doBackgroundTask()
  }

  func beginBackgroundUpdateTask() {

    backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
      self.endBackgroundUpdateTask()
    })

  }

  func endBackgroundUpdateTask() {
    UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
    self.backgroundUpdateTask = UIBackgroundTaskInvalid
  }
func doBackgroundTask() {

  DispatchQueue.global(qos: .background).async {

    self.beginBackgroundUpdateTask()
    self.StartupdateLocation()
    self.bgtimer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(self.bgtimer(timer:)), userInfo: nil, repeats: true)
    RunLoop.current.add(self.bgtimer, forMode: RunLoopMode.defaultRunLoopMode)
    RunLoop.current.run()
    self.endBackgroundUpdateTask()

  }


}


func bgtimer(timer:Timer!){
  print("Fired from Background ************************************")
  updateLocation()
}
  func StartupdateLocation() {
    locationManager.delegate = self
    locationManager.startUpdatingLocation()
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.distanceFilter = kCLDistanceFilterNone
    locationManager.requestAlwaysAuthorization()
    locationManager.allowsBackgroundLocationUpdates = true
    locationManager.pausesLocationUpdatesAutomatically = false
  }

  func updateLocation() {
locationManager.startUpdatingLocation()
    locationManager.stopUpdatingLocation()
    print("Latitude: \(LatitudeGPS)")
    print("Longitude: \(LongitudeGPS)")
    print("Speed: \(speedGPS)")
    print("Heading: \(Course)")
    print("Altitude BG: \(Altitude)")
    DispatchQueue.main.async {
      print(UIApplication.shared.backgroundTimeRemaining)

    }
  }

  func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

    LatitudeGPS = String(format: "%.10f", manager.location!.coordinate.latitude)
    LongitudeGPS = String(format: "%.10f", manager.location!.coordinate.longitude)
    speedGPS = String(format: "%.3f", manager.location!.speed)
    Altitude = String(format: "%.3f", manager.location!.altitude)
    Course = String(format: "%.3f", manager.location!.course)

  }
}

i think after a few second my application terminated and location update stoped. I want to after 20 min that application terminated (os or user) stop updating location to keep the battery charge. where is my problem in location update.

ava
  • 1,148
  • 5
  • 15
  • 44

2 Answers2

24

Couple of things to be changed.

Step 1:

Make sure you have enabled location updates background mode in capabilities section of your project as shown below

enter image description here

Step 2:

And when I'm killing the app location update against stoped.

Quoting from apple docs

If you start this service and your app is subsequently terminated, the system automatically relaunches the app into the background if a new event arrives. In such a case, the options dictionary passed to the application(:willFinishLaunchingWithOptions:) and application(:didFinishLaunchingWithOptions:) methods of your app delegate contains the key location to indicate that your app 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.

link : https://developer.apple.com/documentation/corelocation/cllocationmanager/1423531-startmonitoringsignificantlocati

important thing to notice in above statement is

Upon relaunch, you must still, configure a location manager object and call this method to continue receiving location events.

Meaning, your current location manager will not be of much use and you should create a new one and configure the new instance and call startMonitorSignificantLocationChanges again.

So iOS will send location updates to terminated apps only when you use startMonitoringSignificantLocationChanges.

All that are applicable only if your app was terminated and iOS relaunched it on receiving location update. But if your app is simply in background you need not do any thing on using startMonitorSignificantLocationChanges

on the other hand startUpdatingLocation will work only when app is in background/foreground mode. iOS will stop updating location if your app gets suspended or killed.

If you start this service and your app is suspended, the system stops the delivery of events until your app starts running again (either in the foreground or background). If your app is terminated, the delivery of new location events stops altogether. Therefore, if your app needs to receive location events while in the background, it must include the UIBackgroundModes key (with the location value) in its Info.plist file.

link: https://developer.apple.com/documentation/corelocation/cllocationmanager/1423750-startupdatinglocation

So modify your code

 locationManager.startMonitoringSignificantLocationChange()

Ok that was about proper usage of startMonitoringSignificantLocationChanges and startupdatinglocation. Now timer for mistakes in your code.

Mistake 1:

self.bgtimer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(self.bgtimer(timer:)), userInfo: nil, repeats: true)

Using timer to get timely updates on location. Thats not how it works!!! You cant run Timer forever. Timer stops as soon as your app suspends or gets terminated. Location Manager informs the app when location changes and you should rely on that only. You cant run timer to timely check location updates. It won't run in suspended or terminated state.

Mistake 2:

  func updateLocation() {
    locationManager.startUpdatingLocation()
    locationManager.stopUpdatingLocation()

Why start and stopping update locations in subsequent statements? That does not make much sense.

Mistake 3:

func StartupdateLocation() {
    locationManager.delegate = self
    locationManager.startUpdatingLocation()

Your StartupdateLocation gets called multiple time and every time you called this method you are repeatedly calling startUpdatingLocation on same instance of location manager. You need not do that! You can call startUpdatingLocation or startMonitoringSignificantLocationChange only once.

Sandeep Bhandari
  • 19,999
  • 5
  • 45
  • 78
  • 1
    if i remove the timer i should call update location in func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) ? – ava Nov 15 '17 at 06:46
  • i can add this tow method for one instance of location manager : locationManager.startMonitoringSignificantLocationChanges() locationManager.startUpdatingLocation() – ava Nov 15 '17 at 06:47
  • @ava: Bingo buddy. You should update locations in locationManager – Sandeep Bhandari Nov 15 '17 at 06:47
  • @ava : To be frank I never tried it but you should be able to do that. But what will you do if user kills the app without putting it to background ??? – Sandeep Bhandari Nov 15 '17 at 06:50
  • i call self.doBackgroundTask() in applicationWillTerminate but thats not worked – ava Nov 15 '17 at 06:53
  • thanks for all comment. i remove timer after you said and when i change my location i can see location update in console. but there is a problem in terminated state. i use simulator for testing and when terminate app cant change location in Xcode and simulator – ava Nov 15 '17 at 07:00
  • I think that because I can't update the location, I do not notice the changes in terminated state – ava Nov 15 '17 at 07:02
  • @ava : You can check location updates in your simulator. Select simulator -> debug ->location ->Freeway ride – Sandeep Bhandari Nov 15 '17 at 07:03
  • 1
    @ava : You won't be able to see console log though as Xcode's debug session is terminated as soon as you killed the app. You can write it to a file in local directory to check location logs later :) – Sandeep Bhandari Nov 15 '17 at 07:04
  • 2
    Finally : "i call self.doBackgroundTask() in applicationWillTerminate but thats not worked" thats because applicationWillTerminate will not be called if your app uses background execution. If you provide background execution (expiration handler) when user kills the app, app will not be terminated rather goes to background mode :) – Sandeep Bhandari Nov 15 '17 at 07:06
  • 1
    That leads to your other question : "can add this tow method for one instance of location manager : locationManager.startMonitoringSignificantLocationChanges() locationManager.startUpdatingLocation()" Yes you can!. When app enters foreground check if locationManager instance exists and if yes stop monitoring significant location change and switch to startUpdatingLocation :) When app is killed or put to background because of expiration handler you will get control back in your expiration handler so there change it to monitor significant location changes :) Hope it helps :) – Sandeep Bhandari Nov 15 '17 at 07:08
  • dear Sandeep thanks for your help. They were awesome and very helpful. – ava Nov 15 '17 at 07:13
  • @ava : Pleasure is mine :) You can read about will terminate here https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623111-applicationwillterminate – Sandeep Bhandari Nov 15 '17 at 07:15
  • 2
    @SandeepBhandari ava does any of you have a code to understand this thing properly? – Abhishek Jan 31 '20 at 12:07
1

iOS :- When App Is Suspended Or Terminated Than You can Track User Location Using

startMonitorSignificantLocationChanges

Using startMonitorSignificantLocationChanges You can Track User Location When User Move Least 500m then You can Access Location. Suppose User Continually Move 50km then In This 50km you can access User Location at least 10 time so this one is not More accurate.

But I have trick for Continually track User Location when app is Suspended or Terminated than also You can Track User Location without any Limites. Yes This is Possible in iOS.

Step 1 :

import CoreLocation

class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    
}

Step 2 :

 let locationManager = CLLocationManager()
@Published var location: CLLocationCoordinate2D?

override init() {
    super.init()
    locationManager.delegate = self
    locationManager.desiredAccuracy = LocationAccuracyBestForNavigation
    locationManager.distanceFilter = 10
    locationManager.activityType = .otherNavigation
    locationManager.allowsBackgroundLocationUpdates = true
    locationManager.pausesLocationUpdatesAutomatically = false
}

Step 3: :- Get Location Permission for first when use app and After Always Allow Location so When App is Background, Foregraound, suspended or Terminated than Track User Location

func PermissionWhenUseApp() {
    locationManager.requestWhenInUseAuthorization()
}

func PermissionAlawyaUseApp() {
    locationManager.requestAlwaysAuthorization()()
}

Step 4 : -

In Step for We can Add Logic for Track User Location When app is Suspended This logic Implement in CLLocationManagerDelegate using

 func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
}

and also we can add 2-3 More Function for Handle Location when App is suspended or when app is Terminated or Background

if You want full Code than Contact me in Telegram @YouKnowBKA

i have Test this Code More than 5 day and Work Well and Good So Contact for More details