Process location data in the background - ios

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'

Related

Update location in Background

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.
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
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.

How to notify user when he enters a CLLocation?

SITUATION:
I followed the following tutorial:
https://www.raywenderlich.com/95014/geofencing-ios-swift
PROBLEM:
The following functions never get triggered:
AppDelegate.swift
func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
if region is CLCircularRegion {
handleRegionEvent(region)
}
}
func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
if region is CLCircularRegion {
handleRegionEvent(region)
}
}
func handleRegionEvent(region: CLRegion!) {
print("Geofence triggered!")
// Show an alert if application is active
if UIApplication.sharedApplication().applicationState == .Active {
if let message = notefromRegionIdentifier(region.identifier) {
if let viewController = window?.rootViewController {
showSimpleAlertWithTitle("Congratulations", message: "You just found: " + message , viewController: viewController)
}
}
} else {
// Otherwise present a local notification
let notification = UILocalNotification()
notification.alertBody = "You just found: " + notefromRegionIdentifier(region.identifier)!
notification.soundName = "Default";
UIApplication.sharedApplication().presentLocalNotificationNow(notification)
}
}
QUESTION:
The tutorial was written for iOS 8. I am currently on iOS 9.3. What caused this issue in your opinion and how do I fix it ?
You didn't show the code that you use to set up CL - which is probably where your problem lies.
Did you edit info.plist?
Are you requesting permission?
Did you call one of the start functions on the CL manager?
Make sure of two things :-
1.) You have added These to your viewDidLoad() :-
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestWhenInUseAuthorization()
locationManager.startMonitoringSignificantLocationChanges()
locationManager.startUpdatingLocation()
Another alternative to requestWhenInUseAuthorization() and startUpdatingLocation() initialisation in specific to Swift 2.2, since in Swift 2.2 the string literals for selectors is deprecated, and instead there this new operator #selector that you need to be using. :-
you can also use :-
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.startMonitoringSignificantLocationChanges()
if locationManager.respondsToSelector(#selector(locationManager.requestWhenInUseAuthorization)) {
locationManager.requestWhenInUseAuthorization()
}
else {
locationManager.startUpdatingLocation()
}
//Prefer the FIRST ONE.
2.) You have updated your info.plist with :-
NSLocationAlwaysUsageDescription : String :-> I need location.
NSLocationWhenInUseUsageDescription: String :-> I need location.
privacy - location usage description: String :-> I need location.
Edit I need location according to the app's need
PS :- If it still not calls your locationManager functions
Simulator :- look for location settings of your app in your simulator settings.
Device: - Go in settings > Privacy > Location services > Your app > Always.
you also might find this explanation useful : - https://stackoverflow.com/a/26090094/6297658
initialize your location manager in app delegate on did finish launching

Get user current location(lat/long) in all app state not always only when My app required without location manager delegate

I am working on watch heart beat app and I want when user heart rate critical then we will get his current location in(foreground, background and terminate) and send it to our server. Is there any way through which I get user location only at that position. I don't want to update his location every time.
To get user location you have to declare :
let locationManager = CLLocationManager()
in your controller.
Then, in viewDidLoad you have to request for location and initialize the CLLocationManager get process :
// Ask for Authorisation from the User.
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
You will get location in CLLocationManagerDelegate :
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
var location:CLLocationCoordinate2D = manager.location.coordinate
print("locations = \(location.latitude) \(location.longitude)")
}
In your info.plist you have to add NSLocationAlwaysUsageDescription and custom alert message to show while requesting for location.
cheers...

Standard Location based iOS app not waking up after suspended IOS

