How to make location more accurate in iOS? - ios

I am building a location based app. Is there anyway to make the location I am getting through CLLocationManager more accurate?
I need to make sure the accuracy is within 50 meters.
Here is my code so far:
func startSignificantChangeUpdates(){
if (locationManager.respondsToSelector(Selector("requestWhenInUseAuthorization"))) {
locationManager.requestWhenInUseAuthorization()
}else {
locationManager.startUpdatingLocation()
}
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
location = locationManager.location
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
if let newLocation = locations.last as? CLLocation {
//Lets not bother if the location is less than 500 meters
let oldLocation = location
location = newLocation
var distance = oldLocation?.distanceFromLocation(newLocation)
if distance > 500 {
NSNotificationCenter.defaultCenter().postNotificationName("location", object: self)
}
if distance > 50 {
NSNotificationCenter.defaultCenter().postNotificationName("imatlocation", object: self)
}
}
}

For accuracy of the returned location you need to check the horizontalAccuracy of the location, that will give you the radius of uncertainty for the location (in meters) you can then decide to act on or reject the location based on how accurate it is.

You can change your desired accuracy to use
kCLLocationAccuracyBest

Related

How to get accurate current location coordinates (latitude/longititude) in IOS Swift

I am using CoreLocation in IOS Swift to get the current coordinates but for some cases, it gives me closest to my desired location while sometimes the coordinates are many meters away from my given address. please check my code which I am using is there any other way to get an accurate current location coordinates.?
My Code is :
import CoreLocation
var locationManager:CLLocationManager!
var userLat = 0.0
var userLong = 0.0
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager = CLLocationManager()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled(){
self.locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:
[CLLocation]) {
guard let userLocation :CLLocation = locations.first else{return}
userLat = userLocation.coordinate.latitude
userLong = userLocation.coordinate.longitude
//current location
print("current location latitude is :%#", userLat)
print("current location longitude is :%#", userLong)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(Error: \(error)")
}
Please help me some time current location is accurate and sometimes it's too meters away. Thanks
Assuming the user of your app has allowed you use precise locations you still need to take into account the fact that the location provided is only as accurate as the hardware can provide. It takes time for the location to get accurate and you may see several calls to didUpdateLocations as the system warms up.
You can use horizontalAccuracy and verticalAccuracy on a CLLocation to make decisions on what to do with a position update.

Is that possible to receive Location even when app is not running in Swift

I am making an app that trace all the trips made by a user.
The app works perfectly when it is in background or in foreground but I am looking for a way to get it worked even if the app is not running.
I followed exactly this answer : Receiving Location even when app is not running in Swift
But this doesn't work as expected.
I have two property in my AppDelegate
var lastLocation:CLLocation?
//And a location manager
var locationManager = CLLocationManager()
In my DidFinishLaunchingWithOptions I configure the CLLocationManager
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
In my applicationWillTerminate :
lastLocation = HomeController.locations.last
createRegion(location: lastLocation)
Here is the rest :
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
UserDefaults.standard.set(UserDefaults.standard.integer(forKey: "nbLocNotrunning") + 1, forKey: "nbLocNotrunning")
//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")
}
}
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()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if UIApplication.shared.applicationState != .inactive {
} 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)
}
}
I wonder where the mistake can come from and why I never enter in the function didExitRegion.
For now, I just increment a variable in UserDefaut "nbLocNotrunning" in order to know how many times I leave a region but this number never increase when I stop my app.

How to check if user's current location is inside a location circle

i want to check if user if inside a vicinity. For example i have specified a radius of 50 meters around current of location of user. Let's say if user is moving, now i want to check if user in inside 50 meter radius or not. Here is my code
override func viewDidLoad() {
super.viewDidLoad()
locationManager.startMonitoringVisits()
locationManager.delegate = self
locationManager.distanceFilter = 1
locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()
}
Here is code for checking distance
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {
return
}
let officeLocation = CLLocationCoordinate2D.init(latitude: 31.471303736482234, longitude: 74.27275174139386)
let circle = MKCircle(center: officeLocation, radius: 50 as CLLocationDistance)
if location.distance(from: officeLocation) > circle.radius {
self.newVisitReceived(des: "YOU ARE OUT OF OFFICE")
}
else{
self.newVisitReceived(des: "YOU ARE IN OFFICE")
}
}
Even if i don't move this code sends notification "YOU ARE OUT".
I would solve this with Geofences...
You have to specify a coordinate center & radius where you want to listen to the user when he goes inside/outside from your geofence.
override func viewDidLoad() {
super.viewDidLoad()
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.allowsBackgroundLocationUpdates = true
locationManager.requestAlwaysAuthorization()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedAlways || status == .authorizedWhenInUse {
// CLLocationCoordinate2D; You have to put the coordinate that you want to listen
let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: 324234, longitude: 23423), radius: 50, identifier: "Ur ID")
region.notifyOnExit = true
region.notifyOnEntry = true
manager.startMonitoring(for: region)
}
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
// User has exited from ur regiom
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
// User has exited from ur region
}
I hope this will be useful
The general problem with location services is that the measurements vary in accuracy, depending on a lot of factors. How would your like your code to behave if the user is standing right on the 50 meter boundary? Your current code would then flip back and forth randomly between 'in office' and 'out of office', if the accuracy is bad.
I think accuracy of a GPS is actually more than 4 meters under the best conditions, so a distanceFilter of 1 might not be appropriate.
I guess you would need some state in your app that tracks when the user was last seen inside the 50 meter radius, and also some grace period before updating that variable again, to avoid 'flickering'.

