Current location span changes to random number on map kit view - ios

i have a map kit view and when the app loads, it gets the users current location and tracks it. It then runs a function to focus on the users location which I've also linked to a button so the user can navigate around and then focus back to their location span.
In this function i set the span to 0.01 but when i print the span value its different. When the app runs and focuses for the first time, the latitude span is printing at 0.00745918279325508
Then when i hit the focus button which runs the exact same function, the lat span is printing at 0.0102124663369167 and its noticeably zoomed out
Why is it doing this when I've already set it to 0.01? here is my code:
#IBAction func focusLocation(sender: UIButton)
{
snapLocation()
}
var locationManager: CLLocationManager!
override func viewDidLoad()
{
super.viewDidLoad()
if (CLLocationManager.locationServicesEnabled())
{
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
snapLocation()
}
}
func snapLocation()
{
let currentLocation = locationManager.location?.coordinate
let latitude:CLLocationDegrees = (currentLocation?.latitude)!
let longitude:CLLocationDegrees = (currentLocation?.longitude)!
let latDelta:CLLocationDegrees = 0.01
let lonDelta:CLLocationDegrees = 0.01
let span = MKCoordinateSpanMake(latDelta, lonDelta)
let location = CLLocationCoordinate2DMake(latitude, longitude)
let region = MKCoordinateRegionMake(location, span)
mapkitView.setRegion(region, animated: true)
}

According to Apple docs:
When setting a new region, the map may adjust the value in the region
parameter so that it fits the visible area of the map precisely. This
is normal and is done to ensure that the value in the region property
always reflects the visible portion of the map. However, it does mean
that if you get the value of that property right after calling this
method, the returned value may not match the value you set.
So, when you apply span, it creates the region which is best fit for your request. It will not be exactly same as what you have requested.

Related

Plotting a Specific Location on map view with latitude and longitude