I am developing a location based app which is supposed to fetch user location always.Im using standard location service. But the problem is that the app after keeping idle for some time in background will not fetch the coordinates even after we move to some other locations. As per apple documentation, when a new location arrives, app should wake up automatically, but that is not happening here. I'm sharing the code and using to fetch location and screenshot of my plist.
class SALocation: NSObject,CLLocationManagerDelegate
{
static let sharedInstance : SALocation = SALocation()
var locationManager : CLLocationManager!
var location : CLLocation!
var address : String!
var latitude : NSString?
var longitude : NSString?
var isAdderssLoaded : Bool = false
var locdictionary : NSMutableDictionary = NSMutableDictionary()
func startLocationManager()
{
if self.locationManager == nil
{
self.locationManager = CLLocationManager()
if CLLocationManager.locationServicesEnabled(){
print("location service enabled")
}
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.distanceFilter = kCLDistanceFilterNone
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
if ( Float (UIDevice.currentDevice().systemVersion) >= 9) {
if #available(iOS 9.0, *) {
self.locationManager.allowsBackgroundLocationUpdates = true
} else {
// Fallback on earlier versions
};
}
self.locationManager.startUpdatingLocation()
//self.locationManager.stopMonitoringSignificantLocationChanges()
}
else
{
self.locationManager.startUpdatingLocation()
}
}
// MARK: CLLocationManagerDelegate
func locationManager(manager: CLLocationManager, didFailWithError error: NSError)
{
UIAlertView(title:"Alert", message:error.description, delegate: nil, cancelButtonTitle:nil, otherButtonTitles:"Ok").show()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
if locations.count > 0
{
self.location = locations[0]
/* storing date and location to plist
*/
let datenow = NSDate()
let dateformatternow = NSDateFormatter ()
dateformatternow.dateFormat = "yyyyMMdd HH:mm:ss"
let timenow:NSString = dateformatternow.stringFromDate(datenow)
let documetsdirectorypath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last
latitude = NSString(format: "%f",self.location.coordinate.latitude)
longitude = NSString (format: "%f",self.location.coordinate.longitude)
let latlong : NSString = NSString(format:"%#~%#",latitude!,longitude!)
NSUserDefaults.standardUserDefaults().setObject(latlong, forKey: "latlong")
let aFilePath = NSString(format: "%#/location.plist",documetsdirectorypath!)
locdictionary.setObject(latlong, forKey: timenow as String)
locdictionary.writeToFile(aFilePath as String, atomically: true)
///////////// ||storing date and location to plist code ends here||\\\\\\
// self.getAddressFromLocation(locations[0] )
// if (NSUserDefaults.standardUserDefaults().objectForKey(SettingAppRefresh) != nil)
// {
// if (NSUserDefaults.standardUserDefaults().objectForKey(SettingAppRefresh) as! NSString).isEqualToString(FalseString)
// {
// // self.locationManager.stopUpdatingLocation()
// }
// }
}
}
}
What i'm doing here is just get location and write it to a plist file. This works in foreground, background etc fine. But when i keep the app idle for 20 minutes, location is not fetched even if i move to some other locations as the app is suspended
Capabilities tab looks like this
To start location in background you must start background service from the following path
Click on your name -> Click on your app name (target) -> goto capabilities -> find the background mode -> enable the location update mode
I am not sure you started that or not because you not put any screenshot about this.
And also check that your user started background refresh in settings.refer below link for this.
Background App Refresh checking, enabling and disabling programatically for whole device and for each particular application in iOS 7
Update::
For location update in background used below link(objective c)
http://www.creativeworkline.com/2014/12/core-location-manager-ios-8-fetching-location-background/
Well, I don't know how you're getting location updates - significant-location change as example and how you exit from background.
I suggest checking if your app is truly in background mode - UIApplication.sharedApplication().applicationState as it can be terminated.
And I also suggest checking out Apple's Execution States for Apps. - especially for your possible use case Implementing Long-Running Tasks part. There is also a good tutorial at rayywenderlich.com called Background modes.
Please use
self.locationManager.requestAlwaysAuthorization()
and don't forget to update your Info.plist to define the NSLocationAlwaysUsageDescription key.

locationManager didUpdateLocations fires twice on device, only once on simulator

