Swift, ClLocationManager slow to fire didEnterRegion - swift

I am trying to implement directions with clLocationManager in a project.
Everything is working ok, but the didEnterrRegion function is very slow to fire.
When testing, I enter the region but only 2-3 minutes after exiting the region I get the callback. Does anyone have any suggestion on how to improve this?
This is the locationManager:
private lazy var locationManager: CLLocationManager = {
let locationManager = CLLocationManager()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.distanceFilter = kCLDistanceFilterNone
handleAuthorizationStatus(locationManager: locationManager)
} else {
//TODO: Handle error
}
return locationManager
}()
This is which regions I am tracking, here I am also drawing each region to easier see when I enter specific region:
private func getRouteSteps(_ mapView: MKMapView, route: MKRoute) {
for monitoredRegion in locationManager.monitoredRegions {
locationManager.stopMonitoring(for: monitoredRegion)
}
let steps = route.steps
self.steps = steps
for i in 0..<steps.count {
let step = steps[i]
let region = CLCircularRegion(center: step.polyline.coordinate, radius: 30, identifier: "\(i)")
let circle = MKCircle(center: region.center, radius: region.radius)
mapView.addOverlay(circle)
locationManager.startMonitoring(for: region)
}
stepCounter += 1
let initialMessage = "Om \(Int(steps[stepCounter].distance)) meter \(steps[stepCounter].instructions.lowercased())"
directionMessage = initialMessage
}
This is the locationManager-function:
extension MapViewModel: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// manager.stopUpdatingLocation()
if directionsViewState != .isShowingRoute {
if let location = locations.last {
self.didUpdateRegion = "Updated region with accuracy: \(location.horizontalAccuracy)"
let center = location.coordinate
setNewRegionForMapView(center: center)
isCenteringUserLocation = true
}
}
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
handleAuthorizationStatus(locationManager: locationManager)
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
self.didEnterregion = "Entered region: \(region.identifier)"
stepCounter += 1
if stepCounter < steps.count {
let message = "Om \(Int(steps[stepCounter].distance)) meter \(steps[stepCounter].instructions.lowercased())"
directionMessage = message
let speechUtterance = AVSpeechUtterance(string: message)
speechSynthesizer.speak(speechUtterance)
} else {
directionMessage = "You have arrived at your destination!"
stepCounter = 0
let speechUtterance = AVSpeechUtterance(string: directionMessage)
speechSynthesizer.speak(speechUtterance)
for monitoredRegion in locationManager.monitoredRegions {
locationManager.stopMonitoring(for: monitoredRegion)
}
}
}
}
I am also calling locationManager.startUpdatingLocations in the init-method.

Suppose that you are using locationManager.requestWhenInUseAuthorization().
So, I have some suggestions for you:
[METHOD 1] Apply background location updating:
Enable background mode to your target
Then add these lines of code:
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
The line allowsBackgroundLocationUpdates = true will allow your app to run in the background to receive new location event (ex: lock screen, use another app,...).
And, pausesLocationUpdatesAutomatically = false will tell the system to not pause location updating, system can pause background location updating to save battery.
Call your locationManager.startUpdatingLocation() to start listening for new location change.
Next [METHOD 2], if above method doesn't work, you can switch to use locationManager.requestAlwaysAuthorization(). Its description is Requests the user’s permission to use location services regardless of whether the app is in use. System can wake your app to run in background to handle new location events. Remember to add the permission description for requestAlwaysAuthorization in Info.plist file.
Next [METHOD 3], try to increase your CLCircularRegion's radius to higher value, ex: 50 meters. Or you can try to increase the distanceFilter to 2 meters, distanceFilter = none isn't a best option.
Finally [METHOD 4], Make your customized region monitoring logic by calculating distance from user's location to region's center whenever we get a new location event, use this one func distance(from location: CLLocation) -> CLLocationDistance. If distance is <= your region's radius, that means user already crossed the boundary.
Hope that you can solve your problem.

Related

Launch Location Updates at specific time in background - Swift (watchOS)

