Update location when user is in background - swift - ios

I've had an issue for like 3-4 months. I've tried everything you can ever imagine to get this to work, but I really can't. Now I'm looking for your help to fix this issue.
I've an application, when you press a start button it should get locations. (Works perfectly fine when ur on the application.)
But once you leave the application, (not killing the process) and goes to the background. The polyline is not drawing like it should. It pauses or something.
I need someone either who can help me here, or create a chatroom with me so we can discuss and I will send the rest of the code.
Here is parts of it, which I think is the most important.
Inside the viewDidLoad
let app = UIApplication.shared
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(notification:)), name: UIApplication.willResignActiveNotification, object: app)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive(notification:)), name: UIApplication.didBecomeActiveNotification, object: app)
-
#objc func applicationWillResignActive(notification: NSNotification)
{
start = CFAbsoluteTimeGetCurrent()
print("Background entered")
startReceivingSignificantLocationChanges()
}
#objc func didBecomeActive(notification: NSNotification)
{
let elapsed = CFAbsoluteTimeGetCurrent() - start
counter = counter + Int(elapsed)
print("Returned to application")
locationManager.stopMonitoringSignificantLocationChanges()
}
< Inside the start button.
//Checking userpermission to allow map and current location
if (CLLocationManager.locationServicesEnabled())
{
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.showsBackgroundLocationIndicator = true
//Retrieve current position
if let userLocation = locationManager.location?.coordinate
{
//Zooming in to current position
let viewRegion = MKCoordinateRegion(center: userLocation, latitudinalMeters: 200, longitudinalMeters: 200)
mapView.setRegion(viewRegion, animated: false)
//Creating a start annotation
if locations.isEmpty
{
let annotation = MKPointAnnotation()
annotation.title = "Start"
annotation.coordinate = userLocation
mapView.addAnnotation(annotation)
}
self.locations.append(userLocation)
print(self.locations, "First")
//Starts the walk-timer, with interval: 1 second
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)
//Sending to update
update()
}
}
< Background worker
func startReceivingSignificantLocationChanges()
{
let authorizationStatus = CLLocationManager.authorizationStatus()
if authorizationStatus != .authorizedAlways
{
return
}
if !CLLocationManager.significantLocationChangeMonitoringAvailable()
{
// The service is not available.
return
}
else
{
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.distanceFilter = 100.0 //100.0 meters
locationManager.activityType = .fitness
locationManager.allowsBackgroundLocationUpdates = true
locationManager.delegate = self
locationManager.startMonitoringSignificantLocationChanges()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations
locations: [CLLocation])
{
let lastLocation = locations.last!
self.locations.append(lastLocation.coordinate)
print("Locations retrieved from background: ", self.locations)
}
There is a lot more I've to show you. But unfortunately it would be way too much...

Please enable the Background Modes from the capabilities of the project and enable the 'Location updates'. After enabling this, the only configuration to get the updates in the background(not in killed state) is to set'allowsBackgroundLocationUpdates' to true(which you have done already).
Here the significant location changes are only needed when you want to get the location when the application is killed by the user. This significant location change will launch the application in background and read the location of the device. For more information on getting location in the background follow :
https://developer.apple.com/documentation/corelocation/cllocationmanager/1620568-allowsbackgroundlocationupdates
For significant location changes while the application is in killed state, follow below link. This is in objective C but it can be easily done in swift also.
http://mobileoop.com/getting-location-updates-for-ios-7-and-8-when-the-app-is-killedterminatedsuspended
Hope this helps.

Related

didUpdateLocations not getting called swift 3

I am developing a screen that will need to update location every 10 minutes using a timer. Other than that It only needs to update location at first load and when the view appears to the user again. It should stop monitoring once the the user goes to another view.
I have a code that is supposed to do this, but the issue is that the didUpdateLocations method is not called at any point. Also the map does not show the current location (I use simulated locations).
I have correctly set up the permissions and the app worked fine when it was setup to just show the location. I need to do this to reduce battery consumption.
Here is my related code:
In viewDidLoad:
if #available(iOS 8.0, *) {
self.locationManager.requestAlwaysAuthorization()
}
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.distanceFilter = 1000
self.locationManager.activityType = CLActivityType.automotiveNavigation
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.pausesLocationUpdatesAutomatically = false
self.locationManager.startUpdatingLocation()
self.map.showsUserLocation = true
In viewWillAppear:
self.map.showsUserLocation = true
self.locationManager.startUpdatingLocation()
In viewWillDisappear:
self.map.showsUserLocation = false
self.locationManager.stopUpdatingLocation()
In didUpdateLocations: (at last line)
self.locationManager.stopUpdatingLocation()
Timer Function: (this gets called fine)
Timer.scheduledTimer(timeInterval: 600.0, target: self, selector: #selector(HomePageViewController.updateLocationFromTimer), userInfo: nil, repeats: true)
#objc func updateLocationFromTimer()
{
self.locationManager.startUpdatingLocation()
}
I also tried to catch any error with the following code:
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
but it did not get called.
I would love to know why the location is not being updated and why the map is not showing the location. Please help.
Make sure you assign the delegate:
self.locationManager.delegate = self
I did not work for me either and discovered that the place at which you set the delegate impacts this.
E.g. this did not work:
var locationManager = CLLocationManager() {
didSet {
locationManager.delegate = self
}
}
Setting it at a later moment did work as expected. Not sure why to be honest, but maybe this helps someone.

CLLocation distance tracking starts off incorrectly

I am new to Swift (and this website, so sorry if I am doing anything wrong), and I am trying to make a running app that tracks the user's location. While the function I used to track the distance works, it doesn't start at 0. When I hit the start button, the distance starts at a random number and then it starts tracking from there.
My question is: Is there something I am not addressing something correctly? If so, is there a way to fix it so that the tracking is more accurate? Here is what I have so far:
override func viewDidLoad() {
super.viewDidLoad()
stopwatchLabel.text = "00:00.00"
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.activityType = .fitness
locationManager.distanceFilter = 10.0
mapView.showsUserLocation = true
startLocation = nil
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Location Delegate Methods
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.002, longitudeDelta: 0.002))
self.mapView.setRegion(region, animated: true)
if startLocation == nil {
startLocation = locations.first
}
var distance = startLocation.distance(from: location!)
let lastDistance = location?.distance(from: location!)
distance += lastDistance!
distanceString = "\(distance)"
distanceLabel.text = distanceString
}
Here is what the app looks like:
the run screen
I realize that other people have asked similar questions, but the questions either have no answer, or they are in a different language (such as Objective-C). If this question has been answered before and I'm just overlooking it, could someone please link the answer to me? Thank you!
When the location manager starts, the first location returned is the cached, last know location. You need to check for this, via the timestamp, as well as check for the level of accuracy that is returned. Something like this in your didUpdateLocations delegate:
let newLocation = locations.last
let timeDiff = newLocation?.timestamp.timeIntervalSinceNow
let accuracyNeeded:CLLocationAccuracy=100.0
if timeDiff < 5.0 && (newLocation?.horizontalAccuracy)!<=accuracyNeeded{
//your code here
}
You have to allow the sensors time to warm up.
Here is a typical didUpdateLocations implementation. We keep track of both the time elapsed since we started updating locations and the improving horizontal accuracy as the sensors warm up. If the horizontal accuracy doesn't improve in a reasonable time, we give up.
You will need a nil property, a Date?, called startTime, and constants REQ_TIME and REQ_ACC. self.stopTrying() turns off updates and resets startTime to nil.
let loc = locations.last!
let acc = loc.horizontalAccuracy
let time = loc.timestamp
let coord = loc.coordinate
if self.startTime == nil { // Date? property, keep track of time
self.startTime = Date()
return // ignore first attempt
}
let elapsed = time.timeIntervalSince(self.startTime)
if elapsed > REQ_TIME { // required time before giving up
self.stopTrying()
return
}
if acc < 0 || acc > REQ_ACC { // desired accuracy
return // wait for the next one
}
// got it
print("You are at \(coord.latitude) \(coord.longitude)")

