Why userLocation returns (-180.0,-180.0) coordinates on Mapbox? - ios

I use Mapbox with Swift 4 and I have a problem when I want to display the user location. I don't understand why the user location is not set as it should be.
I would get the user location coordinates in the viewDidLoad() method. To do so, I have set MGLMapViewDelegate and CLLocationManagerDelegate in my ViewController declaration. Then, in my viewDidLoad() I have:
// Mapview configuration
let mapView = MGLMapView(frame: self.mapView.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.showsUserLocation = true
mapView.setUserTrackingMode(.follow, animated: true)
mapView.delegate = self
self.mapView.addSubview(mapView)
// User location
print("User location:")
print(mapView.userLocation!.coordinate)
But I get this:
CLLocationCoordinate2D(latitude: -180.0, longitude: -180.0)
I think it is because the location is not set when the view loads, but I need to get values in viewDidLoad().
What should I do, and why the line mapView.userLocation!.coordinate doesn't work?
EDIT
In fact, I want to use MapboxDirections to display on the map a line between the user location and a fixed point. To do it, I use this code (see the first comment):
let waypoints = [
// HERE I would use the user location coordinates for my first Waypoint
Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.9131752, longitude: -77.0324047), name: "Mapbox"),
Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), name: "White House"),
]
let options = RouteOptions(waypoints: waypoints, profileIdentifier: .automobileAvoidingTraffic)
options.includesSteps = true
_ = directions.calculate(options) { (waypoints, routes, error) in
guard error == nil else {
print("Error calculating directions: \(error!)")
return
}
if let route = routes?.first, let leg = route.legs.first {
print("Route via \(leg):")
let distanceFormatter = LengthFormatter()
let formattedDistance = distanceFormatter.string(fromMeters: route.distance)
let travelTimeFormatter = DateComponentsFormatter()
travelTimeFormatter.unitsStyle = .short
let formattedTravelTime = travelTimeFormatter.string(from: route.expectedTravelTime)
print("Distance: \(formattedDistance); ETA: \(formattedTravelTime!)")
if route.coordinateCount > 0 {
// Convert the route’s coordinates into a polyline.
var routeCoordinates = route.coordinates!
let routeLine = MGLPolyline(coordinates: &routeCoordinates, count: route.coordinateCount)
// Add the polyline to the map and fit the viewport to the polyline.
mapView.addAnnotation(routeLine)
mapView.setVisibleCoordinates(&routeCoordinates, count: route.coordinateCount, edgePadding: .zero, animated: true)
}
}
}

Larme is correct: the user's location typically isn't available yet in -viewDidLoad. Use the -mapView:didUpdateUserLocation: delegate method to be notified when the user's location becomes available and when it updates.
If you need the user’s location before a map is shown, consider running your own CLLocationManager.
-180, -180 is the kCLLocationCoordinate2DInvalid constant from Core Location. You should typically check if CLLocationCoordinate2DIsValid() before trying to display CLLocationCoordinate2D on a map.

Sergey Kargopolov has a great example of how to obtain the user location using CLLocationManager and CLLocationManagerDelegate. Here is his code:
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager:CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineMyCurrentLocation()
}
func determineMyCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
//locationManager.startUpdatingHeading()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation:CLLocation = locations[0] as CLLocation
// Call stopUpdatingLocation() to stop listening for location updates,
// other wise this function will be called every time when user location changes.
// manager.stopUpdatingLocation()
print("user latitude = \(userLocation.coordinate.latitude)")
print("user longitude = \(userLocation.coordinate.longitude)")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
{
print("Error \(error)")
}
}

Related

Drawing MKPolyline Which Follows User's Current Location