I've developing an indipendent WatchOS app whose aim is identifying when an user leaves a specific area, sending consequentially a notification. In order to do that, the application heavily relies on background location updates.
So far, app is working fine. It fetches position based on distanceFilter property of CLLocationManager. The problem is battery. The approach I followed keep background location updates in execution, even though they're fetched only when a specific distance is "covered".
My idea then was to disable location update when the area is left by the user, and also disable this service during night hours. However, I'm facing serious problem with this type of approach.
My main problem is that disabling location update while in background does not allow me to resume it. I tried doing this with:
A Timer.
scheduleBackgroundRefresh(withPreferredDate:userInfo:scheduledCompletion:) method, calling startLocationUpdates() in the delegate
Nothing seems to work. My question is:
There is a way for resume background location updates if it was previously disabled?
Thank you in advance.
UPDATE n.2: I've tried to execute location updates with WKApplicationRefreshBackgroundTask but it just ignore requestLocation() function (suggested by #RomuloBM)
//In extension delegate handle() function
case let backgroundTask as WKApplicationRefreshBackgroundTask:
// Be sure to complete the background ta
LocMng = LocationManager() // I even tried to create a new element!
LocMng.LocMng.requestLocation()// it is just ignored
backgroundTask.setTaskCompletedWithSnapshot(false)
I call a background task with this function in my LocationManager:
//In didUpdateLocation
if background {
WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: Date(timeIntervalSinceNow: 30), userInfo: nil){ _ in
print("Done")
self.background = false
self.LocMng.stopUpdatingLocation()
}
}
For reference, here is my LocationManager class:
enum ScanningMode {
case Precise
case Normal
}
class LocationManager : NSObject, CLLocationManagerDelegate, UNUserNotificationCenterDelegate, ObservableObject {
let LocMng = CLLocationManager()
let NotMng = UNUserNotificationCenter.current()
var modeOfScanning: ScanningMode!
var region: CLCircularRegion!
var previousLocation: CLLocation!
// variables for position...
override init() {
super.init()
// stuff for my app...
modeOfScanning = .Precise
setupManager()
setupNotification()
startLocalization()
}
private func startLocalization(){
switch modeOfScanning!{
case ScanningMode.Precise:
LocMng.desiredAccuracy = kCLLocationAccuracyBest
LocMng.distanceFilter = 15
case ScanningMode.Normal:
LocMng.desiredAccuracy = kCLLocationAccuracyHundredMeters
LocMng.distanceFilter = 80
}
LocMng.startUpdatingLocation()
}
private func setupManager(){
LocMng.requestAlwaysAuthorization()
LocMng.delegate = self
LocMng.desiredAccuracy = kCLLocationAccuracyBest
}
private func setupNotification(){
NotMng.delegate = self
NotMng.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("NotificationCenter Authorization Granted!")
}
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == CLAuthorizationStatus.authorizedAlways{
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
LocMng.allowsBackgroundLocationUpdates = true
// For the sake of clarity, I will cut out this chunk of code and
// just showing how I execute my action based on the result of location
// This is just an example
actualLocation = locations[length-1]
//find if in a forget
if previousLocation != nil{
if !region.contains(actualLocation!.coordinate) && region.contains(previousLocation!.coordinate){
//Schedule notification
LocMng.stopUpdatingLocation() // <- this does not allow me to resume
}
}
previousLocation = actualLocation
}
}

Calculating total distance traveled swift [duplicate]