location in the background does not work

I am developing a code that should get my location every 10 minutes and salvation in CoreData. When I walk into background with conectavo app to xcode can not see the log that the service is running, but when I go out walking the streets he simply not saved or saved too few times.
This is part of my code that should do this function.See save in codeData:
var saveLocationInterval = 60.0
func applicationDidEnterBackground(application: UIApplication) {
UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
self.timer = NSTimer.scheduledTimerWithTimeInterval(saveLocationInterval, target: self, selector: #selector(AppDelegate.saveLocation), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.timer, forMode: NSRunLoopCommonModes)
locationController.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
saveLocation()
}
func saveLocation(){
print("======")
let logUser = NSEntityDescription.insertNewObjectForEntityForName("LOG_GPS", inManagedObjectContext: self.managedObjectContext) as! LOG_GPS
if locationController.location == nil{
logUser.latitude = ""
logUser.longitude = ""
} else {
logUser.latitude = "\(locationController.location!.coordinate.latitude)"
logUser.longitude = "\(locationController.location!.coordinate.longitude)"
}
logUser.velocidade = userSpeed > 0 ? Int(userSpeed) : 0
logUser.address = "\(userSpeed)"
if _usuario.chave != nil {
logUser.chave_usuario = "\(_usuario.chave!)"
}
if _empresa.chave != nil {
logUser.chave_licenca = "\(_empresa.chave!)"
}
print("localizaƧao salva no bd \(logUser.latitude)")
let date = NSDate()
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd/MM/yy HH:mm:ss"
let dateString = dateFormatter.stringFromDate(date)
logUser.data = dateString
do {
try self.managedObjectContext.save()
} catch {
}
}
Another major error in my code I can not solve is the User's speed. In the method the low I'm trying to save your speed in a variable and then save the CoreData however this is me always returning a negative value:
func locationManager(manager: CLLocationManager, didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation) {
var speed: CLLocationSpeed = CLLocationSpeed()
speed = newLocation.speed
print(speed * 3.6)
userSpeed = speed * 3.6
}
this is my background mode
Possible solution 1
You need a key in your Info.plist that describes why your app needs background location.
Go to your Info.plist, find the Bundle Version key and click the + that appears when you hover over that. Then add the key NSLocationAlwaysUsageDescription, set it to be a string, and set the value as whatever you want the description to be, like "We need your location in the background so we share your location with friends."
Now your app should work. If it doesn't....
Possible solution 2 (more likely solution if you know what you're doing)
With iOS 9, Apple made it so that apps on physical devices need a special line of code to run location services in the background. The change was not widely reported on (if at all?) but I managed to figure this one out a while ago. Here's what you need to do to get location services working in the background on physical devices again:
In your main location tracking view controller's ViewDidLoad put...
if #available(iOS 9.0, *) {
locationManager.allowsBackgroundLocationUpdates = true
} else {
// You don't need anything else on earlier versions.
}
This will (mysteriously enough) likely be all you need to solve your problem.

Run a Swift 2.0 app forever in background to update location to server

I have written the below code that has a timer that calls a callback function every minute. When the app goes to the background I have started another timer that calls the same callback method, but the background timer works for only three minutes.
I understand that Apple allows background tasks for only three minutes. My app is more like a tracking app that tracks the location of the user every minute even when the app is in background, so I need to implement this functionality.
I learned that beginBackgroundTaskWithExpirationHandler is to be used but I don't know whether my implementation is correct.
Note: I have Required background modes in plist toApp registers for location updates.
Any working code or links are much appreciated.
override func viewDidLoad() {
super.viewDidLoad()
timeInMinutes = 1 * 60
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
var timer = NSTimer.scheduledTimerWithTimeInterval( timeInMinutes, target: self, selector: "updateLocation", userInfo: nil, repeats: true)
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
self.latitude = locValue.latitude
self.longitude = locValue.longitude
if UIApplication.sharedApplication().applicationState == .Active {
} else {
backgroundTaskIdentifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({ () -> Void in
self.backgroundTimer.invalidate()
self.backgroundTimer = NSTimer.scheduledTimerWithTimeInterval( 60.0, target: self, selector: "updateLocation", userInfo: nil, repeats: true)
})
}
}
func updateLocation() {
txtlocationLabel.text = String(n)
self.n = self.n+1
var timeRemaining = UIApplication.sharedApplication().backgroundTimeRemaining
print(timeRemaining)
if timeRemaining > 60.0 {
self.GeoLogLocation(self.latitude,Longitude: self.longitude) {
results in
print("View Controller: \(results)")
self.CheckResult(String(results))
}
} else {
if timeRemaining == 0 {
UIApplication.sharedApplication().endBackgroundTask(backgroundTaskIdentifier)
}
backgroundTaskIdentifier2 = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({ () -> Void in
self.backgroundTimer.invalidate()
self.backgroundTimer = NSTimer.scheduledTimerWithTimeInterval( 60.0, target: self, selector: "updateLocation", userInfo: nil, repeats: true)
})
}
}
Periodic location updates are a bit tricky in IOS.There's a good thread that discusses this, you can read more here
iOS will terminate your app after a few minutes, regardless if your timer is running or not. There is a way around this though, I had to do something similar when writing an ionic app so you can check out the code for this here, that link has a swift class that manages the periodic location updates in iOs.
In order to get periodic locations in the background, and not drain the battery of the device, you need to play with the accuracy of the location records, lower the accuracy of the location manager setting its desiredAccuracy to kCLLocationAccuracyThreeKilometers, then, every 60 seconds you need to change the accuracy to kCLLocationAccuracyBest, this will enable the delegate to get a new, accurate location update, then revert the accuracy back to low. The timer needs to be initialized every time an update is received.
There's also a way to wake up the app in the background after its been killed by the user, use the app delegate to have the app listen for significant changes in location before its killed. This will wake up the app in the background when the user's location makes a big jump (can be around 200ms). When the app wakes up, stop monitoring for significant changes and restart the location services as usual to continue the periodic updates.
Hope this helps.
Update
In Swift 2 you'll also need:
self.locationManager.allowsBackgroundLocationUpdates = true
You can use library for background location tracking, example of use:
var backgroundLocationManager = BackgroundLocationManager()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
backgroundLocationManager.startBackground() { result in
if case let .Success(location) = result {
LocationLogger().writeLocationToFile(location: location)
}
}
return true
}
It's working when application is killed or suspended.