I am trying to draw a path behind the user as they move, tracking their path (like Strava or FitBit apps do when a user starts a workout). So far, the map centres on the user's location but does not start drawing when the user moves. I have tried to implement this with renderForOverlay, but it fails to do so when tested. The code is as follows:
ViewController.swift
import UIKit
import MapKit
import CoreLocation
class StartWorkoutViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapsView: MKMapView!
#IBOutlet weak var startButton: UIButton!
var locationManager: CLLocationManager!
var allLocations: [CLLocation] = []
#IBAction func startButton(_ sender: Any) {
// Start the workout
}
override func viewDidLoad() {
super.viewDidLoad()
// Request user's current location
locationManager = CLLocationManager()
locationManager?.requestAlwaysAuthorization()
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
locationManager?.startUpdatingLocation()
locationManager?.startUpdatingHeading()
locationManager?.delegate = self
mapsView?.showsUserLocation = true
mapsView?.mapType = MKMapType(rawValue: 0)!
mapsView?.userTrackingMode = .follow
mapsView?.delegate = self
let noLocation = CLLocationCoordinate2D()
let viewRegion = MKCoordinateRegion(center: noLocation, latitudinalMeters: 100, longitudinalMeters: 100)
mapsView?.setRegion(viewRegion, animated: true)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Add location to the array and prepare to draw a line between last location and current location
print("Location Updated")
allLocations.append(locations[0])
let previousLocation = allLocations[allLocations.count - 1]
let newLocation = locations[0]
let previousCoordinates = previousLocation.coordinate
let newCoordinates = newLocation.coordinate
var area = [previousCoordinates, newCoordinates]
let polyline = MKPolyline(coordinates: &area, count: area.count)
mapsView.addOverlay(polyline)
}
// DOES NOT WORK
func mapView(_ mapsView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKPolyline {
let polylineRenderer = MKPolylineRenderer(overlay: overlay)
polylineRenderer.strokeColor = UIColor.red
polylineRenderer.lineWidth = 4
return polylineRenderer
} else {
return MKPolylineRenderer()
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// If the authorisation for the user's location has changed, ask again
if status != .authorizedAlways {
locationManager = CLLocationManager()
locationManager?.requestAlwaysAuthorization()
}
}
}
Thank you!
The problem is that you’re grabbing a location, adding it to the array, and then creating a polyline from the last location in the array, allLocations[allLocations.count - 1], (which is now the current location) to the current location (i.e. to itself).
So, grab the last item, previousCoordinate, from the array before you add the new location to it:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let currentLocation = locations.first(where: { $0.horizontalAccuracy >= 0 }) else {
return
}
let previousCoordinate = allLocations.last?.coordinate
allLocations.append(currentLocation)
if previousCoordinate == nil { return }
var area = [previousCoordinate!, currentLocation.coordinate]
let polyline = MKPolyline(coordinates: &area, count: area.count)
mapsView.addOverlay(polyline)
}
I'd also suggest, as you see above, checking for the horizontal accuracy of the location update, to make sure it’s non-negative.
Anyway, that yields:
A few other observations:
I'd suggest retiring the noLocation pattern in viewDidLoad. My above pattern doesn't require that dummy value in the array.
Another issue is that in didChangeAuthorization, you are instantiating a new CLLocationManager and not setting its properties. You are therefore losing the configuration of the original CLLocationManager in viewDidLoad. There’s no need to instantiate another one, but if you do, remember to configure it properly.

Not able to show annotation pin on MKMapview Swift

