location manager not able to get speed in ios - ios

I am using CLLocationManager in my iOS app to get user's current location.
I also need to calculate the speed of the user and I got that, it can be calculated using the delegate method "didUpdateLocations".
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
var speed1 = location.speed
speedLabel.text = "\(speed1)"
}
I can get the update of user's current location but the speed value is not correct. Initially the value of speed is -1 then no change in the value even the location is updating correctly.
The initialization of my location manager is below
var locationManager = CLLocationManager()
locationManager.delegate = self;
locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
What's wrong with it.
Advanced thanks..

Usually, when dealing with external events and accessories (location, hardware communications etc.) Apple will give you a -1 when the information you want is not computable or is somehow invalid. You cannot make any assumptions, you simply need to intercept that information (computation error of some kind) and deal with it as if the information was missing.
A negative value indicates an invalid speed
https://developer.apple.com/documentation/corelocation/cllocation/1423798-speed

Related

Current location on maps is not accurate. didUpdateLocations iPhone

I am working on app where I need exact current location. The current location works fine when I am outdoors in iOS app. But when I am inside a building the current location is inaccurate. Its 100 meter away from my exact location.
The setting for location manager are as follows:
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 100
locationManager.allowsBackgroundLocationUpdates = true
And I am calling this function for location updates:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
self.updatedLocation = location
}
The current location works fine on Android devices in buildings.
One thing that I have observed that when I open the Apple Maps and Google Maps app the current
location is the same as I am getting in my app but they have a light blue circle showing that you could be anywhere around that area.
Is there anyway I could improve the current location when I am inside a building?
Any help would be appreciated.
First image is screenshot from iOS where current location is inaccurate and the second image is screenshot from Android device where the location is precise.
I have noticed this problem as well, especially with find my iphone, when i'm in a building the location of my phone is always 50-100 m away i didn't know why.
Currently I'm working on an app that require the exact location, so one thing you can do is check the horizontalAccuracy property on the CLLocation that you're being returned. If this is above a certain threshold then you could throw away the result and wait for a more accurate one. If it is a number of miles out then I would expect the accuracy figure to be quite large. It's most likely using a cell site to determine location rather than GPS, and the margin of error would be much greater.
Here it is an example from my own application:
func requestUserLocation(){
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.distanceFilter = kCLDistanceFilterNone
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
Then on didUpdateLocations function I'll do the check
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
manager.desiredAccuracy = 1000 // 1km accuracy
if locations.last!.horizontalAccuracy > manager.desiredAccuracy {
// This location is inaccurate. Throw it away and wait for the next call to the delegate.
print("i don't want this location")
return;
}
// This is where you do something with your location that's accurate enough.
guard let userLocation = locations.last else {
print("error getting user location")
return
}
}

CLLocationManager with kCLLocationAccuracyBestForNavigation not very accurate

I'm developing an app for runners, so I need high accuracy GPS coordinates. The Android version is ready, now I'm testing with the iOS version. I use the default location provider in Swift in this way:
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.distanceFilter = 1.0
locationManager.requestAlwaysAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
And I process coordinates in this way:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last as CLLocation?
let accuracy = location?.horizontalAccuracy
}
When accuracy is lower than 20, I process the coordinate in 'location' and show the location on a map. This is working fine, but the accuracy is far from good.
kCLLocationAccuracyBestForNavigation should give the best results, but...
Today, I was testing with two devices at the same time: one Android device (price: $150) and a iPhone X (price: $1000). The Android device shows me the location all the time maximum 10 meters from my real location. The iPhone X, with code above, shows me in 70% of the time a location approximately 30-40 meters from my real location. It's not good enough for a running app which gives spoken instructions.
It can't be that the GPS receiver in an iPhone X is of a lower quality than a GPS receiver in a Huawei Y5? The environmental conditions were exactly the same.
Is there a way to receive more accurate locations?
I would suggest to you
locationManager.desiredAccuracy = kCLLocationAccuracyBest
knowing that this option will use the most power from your device as per the documentation in the below link
CLLocationAccuracy
note that the below code is intended to be used for automobile navigation
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation

Keeping track of time in background to handle location updates

