startMonitoringSignificantLocationChanges not working in swift - ios

I'd added CLLocationManager in my app using Swift in the AppDelegate file.
In the Appdelegate.swift file,
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
In the didbecomeActive method:
func applicationDidBecomeActive(application: UIApplication) {
if((locationManager) != nil)
{
locationManager.stopMonitoringSignificantLocationChanges()
locationManager.delegate = nil
locationManager = nil
}
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.activityType = CLActivityType.OtherNavigation
locationManager.requestAlwaysAuthorization()
locationManager.startMonitoringSignificantLocationChanges()
}
If I use startUpdatingLocation, didUpdateLocations method gets called, but not for startMonitoringSignificantLocationChanges.
Why it's not getting called for startMonitoringSignificantLocationChanges.
I'm testing this in ios simulator. I don't know how to check in device.

It's working, but it's really hard to trigger significant location changes - it usually happens when the device is changing cell towers - I don't think it's possible to do with the simulator.
You'd probably have to get on a bike/car and travel AT LEAST a few kilometres.
There's a trick you can use though, that will trigger significant location change:
Switch Airplaine mode in your iPhone on and off with a few second intervals repeatedly, it should trick the device into thinking that it changed cell towers and trigger the significant location change.

In your simulator, goto Debug->Location->Custom and change location, then test it.

In your simulator, select Features -> Location -> Freeway Drive
Wait a bit for startMonitoringSignificantLocationChanges to trigger didUpdateLocations.

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. startMonitoringSignificantLocationChanges()

Related

continuous iOS location updates in background

I am trying to create a simple iOS app that will continuously track location in the background and notify with high accuracy when the user has entered a specific region (don't want to use region monitoring because it's not accurate or fast enough for what I want).
This app works fine in the foreground but once I go into the background it does not work.
I created a test app to investigate how background location updates work. I made a simple app that just prints out a message when a location update happens (to the log).
What I see is that in foreground mode the updates happen as expected but when I lock my phone and the app switches to background the location updates happen for about 30 seconds and then stop. There is no mention in the Apple docs (that I can find) explaining this behavior.
I made sure that I enabled background processing in the info.plist (done in "capabilities" --> "background modes" --> "location updates")
Here is the code I used to test this:
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
// configure location updates
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 0.1
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("location updated! \(counter)")
counter = counter + 1
}
Any ideas on what I might be missing? I tried different location accuracy settings (such as BestForNavigation, etc) and no change.
Thanks.
You have to add this in your plist :
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
add this
locationManager.allowsBackgroundLocationUpdates = true
I think this might be the answer:
allowsBackgroundLocationUpdates.
Apps that want to receive location updates when suspended must include
the UIBackgroundModes key (with the location value) in their app’s
Info.plist file and set the value of this property to true.
...
The
default value of this property is false.
Are you setting that?
You can add these two line to make it working in background mode:
locationManager!.allowsBackgroundLocationUpdates = true
locationManager!.pausesLocationUpdatesAutomatically = false

Receiving Location even when app is not running in Swift

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.

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

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

CoreLocation Authorization issues - never prompted for authorization

I was testing out CoreLocation to learn how to capture and record the location of the user. I built a simple Master-Detail app which, in the detail pane, showed the user's live location. Everything worked as expected.
Next, I moved on to making my real app which also uses CoreLocation. I built the app with a Master-Detail style and also made it so that when the user opens the detail pane, it should show their current, live, location. However, nothing happens at all.
What I determined after a LOT of debugging and research is that as soon as my locationManager is created and I call locationManager.startUpdatingLocation() the authorization to get the location is changed to denied (or false, or whatever its called). The only way I can get live tracking is build and run the app in Xcode, once it opens in the simulator, open up Location Services and change the app setting to allow location tracking to "Always". Then I can see in the console that locations are being acquired.
I don't understand why I have to keep changing the authorization to "Always" over and over. I have deleted the app on the simulator, done a "clean" from XCode to start fresh, and still it starts on the simulator without authorization.
Anyone have ideas?
UPDATE Now I see that, when I build and it runs in the simulator, I get ONE location, then the authorization is changed to not allow location services.
Here's my code (which worked on another app):
import UIKit
import CoreLocation
class DetailViewController: UIViewController, CLLocationManagerDelegate {
var locationManager : CLLocationManager!
func startTrackingLocation() {
println("tracking engaged")
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization() // <-- this was originally commented out
locationManager.startUpdatingLocation()
println("and we're tracking")
println(locationManager.location)
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
println("location acquired") // <--- Only see this when I manually allow location when app is running
self.newLongLabel?.text = "\(locations[0].coordinate.longitude)"
self.newLatLabel?.text = "\(locations[0].coordinate.latitude)"
}
override func viewDidLoad() {
super.viewDidLoad()
self.startTrackingLocation()
self.configureView()
}
// more class stuff...
If you're certain you have added the key to plist file try checking for it in the didChangeAuthorizationStatus: location manager delegate method.
Be sure you have both keys in plist as type string and with the values you would like to display to the user:
NSLocationAlwaysUsageDescription
NSLocationWhenInUseUsageDescription
- (void) locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{
if (status == kCLAuthorizationStatusAuthorizedWhenInUse || status == kCLAuthorizationStatusAuthorizedAlways){
if ([CLLocationManager locationServicesEnabled]) {
[yourLocationManager startMonitoringSignificantLocationChanges];
//run your code here
}
}
}

didUpdateLocations always be called several times with different coordinates

So, my app requires to capture location every 15 meter.
in viewDidLoad
NSOperationQueue.mainQueue().addOperationWithBlock {
self.manager = CLLocationManager()
self.manager.delegate = self
self.manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
self.manager.distanceFilter = 15.0
self.manager.headingFilter = 10
self.manager.startUpdatingLocation()
}
First time (i don't move), in
func locationManager(manager:CLLocationManager, didUpdateLocations locations:[AnyObject])
i get (real example):
*.1488241293991
*.5997807433053
second time:
*.1489010891664
*.599624152471
I test on real device with ios7, and swift.
What am I doing wrong?
Thanks!
From CLLocationManager Class Reference:
When requesting high-accuracy location data, the initial event delivered by the location service may not have the accuracy you requested. The location service delivers the initial event as quickly as possible. It then continues to determine the location with the accuracy you requested and delivers additional events, as necessary, when that data is available.
So the first location you retrieve might be inaccurate.

Resources