I want to plot a specific point on the map having the lat and lon from an api.
Program flow:
Get LAT & LON from api (done)
Ping api again via timer after every 5 seconds to get the latest location (done)
Plot location with retrieved LAT & LON on map
The issue is every code on the net has to do with 2 points, so user loc and destination loc. I cant seem to get it to work without user loc. I have however coded this to plot the location. However, with this when I touch the map, the map zooms out. Another issue is when I get another point the previous one also remains on the screen. (for testing purpose I hard coded the lat and lon but when I connect the api code that refreshes, prior points remain and the map code is the same as this. The lat and lon are passed via func parameters in createAnnotation().)
My code:
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self // or connect in storyboard
createAnnotation()
}
func createAnnotation(){
let annotations = MKPointAnnotation()
annotations.coordinate = CLLocationCoordinate2D(latitude: 41.87369, longitude: -87.813293)
mapView.addAnnotation(annotations)
}}
How Do I plot the coordinates properly? and then delete the prior and show the new one?.
For the "previous one also remains on the screen" problem: don't keep making a new annotation and calling addAnnotation if you don't want to keep adding new annotations. Instead, keep hold of the annotation that you add, and move it later using its coordinate property. Something like this maybe:
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var annotationForThing: MKPointAnnotation?
var coordinateOfThing: CLLocationCoordinate2D? {
didSet {
guard let newCoord = coordinateOfThing else {
if let existing = annotationForThing {
mapView.removeAnnotation(existing)
}
return
}
if let existing = annotationForThing {
existing.coordinate = coordinateOfThing
}
else {
let newAnnotation = MKPointAnnotation()
newAnnotation = coordinateOfThing
mapView.addAnnotation(newAnnotation)
annotationForThing = newAnnotation
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self // or connect in storyboard
}

How to I take my current starting location and measure the distance after/during a run, using Swift 3

Currently I have the following, its really basic but whenever I run the application it has this random initial jump, so it will start at 0 then jump to a random distance then count up normally, I've looked at a bunch of tutorial but it doesn't seem to solve the issue.
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var MyMap: MKMapView!
#IBOutlet weak var distanceLabel: UILabel!
let mapManager = CLLocationManager()
var startLocation: CLLocation!
override func viewDidLoad() {
super.viewDidLoad()
mapManager.delegate = self
mapManager.desiredAccuracy = kCLLocationAccuracyBest
mapManager.requestWhenInUseAuthorization()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func startButton(_ sender: Any) {
mapManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
let span: MKCoordinateSpan = MKCoordinateSpanMake( 0.01, 0.01)
let myLocation : CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region: MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
MyMap.setRegion(region, animated: true)
self.MyMap.showsUserLocation = true
if (startLocation == nil) {
startLocation = locations.first
}
else {
let newLocation = location.distance(from: startLocation)
distanceLabel.text = String(newLocation)
}
}
}
The location manager does a couple of rather odd things that you have to allow for.
First, it caches the last location it recorded, and when you first ask for location updates, it may give you a "stale" location (a reading with a very old timestamp.) I haven't looked at the data you get for several OS releases, so I don't know if it still does this. If it does, the way to fix that is to check the date on the location updates you get and reject any that are more than a couple of seconds old.
Second, the first readings you get may be wildly inaccurate. It takes the GPS a while to settle down. You can deal with This problem by checking the horizontal accuracy reading. This is expressed in meters, and smaller is better. It's actually a margin of error. (The name accuracy is a bad choice of words if you ask me.) So you might decide to reject readings with a horizontal accuracy > 100 meters, or 50 meters, or something like that.
If you do those 2 things then you should avoid a huge jump at the beginning. However, you may also find that when you go into areas with poor GPS signal, your horizontal accuracy drops below your threshold and you stop getting updates. You might need to create logic that handles that case too.

Current location in not working in Google Map

I have integrated google map in swift 3, when map screen appear than current location in not showing, i have added two keys in .plist file and also set CLLocationManager delegate and requestAlwaysAuthorization
class MapViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate {
#IBOutlet var mapView: GMSMapView!
var marker: GMSMarker?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "MapVC"
self.doSetupUI()
self.searchLocation()
}
override func viewWillAppear(_ animated: Bool) {
let locationManager : CLLocationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
}
func doGoogleMapSetup(lat : Double , lng : Double) {
let camera = GMSCameraPosition.camera(withLatitude: lat, longitude:lng, zoom:16)
let mapView = GMSMapView.map(withFrame: .zero, camera:camera)
mapView.isMyLocationEnabled = true
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: lat, longitude: lng)
marker.snippet = ""
marker.appearAnimation = kGMSMarkerAnimationPop
marker.map = mapView
let arrPoints : NSMutableArray = NSMutableArray()
arrPoints.add(UserDefaults.standard.object(forKey: "addressPoints"))
for i in 0..<arrPoints.count {
let path : String = (arrPoints.object(at: i)as! NSMutableArray).object(at: 0) as! String
let route : GMSPath = GMSPath.init(fromEncodedPath: path)!
let polyLine : GMSPolyline = GMSPolyline.init(path: route)
polyLine.strokeWidth = 2.0
polyLine.strokeColor = UIColor.red
polyLine.map = mapView
}
}
For showing current location we don't need any location manager in case of GoogleMaps. All we need is to add one of the keys or both in the .plist. So make sure the key is there. I have used NSLocationWhenInUseUsageDescription key.
<key>NSLocationWhenInUseUsageDescription</key>
<string>Allow location</string>
Also make sure that you have called GMSServices provideAPIKey method and replaced with the API_KEY you generated in google developer console. Also all the relevant Google APIs as per requirement should be enabled.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
GMSServices.provideAPIKey("YOUR_API_KEY")
return true
}
So, I am assuming you have done all the settings and things right in google developer console.
By just writing the below line in your controller where you have made the GoogleMap can show the location allow/disallow prompt and take the permission of the user.
mapView.isMyLocationEnabled = true
However this will not animate your map to your current location. But you can manually drag the map to check the current location and you will see a blue dot at your current location.
But now we also want to animate to the current location whenever we load that ViewController. Now the need for CLLocationManager arrives. So that in its didUpdateLocation delegate, we can fetch the current location and can just animate the graph to the current location.
So here is my complete controller.
import UIKit
import GoogleMaps
class ViewController: UIViewController,GMSMapViewDelegate,CLLocationManagerDelegate {
#IBOutlet weak var mapView: GMSMapView!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
mapView.isMyLocationEnabled = true
mapView.delegate = self
//Location Manager code to fetch current location
self.locationManager.delegate = self
self.locationManager.startUpdatingLocation()
}
//Location Manager delegates
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
let camera = GMSCameraPosition.camera(withLatitude: (location?.coordinate.latitude)!, longitude:(location?.coordinate.longitude)!, zoom:14)
mapView.animate(to: camera)
//Finally stop updating location otherwise it will come again and again in this delegate
self.locationManager.stopUpdatingLocation()
}
}
Another way of doing is not using the didUpdateLocation and not using the location manager is just by using the GMSMapViewDelegate delegate method mapViewDidFinishTileRendering
func mapViewDidFinishTileRendering(_ mapView: GMSMapView) {
let location = mapView.myLocation
let camera = GMSCameraPosition.camera(withLatitude: (location?.coordinate.latitude)!, longitude:(location?.coordinate.longitude)!, zoom:14)
mapView.animate(to: camera)
}
It will be called everytime the map rendering is finished.
But this comes with a limitation, it will always bring you to the current location whenever you drag/pinch/zoom map as the rendering finish everytime you play with map. So, you can just implement some kind of bool variable logic here.
You can get your location by using
let yourCurrentLocation = mapView.myLocation
Make sure to do this on a device rather than simulator. If you are using simulator, you have to choose some custom location and then only you will be able to see the blue dot.
I already gave this type of answer. Check this Link. But that was in Swift 2.x. The one which I posted in this answer is in Swift 3.x
This is a bit detailed, so I'd like to leave it in a full answer. This is the most common reason I have encountered for a nil location, since figuring out the basics, a few years ago. So, you call CLLocationManager.startLocating(), in your viewDidLoad. Then you call the method that sets up your map. Sometimes this works, and sometimes it doesn't, because of a race condition caused by the amount of time it takes the CLLocationManager to set up permissions, on the one hand, and access the user's location, in another part of the code. Let's look at an order of events, where it doesn't work:
1) you call requestAlwaysAuthroization and startLocating
2) User permissions setup is triggered on one thread
3) In your ViewController, you request the user's location, to set up your map
4) It comes back nil
5) NOW, step 2 finishes, and the app has access to the user's location, but it's too late
The core problem, is that the process that starts with requesting permissions and location, takes more than a few milliseconds. And if your view is already set up, it takes few milliseconds for it to go through the methods in your viewDidLoad. By the time you have the location you need, you've already requested it. This has caused me too many crashes, in my location-based apps.
My workaround, has been to craft a singleton CLLocationManager, make my starting view a delegate, and requestAlwaysAuthorization and startLocating, in that view. That way, when I get to the view that needs the location, the app has already started locating, and the locationManager.location is not nil.
This is an approach that will obviously not work for every app. If you need clarification, let me know, and if you need code, as well. I have a few public iO git repos, with projects where I have encountered and fixed this problem.