I have two constraints in my app, one is user location and the other one is time. Following is the simple location implementation.
func determineMyCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.allowsBackgroundLocationUpdates = true
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestAlwaysAuthorization()
locationManager.distanceFilter = 20
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("*******Location updated*******")
}
With this code, I expect to get triggered every time the user location changes by 20m (in background as well). However, I also need to track the time the user stays in a particular location. Since i need to track the background case, I cannot use timer.
I followed https://www.raywenderlich.com/143128/background-modes-tutorial-getting-started to try background task but as stated in the article, the background time allowed that I got was around 3 min (which is variable). So i believe I cannot go with this.
How can I solve this?
EDIT: I also need to make an api call if the user stays in a location for X minutes. So for that case, it would not be feasible to wait for location updates and calculate the time differences. I could previously solve this by removing the distance filter and continuously checking the location and comparing the time and location. I guess continuous location tracking will get the app rejected, that's why I went for filter. But i am not sure if it will still get rejected or not since iOS will need to track the location for filter as well.
You can use simple Date objects to track the time spent between location updates even if your app is in the background. Simply declare a Date property for your class, update its value from func locationManager(_:, didUpdateLocations:) and compare that to the current time.
// Declare this in a scope that can be accessed from `didUpdateLocations` and where its value won't be released from memory
var lastUpdateTimestamp = Date()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let now = Date()
let timeSinceLastUpdate = now.timeIntervalSince(lastUpdateTimestamp)
print("\(timeSinceLastUpdate) seconds passed since last location update")
lastUpdateTimestamp = now
}
Response to the question edit: making an API call after X minutes have passed in the background without a location update is not possible, since there's no supported background mode for executing arbitrary functions at specific points in time. Getting rid of the distance filter as you explained in your edit could be a valid alternative, however if your app is only using location updates to be able to make the API calls and isn't actually doing anything with those locations, your app might indeed get rejected.
When you receive a new location update, check the time difference from the previous update.. giving you the time that they were at the last location.
Depending on what you are doing with these location updates, this might just be pulling the latest update from some DB, or posting to an API.
An example..
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("*******Location updated*******")
let lastUpdateDate = // get the last datetime you received information
let currentDate = Date()
let minutesDifference = let componentsLeftTime = Calendar.current.dateComponents([.minute], from: lastUpdateDate, to: currentDate)
}

What's the proper way to use startMonitoringSignificantLocationChanges in swift 4?

I try to make a running app, and I want to use the method startMonitoringSignificantLocationChanges, but I'm missing something because is not firing the didUpdateLocations that the documentation says it should.
I have successfully used the startUpdatingLocation method of the CLLocationManager class and I can totally can use it for my interests, but I'd like to understand why my implementation doesn't work.
So, I have this code:
let locationManager = CLLocationManager()
func determineMyCurrentLocation() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
print("authorization Status: \(CLLocationManager.authorizationStatus().rawValue)")
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation() //(1)
//(2)locationManager.startMonitoringSignificantLocationChanges()
locationManager.distanceFilter = 20
//locationManager.start
//locationManager.startUpdatingHeading)(
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { print("yaaay!") }
If you comment (1) and uncomment 2 the func locationManager...etc is never fired.
Also, I have my info.plist with the proper keys set, "Privacy - Location When In Use Usage Description" and "Privacy - Location Always and When In Use Usage Description").
Any idea?
Thanks in advance.
EDITED: My question was imprecise. I put the locationManager property as a class member and I completed the properties in my info.plist
The startMonitoringSignificantLocationChanges requires always authorisation. That also means you should turn on "Background Modes" in Capabilities section and selection "Location Updates". You also require the Privacy - Location Always Usage Description Try these two and you should receive significant location change updates.

Getting really bad accuracy from CLLocationManager

I'm using CLLocationManager to get users location.
I want to get a single location update.
My problem is that I'm getting really bad horizontalAccuracy
location is %# <+xx.xxxxxx,+yy.yyyyyyy> +/- 3881.91m
verticalAccuracy: 65.4401861912846, horizontalAccuracy: 3881.90892434957
Code:
fileprivate lazy var locationManager: CLLocationManager = {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
manager.pausesLocationUpdatesAutomatically = false
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.distanceFilter = kCLDistanceFilterNone
return manager
}()
override init() {
super.init()
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let mostRecentLocation = locations.last else {
return
}
let verticalAccuracy = mostRecentLocation.verticalAccuracy
let horizontalAccuracy = mostRecentLocation.horizontalAccuracy
print("location is %#", mostRecentLocation)
print("verticalAccuracy: \(verticalAccuracy), horizontalAccuracy:\(horizontalAccuracy)")
}
Any suggestions why is this happening?
I'm in a room next to a window so i except to get bad accuracy but not that bad.
Thanks
I'm getting ridiculous results.
I got horizontalAccuracy of 15,000 m.
When i go out doors it works great but in doors should not be as bad as this.
Using Cellular triangulation and wifi should give a lot better results.
after 20 minutes i started to get good results of +- 50 m accuracy in doors.
I suggest you add a condition to make sure that you will just be using a location that have a better accuracy. On my case I use 20 as the desired horizontal accuracy on below example.
Example:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locationManager.stopUpdatingLocation()
if locations.last!.horizontalAccuracy < 20 {
//Only use location that enters here
}
else {
//If the accuracy is not met then start updating location again and if possible increase more the accuracy (use kCLLocationAccuracyBestForNavigation if you really need it). Make sure that you use the desired accuracy and filter properly to avoid draining battery.
locationManager.startUpdatingLocation()
}
}
I always have found that when you first start location updates, the first few readings are quite poor, and then the accuracy improves as the GPS settles down.
What I do is to first check if the timestamp on the location is more than a few seconds old. If it is, I discard it. (Not sure if recent OS versions still send "stale" GPS readings, but the system used to give you a location from the last time the GPS was powered up, sometimes several hours old.)
Once I get current locations, I check the accuracy, and discard any location updates who's accuracy reading is too poor. Only when I get a reading that's good enough do I use it (and stop location updates in your case, since you only want 1 update.)
Bear in mind that it can take 30 seconds (or more) for the GPS to settle down, and in an area with poor GPS signal, you may never get a good enough reading.

Resources