MKMapItem.forCurrentLocation() returns "Unknown Location"

Inside my ViewController's class viewDidLoad method I have:
override func viewDidLoad() {
super.viewDidLoad()
requestAuthorization()
locationManager.delegate = self
print(MKMapItem.forCurrentLocation())
}
and here is requestAuthorization() function:
private func requestAuthorization(){
if CLLocationManager.authorizationStatus() == .notDetermined{
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
}
else if CLLocationManager.authorizationStatus() == .denied{
//TODO
}
}
the problem is that forCurrentLocation() function never returns actual user location instead returned coordinates of MKMapItem are: (latitude: 0, longitude: 0). Here is the result of the print function.
MKMapItem: 0x60000014e020
isCurrentLocation = 1;
name = "Unknown Location";
What am I doing wrong?
Edit:
The reason I wanted to use MKMapItem.forCurrentLocation() was that I planned to get user coordinates in prepareForSegue. And I thought it would be easier than using didUpdateLocations delegate method. See the code:
if segue.identifier == "categories",
let destinationVC = segue.destination as? CategoriesGroupViewController{
if let cell = sender as? UICollectionViewCell{
let indexPath = categoriesCollectionView.indexPath(for: cell)
destinationVC.groupOfDestinations = destinationsByCategory[indexPath!.row]
// 0 is index for Near Me cell
if indexPath?.row == 0 {
destinationVC.groupOfDestinations = getNearMeDestinations()
}
}
private func getNearMeDestinations() -> GroupOfDestinations{
let userCoordinates = MKMapItem.forCurrentLocation().placemark.coordinate
let nearMeCircularRegion: CLCircularRegion = CLCircularRegion(center: userCoordinates, radius: 10000, identifier: "nearMe")
...
return nearMeDestinations
}
You´re not doing anything wrong. The MKMapItem.forCurrentLocation() Creates and returns a singleton map item object representing the device’s current location.
MKMapItem.forCurrentLocation().isCurrentLocation is a Boolean value indicating whether the map item represents the user’s current location. In your case true.
MKMapItem.forCurrentLocation().name The descriptive name associated with the map item. If this map item represents the user’s current location, the value in property is set to a localized version of “Current Location”.
And that it returns Unknown Location is weird. But it´s enough for you to keep track of the MKMapItem.forCurrentLocation().isCurrentLocation value.
Update:
To get the user locations coordinate do the following:
var location = CLLocation()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let latitude = manager.location?.coordinate.latitude, let longitude = manager.location?.coordinate.longitude else { return }
location = CLLocation(latitude: latitude, longitude: longitude)
}
And then use location which always will be up to date when the user moves.
locationManager.desiredAccuracy = kCLLocationAccuracyBest
Put above line outside if else block, Like
var locationManager: CLLocationManager?
private func requestAuthorization(){
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
if CLLocationManager.authorizationStatus() == .notDetermined{
locationManager.requestWhenInUseAuthorization()
}
else if CLLocationManager.authorizationStatus() == .denied{
//TODO
}
}
There is delegate method for map, Which gives you current location
// CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
Print(location)// This is your current location
}

Why iOS CLLocation shows incorrect values?

I'm trying to calculate distance between 2 coordinates. For this I do:
func setupLocationManager() {
if CLLocationManager.authorizationStatus() == .NotDetermined {
locationManager.requestWhenInUseAuthorization()
}
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let latitude = locations.last?.coordinate.latitude
let longitude = locations.last?.coordinate.longitude
let myLocation = CLLocation(latitude: latitude!, longitude: longitude!)
let targetLocation = CLLocation(latitude: 41.4381022, longitude: 46.604910)
let distance = myLocation.distanceFromLocation(targetLocation)
print(distance)
}
I'm checking in Google Maps there are 13 km distance! But my app shows me 2-3 km!
How can I improve that?
Google maps gives you the route distance between two locations, CLLocation gives you the birds-eye distance between two locations.
From the documentation:
This method measures the distance between the two locations by tracing
a line between them that follows the curvature of the Earth. The
resulting arc is a smooth curve and does not take into account
specific altitude changes between the two locations.
Here is an example based on a working GPS app.
import CoreLocation
public class SwiftLocation: NSObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
private var latestCoord: CLLocationCoordinate2D
init(ignore:String) {
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.startUpdatingLocation()
latestCoord = locationManager.location!.coordinate
super.init()
locationManager.delegate = self
}
private func locationManager(manager: CLLocationManager, didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation) {
latestCoord = manager.location!.coordinate
}
public func getLatest() -> CLLocationCoordinate2D {
return latestCoord
}
}

Resources