Process location data in the background

I am currently receiving location updates and want to periodically send an update to a server via an api call. Everything works when launching the app but updates in the background are really inconsistent.
Here's what I'm currently trying to do:
var lastUpdateTime: Double?
func locationManager(manager: CLLocationManager, didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation)
{
userLocation = newLocation
if let lastUpdate = lastUpdateTime {
let thisUpdate = NSDate().timeIntervalSince1970
let timeInterval = thisUpdate - lastUpdate
if timeInterval > 60 {
if
let latitude = userLocation?.coordinate.latitude,
let longitude = userLocation?.coordinate.longitude
{
// Make API call to update location
lastUpdateTime = NSDate().timeIntervalSince1970
}
}
} else {
lastUpdateTime = NSDate().timeIntervalSince1970
}
}
I've read what I can find regarding background tasks and location updates but honestly I'm a bit lost. If it helps, the api call is made through Alamofire if that has an effect on how often it gets executed.
EDIT:
Added CLLocationManager options...
locationManager.delegate = self
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
For getting location updates in background , app should enable 'location update' background mode in capabilities tab in project settings.
Apart from this, required background location key(NSLocationAlwaysUsageDescription) needs to be added into the info.plist file. If these two things are done correctly, app will receive location updates continuously in background when call 'startUpdatingLocation'

Resources