Determining if location is invalid /cached/ (eg underground) - ios

I’m coding a project in where I need to know the user’s exact position. (LocationManager)
The problem is that for example underground (metro) due to the weak signal the LocationManager can not determine the user’s location, there for it gives back cached location only..
What I do now is that I check whether the location is too old, if so then wait for a new one..
Okay but there’s a problem.. if user doesn’t move at all then location won’t get updated aswell because it will be only a cached location.. the timestamp check will say its too old..
How can I fix that?

You can check cache locations by timeStamp you can write in LocationManagerDidUpdateLocation
e.g
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let newLocation = locations.first {
let age: TimeInterval = -newLocation.timestamp.timeIntervalSinceNow
if age > 120 {
return
}
// ignore old (cached) updates
if newLocation.horizontalAccuracy < 0 {
return
}
}
}

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

How do I update my location data in Firebase infrequently?

I am using Significant-Change Location service which updates location every 500 meters or more. So, if a user goes on a long drive, the location is updated very frequently and since I am updating my firebase database with the location with every update, that will be a lot of writes. How can I limit the times I update my database for example, update the db every hour or update only when new location updates have stopped or something else?
This is my current code:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let lastLocation = locations.last!
lat = lastLocation.coordinate.latitude
long = lastLocation.coordinate.longitude
db.collection(coll).document(uid).updateData(["loc": GeoPoint(latitude: lat!, longitude: long!)])
}
What I did was say if it has been >= x miles since the last update in the db, then update again, else do nothing

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

how to prevent mock GPS location (spoofing) from fake GPS app in Swift? [duplicate]

This question already has answers here:
iOS detect mock locations
(5 answers)
Closed 4 years ago.
I am making an app that using CLLocationManager, this app will be used to record the attendee of the employee. to validate the attendee of the employees, we will get GPS Coordinate.
as far as I know, in there is an app that usually used to get fake GPS. I want to prevent this mock GPS to be active when the user using my app.
If I am using Android, I can download a fake GPS app. and when let say I use tinder I can fake my location. let say actually I am in Bangkok, but because I use fake GPS app, I can set my tinder location to be in London, not in Bangkok anymore.
So Basically I want to prevent fake location that comes from that fake GPS when the user using my App. To be honest I don't really know whether iOS allow fake location or not
can I get that function in Swift?
here is the class LocationManager I use to get the coordinate
import UIKit
import CoreLocation
class LocationManager: NSObject {
let manager = CLLocationManager()
var didGetLocation: ((Coordinate?) -> Void)?
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestLocation()
}
func getPermission() {
// to ask permission to the user by showing an alert (the alert message is available on info.plist)
if CLLocationManager.authorizationStatus() == .notDetermined {
manager.requestWhenInUseAuthorization()
}
}
}
extension LocationManager : CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
manager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {
didGetLocation?(nil)
return
}
let coordinate = Coordinate(location: location)
if let didGetLocation = didGetLocation {
didGetLocation(coordinate)
}
}
}
private extension Coordinate {
init(location: CLLocation) {
latitude = location.coordinate.latitude
longitude = location.coordinate.longitude
}
}
You can check if the app is jailbroken or not. If it already jailbroken, you can prevent the user to use the app with showing permanent dialog or something else.
If you wanna know how to detect the device is jailbroken or not, you can find it by yourself. There is so many literature that will tell you how.
Cheers :)
Only thing I can tell you and I've been in a similar situation is you need to have a backup method to get the user location. Use an IP location API/service (which is not 100% reliable) and create a logic in your app to compare the data.
PS: THIS IS NOT A SOLUTION TO YOUR PROBLEM it's just an idea you could try to work with. But this would only work best if spoofing the location is happening using different cities/states since IP location is not a high accuracy one. If GPS says you are in San Diego but your IP say you are in San Francisco, then you could block the UI/request until user confirms something.
PS2: in iOS the only way I know a user can spoof it's location is running an app through XCode, using the location feature and then opening your app. (used to do that a lot with pokemon go #notproud :))

Location timestamp accuracy in ios

After analysing location services in iOS 10, found that some inconsistency is in the caching behaviour.
Fetching locations in a periodic time (in my case every 20 secs) returns locations but their timestamps are not in chronologically ordered. This indicates that the caching locations might have issues. So if you are checking accuracy through location-timestamp better to save the previous timestamps also. So that you could decide that the location fetched can be used or not.
Below image is taken from my console log. Here I used the format "Lat Long : latitude_longitude | location_timestamp | Now : current_timestamp"
Yes some time in best accuracy ios take the location from the cache so you need to avoid that location here is the code for accurate locationtion.
Update :
"Because it can take several seconds to return an initial location, the location manager typically delivers the previously cached location data immediately and then delivers more up-to-date location data as it becomes available. Therefore it is always a good idea to check the timestamp of any location object before taking any actions."
Reference :
https://developer.apple.com/reference/corelocation/cllocationmanager
Note: you can vary the accuracy for the device like ipod and ipad
//MARK: Location delgates
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
if locations.count > 0
{
let location = locations[locations.count-1]
let maxAge:TimeInterval = 60;
let requiredAccuracy:CLLocationAccuracy = 100;
let locationIsValid:Bool = Date().timeIntervalSince(location.timestamp) < maxAge && location.horizontalAccuracy <= requiredAccuracy;
if locationIsValid
{
NSLog(",,, location : %#",location);
NSLog("valid locations.....");
}
}
}
The problem behind this is that sometimes the timestamps do not match to the location! E.g. while traveling you suddenly records speeds > 300km/h and best accuracy.
I would sort the locations and only would take the last one if not too old:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let sortedLocations = locations.sorted { (l1, l2) -> Bool in
return l1.timestamp.compare(l2.timestamp) != .orderedDescending
}
if let newestLocation = sortedLocations.last{
if Date().timeIntervalSince(newestLocation.timestamp) < 60{
//TODO: Use the location
}
}
}
Yes like #chirag shah commented we definitely need to do that check. My suggestion is we should know about that the caching technique has been modified. And also it is not enough only checking the timestamp, we have to focus on the failure case. Here is the objective C code
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
CLLocation* location = [locations lastObject];
NSDate* locationTimestamp = location.timestamp;
NSTimeInterval cachedBefore = [locationTimestamp timeIntervalSinceNow];
if (fabs(cachedBefore) < 60.0) {
// Implement your code here
}else{
// Try again or wait for fetching location
}
}

Resources