I am trying to show annotation pin for current location. For that I have written following code
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
mapView.layer.cornerRadius = 8.0
imageView.layer.cornerRadius = 8.0
//mapview settings
mapView.mapType = MKMapType.standard
mapView.isZoomEnabled = true
mapView.isZoomEnabled = true
// Or, if needed, we can position map in the center of the view
mapView.center = view.center
mapView.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
determineCurrentLocation()
}
func determineCurrentLocation()
{
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
//locationManager.startUpdatingHeading()
locationManager.startUpdatingLocation()
}
}
//Mapview Delegate methods
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation:CLLocation = locations[0] as CLLocation
// Call stopUpdatingLocation() to stop listening for location updates,
// other wise this function will be called every time when user location changes.
//manager.stopUpdatingLocation()
let center = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
mapView.setRegion(region, animated: true)
// Drop a pin at user's Current Location
let myAnnotation: MKPointAnnotation = MKPointAnnotation()
myAnnotation.coordinate = CLLocationCoordinate2DMake(userLocation.coordinate.latitude, userLocation.coordinate.longitude);
myAnnotation.title = "Current location"
mapView.addAnnotation(myAnnotation)
}
func locationManager(manager: CLLocationManager, didFailWithError error: Error)
{
print("Error \(error)")
}
But, its nothing showing in mapview. Any suggestions?
You need to add NSLocationAlwaysUsageDescription key in Info.plist with a message to be displayed in the prompt.

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
}

iOS & Swift: display distance from current location?

I have scoured the interwebs and stackoverflow, and I can't find a solution to my problem.
I am attempting to:
Get a user's current location (lat & long)
Calculate the distance between a user's current location and another location (lat & long) that I set internally
Return the distance in a list view
So far, I can accomplish this if I manually set my current location, but I need to to update.
I have had success returning my current location (I set it as Apple headquarters in the Simulator) in the log, but no success in the actual app or simulator.
Here's what I have:
import UIKit
import CoreLocation
import MapKit
class ViewController: UITableViewController, CLLocationManagerDelegate, MKMapViewDelegate {
override func prefersStatusBarHidden() -> Bool {
return true
}
var shops = [coffeeShop]()
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Ask for Authorisation from the User.
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
loadShops()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
print("locations = \(locValue.latitude) \(locValue.longitude)")
}
func loadShops() {
let currentLocation = CLLocation()
let currentLat = currentLocation.coordinate.latitude
let currentLong = currentLocation.coordinate.longitude
var myLocation = CLLocation(latitude: currentLat, longitude: currentLong)
let shopLocation1 = CLLocation(latitude: 39.7886939, longitude: -86.1547275)
let distance1 = myLocation.distanceFromLocation(shopLocation1) / 1000
let shop1 = coffeeShop(location: distance1)!
}
In addition, I have everything set in the info.plist and all of that good stuff.
HOW DO I MAKE THIS WORK!? * weeps softly *
Thanks in advance for all of your help!
I was able to use the following code to achieve what I needed. Sometimes you just gotta put it out there in the universe for the universe to respond on its own. Thanks everyone for the help!
let currentLat = self.locationManager.location!.coordinate.latitude
let currentLong = self.locationManager.location!.coordinate.longitude

Current Location in Google Maps with swift