Same code, I'm assuming that the device is actually updating the location twice for some reason, even though I only call startUpdatingLocation() once and I run some stopUpdatingLocations() inside of didUpdateLocations
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
manager.stopUpdatingLocation()
let loc: CLLocation = locations[locations.count - 1]
let id = 0
let type = 0
let number = 0
createNewDataPoint(id, loc: loc, type: type, number: number)
}
In this case, createNewDataPoint gets called twice, creating 2 new datapoints. It only happens once in the simulator, so I'm assuming it has something to do with the actual device and the GPS since the simulator fakes its location.
startUpdatingLocation() is only in my code one time, on a button. Basically, you click the button, go go manager.startUpdatingLocations(), didUpdateLocations hits once on simulator, twice on device (identical coordinates) and it creates 2 new data points.
The only other code that mentions anything related is setting the accuracy, filter, authorization requests, and the previously mentioned startUpdatingLocation(). Is there something I can do to make sure I'm not creating twice as many data points as necessary?
Location Manager delegate methods can be called very frequently and at any time.
You may however, apply following algorithm to safeguard yourself:
Create a global bool say didFindLocation.
Set didFindLocation to false when you call startUpdatingLocation.
Inside delegate call back didUpdateLocations:, if didFindLocation was false, set didFindLocation to true and then call stopUpdatingLocation.
Hope this helps.
The best way is do as following:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
manager.stopUpdatingLocation()
manager.delegate = nil
}
Best solution for iOS 10.0+
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
[locationManager stopUpdatingLocation]; // stop location manager
locationManager.delegate = nil;
//Your logics...
//This will be called only one time now.
}
But don't forget to set the delegate again.
After getting the desired latitude and longitude just call stopUpdatingLocation()and set the delegate to nil.
In Swift 3:
locationManager.stopUpdatingLocation()
locationManager.delegate = nil
In Objective-C:
[locationManager stopUpdatingLocation]
locationManager.delegate = nil
Here locationManager is the object of CLLocationManager.
You will not get frequently on simulator. and on device when you will move far away then only you get didUpdateLocations. just move in a open space so GPS can identify you device location so it get best accuracy.
Instead of starting / ending the location update and setting delegate to nil, there is a method called requestLocation which is ideal when your application need quick fix on the user's location:
From the docs:
override func viewDidLoad() {
// Create a location manager object
self.locationManager = CLLocationManager()
// Set the delegate
self.locationManager.delegate = self
}
func getQuickLocationUpdate() {
// Request location authorization
self.locationManager.requestWhenInUseAuthorization()
// Request a location update
self.locationManager.requestLocation()
// Note: requestLocation may timeout and produce an error if authorization has not yet been granted by the user
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Process the received location update
}
Use this method when you want the user’s current location but do not need to leave location services running.
#Zumry Mohamed 's solution is right
i try the code like this:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
[self.locationManager stopUpdatingLocation];
self.locationManager.delegate = nil;
self.locationManager = nil;
}
finally this delegate is called only once, i understand now why the problem is occurred, just because manager call the stopUpdatingLocationmethod but system doesn't help us to make the delegate invalid, so we can receive the callback every time location updates due to your desiredAccuracy and distanceFilter property settings of your CLLocationManager, so the final solution is just like what #Zumry Mohamed said, we can manually set the delegate to nil when we stopUpdateLocation. hope it will help you understand what happens why this could solve the problem.
locationManager.startUpdatingLocation() fetch location continuously and didUpdateLocations method calls several times,
Just set the value for locationManager.distanceFilter value before calling locationManager.startUpdatingLocation().
As I set 200 meters(you can change as your requirement) working fine
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 200
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
Another way is to set a time interval to turn on and off the delegate and so the location manager. A sorta of this
var locationManagerUpdate:Bool = false //Global
func scheduledTimerWithTimeInterval(){
// Scheduling timer to Call the function "updateCounting" with the interval of 10 seconds
timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(self.updateLocationManager), userInfo: nil, repeats: true)
}
#objc func updateLocationManager() {
if locationManagerUpdate == false {
locationManager.delegate = self
locationManagerUpdate = true
}
}
extension lm_gest: CLLocationManagerDelegate {
// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if locationManagerUpdate == true {
manager.stopUpdatingLocation()
manager.delegate = nil
}
//your code here...
}

Resources