mapView in ios 9 simulator is blank

Facing an issue using xcode v7.2.1 and the ios simulator v9.2 .
The problem i'm facing is that the map appears blank when i run the application.
The grid lines appear for a second and then it's just blank. I've tried searching for a solution but haven't found any that have worked.
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet var map: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
//maps integration
let longitude : CLLocationDegrees = 17.486374
let latitude : CLLocationDegrees = 78.543345
let longDelta : CLLocationDegrees = 0.1
let latDelta : CLLocationDegrees = 0.1
let location : CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
let span : MKCoordinateSpan = MKCoordinateSpanMake(latDelta, longDelta)
let region : MKCoordinateRegion = MKCoordinateRegionMake(location, span)
map.setRegion(region, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
it's just a picture of the simulator once the app is run.
This is what i meant when i said blank incase it wasn't clear.
Thanks in advance !
I think it's possible you may have confused your latitude and longitude values. The lat/long you have now takes you to Svalbard and Jan Mayen (a couple of remote islands in the middle of nowhere, north of Norway), but if you switch the lat and long you get Hyderabad in India.
Just Print the Value.
it might be 0,0 and your location would be on the sea.
please make sure that your location have valid location. longitude and latitude.
This case always happen when you trying to update the map on ViewDidload instead of view Didappear.

Current Location not showing in iOS Simulator

I'm trying to show the user's current location in an MKMapView, mapView. I ask for permission and I believe all my bases are covered but for some reason the current location does not show up on the map.
Below is the code. Do not mind the segment controls, I just use these to change the map type.
import UIKit
import MapKit
let annotationId = "annotationID";
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var segControls: UISegmentedControl!
let locationManager = CLLocationManager();
var zoomToUserLocation = false;
// Switch statements for the seg controls.
#IBAction func mapType(sender: UISegmentedControl) {
switch segControls.selectedSegmentIndex {
case 0:
mapView.mapType = MKMapType.Standard
case 1:
mapView.mapType = MKMapType.Satellite
case 2:
mapView.mapType = MKMapType.Hybrid
default:
mapView.mapType = MKMapType.Standard
}
}
override func viewDidLoad() {
super.viewDidLoad()
// We are setting the delegate equal to self here to be able to use the indentifier that deques the annotations.
locationManager.delegate = self
// Status of user is variable that speaks directly to (in this case) authorization status
let status = CLLocationManager.authorizationStatus()
if status == CLAuthorizationStatus.NotDetermined {
locationManager.requestWhenInUseAuthorization()
} else if status != .Denied {
locationManager.startUpdatingLocation()
}
// let annotation = MKPointAnnotation();
}
// If the status changes to authorize when in use or always authorize
// then start updating the location, if not don't do anything.
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == CLAuthorizationStatus.AuthorizedWhenInUse || status == CLAuthorizationStatus.AuthorizedAlways {
locationManager.startUpdatingLocation()
}
}
// If the location failed when trying to get users location execute this function
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
println("Error: \(error)")
}
}
extension ViewController: MKMapViewDelegate {
func mapView(mapView: MKMapView!, didUpdateUserLocation userLocation: MKUserLocation!) {
let coordinate = userLocation.coordinate; // this sets the var coordinates to the location of the user.
println("User Location = (\(coordinate.latitude), \(coordinate.longitude))");
if zoomToUserLocation == true {
let locationOfDevice: CLLocation = userLocation.location // this determines the location of the device using the users location
let deviceCoordinate: CLLocationCoordinate2D = locationOfDevice.coordinate // determines the coordinates of the device using the location device variabel which has in it the user location.
let span = MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1) // this determines the span in which its determined that 1 degree is 69 miles.
let region = MKCoordinateRegion(center: deviceCoordinate, span: span) // set the center equal to the device coordinates and the span equal to the span variable we have created this will give you the region.
mapView.setRegion(region, animated: true);
}
}
}
You need to set the MKMapView's showsUserLocation property to true. It's a boolean property that will display the current location (that pulsing blue dot you refer to - which, can be tinted and is not always blue).
Here's how Apple sums it up in their documentation
This property does not indicate whether the user’s position is actually visible on the map, only whether the map view should try to display it. Setting this property to true causes the map view to use the Core Location framework to find the current location and try to display it on the map. As long as this property is true, the map view continues to track the user’s location and update it periodically. The default value of this property is false.
Showing the user’s location does not guarantee that the location is visible on the map. The user might have scrolled the map to a different point, causing the current location to be offscreen. To determine whether the user’s current location is currently displayed on the map, use the userLocationVisible property.
So in your viewDidLoad function, you should set this property:
mapView.showsUserLocation = true
The zoomToUserLocation property does not control the current location dot.
Sometimes you need to set a custom location first to get the simulator to update and actually set a location (no idea why). What I do is go to Debug->Location->Apple just to check that the map location stuff actually works. Then later i set a custom location with coordinates which i find from google maps or similar.

Resources