Calculate total distance swift iOS - swift

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()
}
}

Related

Swift, ClLocationManager slow to fire didEnterRegion

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.

Swift LocationManager - Completion Handler

I'm trying to use a Location Protocol to enable the mocking of CLLocationManager & CLLocationManager Delegate functions. I've implemented a completion handler (via a web explainer) to be able to return a location via a delegate however the competition never returns (the delegate does return the correct value).
When writing this post the code doesn't seem to make sense, however, I'm not sure how to solve it in this kind of structure.
Call:
func test_getCurrentLocation_returnsExpectedLocation() {
var locationManagerMock = LocationManagerMock()
locationManagerMock.locationToReturn = { return CLLocation(latitude: 10.0, longitude: 10.0)}
let sut = LocationService(locationManager: locationManagerMock)
let expectedLocation = CLLocation(latitude: 10.0, longitude: 10.0)
let completionExpectation = expectation(description: "completion expectation")
sut.getCurrentLocation { (location) in
XCTAssertEqual(location.coordinate.latitude, expectedLocation.coordinate.latitude)
XCTAssertEqual(location.coordinate.longitude, expectedLocation.coordinate.longitude)
completionExpectation.fulfill()
}
wait(for: [completionExpectation], timeout: 1)
}
Location Manager with completion
func getCurrentLocation(completion: #escaping (CLLocation) -> Void) {
self.locationManager.requestLocation()
self.currentLocationCallback = { (location) in
completion(location)
}
}
Delegate write to class var
func locationManager(_ manager: LocationManagerInterface, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
self.currentLocationCallback?(location)
print("Content from Delegate")
print(location)
self.currentLocationCallback = nil
}

CLLocationManager requestLocation not calling didUpdateLocations

I have a simple CLLocationManager implementation that works in one project but not in my new project.
The code is almost identical but I cannot get the .didUpdateLocations function to call. My code is below. Any ideas why I cannot get the update to work? I'm at a loss, I've build many apps using location services and never seen this situation.
Also I have the three settings in the PLIST set correctly for Privacy-Location Always etc.
There are no errors given, it simply doesn't call .didUpdateLocations
Weather Class
class DarkSkyWeatherController: UIViewController, CLLocationManagerDelegate {
var weatherGetterDelegate: DarkSkyWeatherControllerDelegate?
var locationManager = CLLocationManager()
var lat = String()
var long = String()
func getLocation() {
// Ask for Authorisation from the User.
locationManager.requestAlwaysAuthorization()
// For use in foreground
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
locationManager.startUpdatingLocation()
}
locationManager.delegate = self
locationManager.requestLocation()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else {return}
print("locations = \(locValue.latitude) \(locValue.longitude)")
lat = String(locValue.latitude)
long = String(locValue.longitude)
getDarkSkyWeather { (fetchedInfo) in
if let myFetchedInfo = fetchedInfo {
self.weatherGetterDelegate?.getMyWeather(weather: myFetchedInfo)
}
}
}
ViewDidLoad in main window
let weather = DarkSkyWeatherController()
weather.weatherGetterDelegate = self
weather.getLocation()
Thanks for looking at this.
Without seeing your full main window code, I bet that the problem is with the scope and lifecycle of your controller:
override func viewDidLoad() {
let weather = DarkSkyWeatherController()
weather.weatherGetterDelegate = self
weather.getLocation()
// Function exits. The weather constant dies off.
// This is why you don't get callbacks.
}
Do the following, instead.
let weather = DarkSkyWeatherController()
override func viewDidLoad() {
weather.weatherGetterDelegate = self
weather.getLocation()
// Function exits, but the weather constant lives on as a field of your main ViewController. You'll get your callbacks now.
}

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

swift - How to delay method call until variables value is stored regarding updating current location

I would like to call a method but it requires coordinates that have not been stored in variables yet.
So far I have:
1) acquired current location
2) Think I store them?
What I want to do:
1) Call method After these variables are stored so the program can run
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var clLatitude : CLLocationDegrees!
var clLongitude: CLLocationDegrees!
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location:CLLocationCoordinate2D = manager.location!.coordinate
//Test printing coordinates to screen
print("locations = \(location.self.clLatitude) \(location.self.clLongitude)")
//place where I think I store the variables?
self.clLatitude = location.latitude
self.clLongitude = location.longitude
}
func methodToBeCalled(){
//to be called after variables are stored.
}
I believe I have covered everything regarding my problem
i think you just need to call the method at the end of locationManager(manager: didUpdateLocations:)
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location:CLLocationCoordinate2D = manager.location!.coordinate
//Test printing coordinates to screen
print("locations = \(location.self.clLatitude) \(location.self.clLongitude)")
//place where I think I store the variables?
self.clLatitude = location.latitude
self.clLongitude = location.longitude
// call method
methodToBeCalled()
}
func methodToBeCalled(){
//to be called after variables are stored.
}