How can I calculate the total distance traveled use CoreLocation in Swift
I haven't been able to so far find any resources for how to do this in Swift for iOS 8,
How would you calculate the total distance moved since you began tracking your location?
From what I've read so far, I need to save location of a points, then calculate the distance between current point, and last point, then add that distance to a totalDistance variable
Objective-C is extremely unfamiliar to me, so I haven't been able to work out the swift syntax
Here is what I've worked out so far, not sure if I'm doing it right. Though the distanceFromLocationmethod is returning all 0.0 so obviously something is wrong
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var newLocation: CLLocation = locations[0] as CLLocation
oldLocationArray.append(newLocation)
var totalDistance = CLLocationDistance()
var oldLocation = oldLocationArray.last
var distanceTraveled = newLocation.distanceFromLocation(oldLocation)
totalDistance += distanceTraveled
println(distanceTraveled)
}
update: Xcode 8.3.2 • Swift 3.1
The problem there is because you are always getting the same location over and over again. Try like this:
import UIKit
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
var startLocation: CLLocation!
var lastLocation: CLLocation!
var startDate: Date!
var traveledDistance: Double = 0
override func viewDidLoad() {
super.viewDidLoad()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
locationManager.startMonitoringSignificantLocationChanges()
locationManager.distanceFilter = 10
mapView.showsUserLocation = true
mapView.userTrackingMode = .follow
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if startDate == nil {
startDate = Date()
} else {
print("elapsedTime:", String(format: "%.0fs", Date().timeIntervalSince(startDate)))
}
if startLocation == nil {
startLocation = locations.first
} else if let location = locations.last {
traveledDistance += lastLocation.distance(from: location)
print("Traveled Distance:", traveledDistance)
print("Straight Distance:", startLocation.distance(from: locations.last!))
}
lastLocation = locations.last
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
if (error as? CLError)?.code == .denied {
manager.stopUpdatingLocation()
manager.stopMonitoringSignificantLocationChanges()
}
}
}
Sample Project
If you want to calculate the route distance between two points you need to use MKDirectionsRequest, this will return you one, or many, routes from point A to point B with the step by step instruction:
class func caculateDistance(){
var directionRequest = MKDirectionsRequest()
var sourceCoord = CLLocationCoordinate2D(latitude: -36.7346287, longitude: 174.6991812)
var destinationCoord = CLLocationCoordinate2D(latitude: -36.850587, longitude: 174.7391745)
var mkPlacemarkOrigen = MKPlacemark(coordinate: sourceCoord, addressDictionary: nil)
var mkPlacemarkDestination = MKPlacemark(coordinate: destinationCoord, addressDictionary: nil)
var source:MKMapItem = MKMapItem(placemark: mkPlacemarkOrigen)
var destination:MKMapItem = MKMapItem(placemark: mkPlacemarkDestination)
directionRequest.setSource(source)
directionRequest.setDestination(destination)
var directions = MKDirections(request: directionRequest)
directions.calculateDirectionsWithCompletionHandler {
(response, error) -> Void in
if error != nil { println("Error calculating direction - \(error.localizedDescription)") }
else {
for route in response.routes{
println("Distance = \(route.distance)")
for step in route.steps!{
println(step.instructions)
}
}
}
}
}
This example code will return you this:
Distance
Distance = 16800.0
Step by Step instructions
Start on the route
At the end of the road, turn left onto Bush Road
Turn right onto Albany Expressway
At the roundabout, take the first exit onto Greville Road toward 1, Auckland
At the roundabout, take the third exit to merge onto 1 toward Auckland
Keep left
Take exit 423 onto Shelly Beach Road
Continue onto Shelly Beach Road
At the end of the road, turn right onto Jervois Road
Turn left onto Islington Street
Keep right on Islington Street
Arrive at the destination
The function can be easily modified to receive two locations and return the distance and any other needed information.
I hope that helps you!
Leo Dabus method could be used to calculate the geographical distance between your actual location and start one.
In order to obtain the precise traveled distance, you have to update "traveledDistance" using the difference between the last position and the old one.
This is my implementation:
var startLocation:CLLocation!
var lastLocation: CLLocation!
var traveledDistance:Double = 0
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
if startLocation == nil {
startLocation = locations.first as! CLLocation
} else {
let lastLocation = locations.last as! CLLocation
let distance = startLocation.distanceFromLocation(lastLocation)
startLocation = lastLocation
traveledDistance += distance
}
}

After requestAuthorization on first load the rest of viewDidLoad isn't executed and the next line jumps to the delegate functions (swift 3)