I'm trying to display the user's current location on a google map but in the case below, the map doesn't even get displayed. What should I change to fix this?
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//user location stuff
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Error" + error.description)
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation = locations.last
let center = CLLocationCoordinate2D(latitude: userLocation!.coordinate.latitude, longitude: userLocation!.coordinate.longitude)
let camera = GMSCameraPosition.cameraWithLatitude(userLocation!.coordinate.latitude,
longitude: userLocation!.coordinate.longitude, zoom: 8)
let mapView = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
mapView.myLocationEnabled = true
self.view = mapView
let marker = GMSMarker()
marker.position = center
marker.title = "Current Location"
marker.snippet = "XXX"
marker.map = mapView
locationManager.stopUpdatingLocation()
}
You can try this bellow code its working fine
import UIKit
import GoogleMaps
import GooglePlaces
class SearchMapsViewController: UIViewController,
UINavigationBarDelegate, GMSAutocompleteFetcherDelegate,
LocateOnTheMap, UISearchBarDelegate, CLLocationManagerDelegate
{
#IBOutlet var googleMapsContainerView: UIView!
var searchResultController: SearchResultsController!
var resultsArray = [String]()
var googleMapsView:GMSMapView!
var gmsFetcher: GMSAutocompleteFetcher!
var locationManager = CLLocationManager()
override func viewDidAppear(animated: Bool) {
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
self.googleMapsView = GMSMapView (frame: self.googleMapsContainerView.frame)
self.googleMapsView.settings.compassButton = true
self.googleMapsView.myLocationEnabled = true
self.googleMapsView.settings.myLocationButton = true
self.view.addSubview(self.googleMapsView)
searchResultController = SearchResultsController()
searchResultController.delegate = self
gmsFetcher = GMSAutocompleteFetcher()
gmsFetcher.delegate = self
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError)
{
print("Error" + error.description)
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let userLocation = locations.last
let center = CLLocationCoordinate2D(latitude: userLocation!.coordinate.latitude, longitude: userLocation!.coordinate.longitude)
let camera = GMSCameraPosition.cameraWithLatitude(userLocation!.coordinate.latitude, longitude: userLocation!.coordinate.longitude, zoom: 15);
self.googleMapsView.camera = camera
self.googleMapsView.myLocationEnabled = true
let marker = GMSMarker(position: center)
print("Latitude :- \(userLocation!.coordinate.latitude)")
print("Longitude :-\(userLocation!.coordinate.longitude)")
marker.map = self.googleMapsView
marker.title = "Current Location"
locationManager.stopUpdatingLocation()
}
do requier setting on infoPlist and then try this
#IBOutlet weak var your "name of view which show map": GMSMapView!
override func viewDidLoad(){
super.viewDidLoad()
placesClient = GMSPlacesClient.shared()
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.distanceFilter = 500
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
mapView.settings.myLocationButton = true
mapView.settings.zoomGestures = true
mapView.animate(toViewingAngle: 45)
mapView.delegate = self }
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let newLocation = locations.last // find your device location
mapView.camera = GMSCameraPosition.camera(withTarget: newLocation!.coordinate, zoom: 14.0) // show your device location on map
mapView.settings.myLocationButton = true // show current location button
var lat = (newLocation?.coordinate.latitude)! // get current location latitude
var long = (newLocation?.coordinate.longitude)! //get current location longitude
}
The problem is that you are setting the mapView's frame to CGRectZero. This causes the map to have zero height and zero width, no wonder it does not show!
Try setting it to CGRectMake(0,0,200,200) for example, this will give you a map at the left top of the screen with a size of 200 x 200.
I have never used Swift before, so the syntax might be a little different for CGRectMake()
It seems that your creation of the map isn't in your viewDidLoad function. You may want to try moving that there and see what happens.
Add the appropriate properties into the info.plist.
You should put make sure you have the NS properties of locationalwaysusagedescription and wheninuseusagedescription in the information properties list. This allows for the permissions of the current location to be asked.
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
class MapsViewController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {
var mapView = GMSMapView()
var locationManager = CLLocationManager()
let marker = GMSMarker()
override func viewDidLoad(){
super.viewDidLoad()
mapView.frame = self.view.bounds
self.view.addSubview(mapView)
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 10
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
mapView.settings.myLocationButton = true
mapView.settings.zoomGestures = true
mapView.animate(toViewingAngle: 45)
mapView.delegate = self
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let newLocation = locations.last // find your device location
mapView.camera = GMSCameraPosition.camera(withTarget: newLocation!.coordinate, zoom: 14.0) // show your device location on map
mapView.settings.myLocationButton = true // show current location button
let lat = (newLocation?.coordinate.latitude)! // get current location latitude
let long = (newLocation?.coordinate.longitude)! //get current location longitude
marker.position = CLLocationCoordinate2DMake(lat,long)
marker.map = mapView
print("Current Lat Long - " ,lat, long )
}
func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
mapView.clear()
DispatchQueue.main.async {
let position = CLLocationCoordinate2D(latitude: coordinate.latitude, longitude: coordinate.longitude)
self.marker.position = position
self.marker.map = mapView
self.marker.icon = UIImage(named: "default_marker")
print("New Marker Lat Long - ",coordinate.latitude, coordinate.longitude)
}
}
}

Resources