My app seems to work fine on each use except the first one.
I ask for user authorization and I have the appropriate keys in the plist but the rest of viewDidLoad after the lines requesting authorization don't execute. I have attached the breakpoints below and breakpoint 2 isn't hit on the first time the app is used.
I'm pretty sure after authorization is given it just jumps to the func locationManager in the extension.
I could wait till the very end to ask for authorization until everything else is set but not sure if this is the best, or only way, out.
Thanks,
class MapController: UIViewController, GMSMapViewDelegate {
var locationManager = CLLocationManager()
var currentLocation: CLLocation?
#IBOutlet var mapView: GMSMapView!
override func viewDidLoad(){
super.viewDidLoad()
locationManager = CLLocationManager()
--------------------------> breakpoint 1
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
-------------------------> breakpoint 2
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self
guard let lat = locationManager.location?.coordinate.latitude else {return}
guard let lng = locationManager.location?.coordinate.longitude else {return}
mapView.settings.compassButton = true
mapView.settings.myLocationButton = true
mapView.isMyLocationEnabled = true
let camera = GMSCameraPosition.camera(withLatitude: lat, longitude: lng, zoom: 1)
mapView.camera = camera
mapView.delegate = self
getData()
}
extension MapController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location: CLLocation = locations.last else {return}
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: 1)
mapView.animate(to: camera)
}
}
Why do you request authorisation to two different things? If you request and get always authorisation, you don't need to request when in use authorisation, since this is only a subset of always authorisation.
Also, these are both asynchronous functions, so you cannot execute location based code right after them, since if you don't have authorisation yet, the code right after requestAuthorization() will be executed before you actually got authorisation and hence the functions won't be called, since you don't have authorisation yet.
You have to check the authorisation status before calling any location related code, such as locationManager.startUpdatingLocation() and only execute the location related code if the status is authorised. If it is not authorised, you have to implement CLLocationManagerDelegate's locationManager(_:didChangeAuthorization:) function and call the location related call inside that function after checking that the result of the change is an authorised status.

Userlocation keeps updating and won´t let me move freely through my map

Let me explain myself. I have created a Map where I show some locations (pins) and the user location. When I run the App, it opens and zooms in (span), then I want to move through the map, but the App "drags" the view back to the user location (it even zooms in back to normal state). My question is, how can I stop my App from doing that over and over again? because it is kind of annoying. Thanks in advance.
The function I used for this Span process was the following...
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
let span: MKCoordinateSpan = MKCoordinateSpanMake(1.0, 1.0)
let ubicacionUsuario: CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region: MKCoordinateRegion = MKCoordinateRegionMake(ubicacionUsuario, span)
mapView.setRegion(region, animated: true)
self.mapView.showsUserLocation = true
}
Inside of your didUpdateLocations function, you can create a counter. Essentially, what the counter would do is update and refresh the user's location on the map for a specific number of iterations. After that, you can use the .stopUpdatingLocation method of the CLLocationManager. So, for your case, you would have something like below:
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
// Top level class stuff, create CLLocationManager Instance
var manager = CLLocationManager()
// set initial value of counter to 0
var updateCount = 0
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//If the location has updated less than 3 times, continue to update the location
if updateCount < 3 {
let region: MKCoordinateRegion = MKCoordinateRegionMake(ubicacionUsuario, span)
mapView.setRegion(region, animated: true)
updateCount += 1
} else {
// Once the update has occurred a satisfactory number of times, prevent the update from automaitcally occuring again
manager.stopUpdatingLocation()
}
}
}

Calculate total distance swift iOS

So in my current project Im doing a method which calculates the saved emission when driving a moped compared to a average car. The function contains two parts, the method (the calculation) and the tracker function. The main problem is that the tracker function somehow does not seem to track at all.
My main question is, how do I get the tracker function to always track while the app is on?
This is the tracker function
var startLocation:CLLocation!
var lastLocation: CLLocation!
var traveledDistance:Double = 0
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if startLocation == nil {
startLocation = locations.first
} else {
if let lastLocation = locations.last {
let distance = startLocation.distanceFromLocation(lastLocation)
let lastDistance = lastLocation.distanceFromLocation(lastLocation)
traveledDistance += lastDistance
print( "\(startLocation)")
print( "\(lastLocation)")
print("FULL DISTANCE: \(traveledDistance)")
print("STRAIGHT DISTANCE: \(distance)")
var travelDistance = setData("distance")
}
}
lastLocation = locations.last
}
And this is the method
func calculateEmission(numbers: Int...) -> Double{
let recordedDistance = getData("distance")
let dis = recordedDistance
let emissionAve = 0.16
let calculatedEmission : Double = Double(dis) * Double(emissionAve)
print(calculatedEmission, "kg Co2")
return calculatedEmission
}
Make sure you have the following in your info.plist. Then you should get prompted to allow access to the location services.
<key>NSLocationAlwaysUsageDescription</key>
<string>Needs access to access GPS</string>
<key>NSLocationUsageDescription</key>
<string>Needs access to access GPS</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Needs access to access GPS</string>
You should have some thing like this in viewDidLoad.
override func viewDidLoad() {
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
}