I calculate the route and I want to add a pin annotation at the end...
declare location, send request, declare directions and reset my map view with new directions...
guard let location = locationManager.location?.coordinate else { return }
let request = createDirectionsRequest(from: location)
let directions = MKDirections(request: request)
resetMapView(withNew: directions)
after I calculate the direction and add annotation...
directions.calculate { [unowned self] (response, error) in
guard let response = response else { return }
for route in response.routes {
self.mapView.add(route.polyline, level: MKOverlayLevel.aboveRoads)
self.mapView.setCenter(route.polyline.coordinate, animated: true)
self.mapView.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)
self.mapView.setRegion(MKCoordinateRegionMakeWithDistance(route.polyline.coordinate, route.distance*0.75, route.distance*0.75), animated: true)
let routeAnnotation = MKPointAnnotation()
routeAnnotation.coordinate = MKCoordinateForMapPoint(route.polyline.points()[route.polyline.pointCount/2])
self.mapView.addAnnotation(routeAnnotation)
}
}
and this is the result... the annotation is not in the right position of my route... where i'm wrong?
UPDATE
In my case I solved the problem...
I select my destination with a pointer positioned on center of the view... move the map and return me the center coordinates with a function that i write for calculate it:
func getCenterLocation(for mapView: MKMapView) -> CLLocation {
let latidude = mapView.centerCoordinate.latitude
let longitude = mapView.centerCoordinate.longitude
return CLLocation(latitude: latidude, longitude: longitude)
}
I simply call this func for determine my pin coordinates that are the same of the center of the view (pointer) and consequently are the same the destination point...
routeAnnotation.coordinate = self.getCenterLocation(for: self.mapView).coordinate
and this is the result
more solutions are accepted...
Related
I have been following Sean Allens tutorial on MapKit and Directions. I used the code to fit it to my SwiftUI project.
Everything works fine, the screen zoom to fit start and destination on the screen, and calculated the rout correctly.
However, it does to actually display the route.
The main Code in my ViewModel (note: .delegate = self is actually in a function, I wrote it at top level for easier understanding how I am setting the Delegat)
func checkForLocationServices() {
if CLLocationManager.locationServicesEnabled() {
locationManager = CLLocationManager()
locationManager!.delegate = self
}
}
//MARK: Directions
func getDirections() {
guard let location = locationManager?.location?.coordinate else {
return
}
let request = createDirectionRequest(from: location)
let directions = MKDirections(request: request)
directions.calculate { calcResponse, error in
guard let response = calcResponse else { return }
for route in response.routes {
print("Route \(route.name) is \(route.distance) long")
self.mapView.addOverlay(route.polyline, level: .aboveRoads)
self.mapView.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)
}
}
}
func createDirectionRequest(from coordinate: CLLocationCoordinate2D) -> MKDirections.Request {
let startLocation = MKPlacemark(coordinate: coordinate)
let destinationLocation = MKPlacemark(coordinate: bbcLocation.coordinate)
let request = MKDirections.Request()
request.source = MKMapItem(placemark: startLocation)
request.destination = MKMapItem(placemark: destinationLocation)
request.transportType = .automobile
request.requestsAlternateRoutes = true
return request
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay as! MKPolyline)
renderer.strokeColor = .blue
renderer.lineWidth = 5.0
print(renderer.strokeStart)
print(renderer.strokeEnd)
return renderer
}
The Delegate function rendererFor is never called, despite me setting the Delegate as seen above. I also checked via Breakpoints that it is actually set, as well as downloaded Seans source code, which set the Delegate the same way I did.
My VM conforms to the MKMapViewDelegate Protocol.
Another user had the same problem, but since he got no response, I thought I might try I someone can help me.
Core-question: Why is my delegate method not called, despite me setting it correctly, and knowing (through console logs) that my route is found, calculated, and in fact zoomed in on?
If you need more code, feel free to ask.
Thanks in advance.
*Seans Project
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func getDirections() {
guard let location = locationManager.location?.coordinate else {
//TODO: Inform user we don't have their current location
return
}
let request = createDirectionsRequest(from: location)
let directions = MKDirections(request: request)
resetMapView(withNew: directions)
directions.calculate { [unowned self] (response, error) in
//TODO: Handle error if needed
guard let response = response else { return } //TODO: Show response not available in an alert
for route in response.routes {
self.mapView.addOverlay(route.polyline)
self.mapView.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)
}
}
}
func createDirectionsRequest(from coordinate: CLLocationCoordinate2D) -> MKDirections.Request {
let destinationCoordinate = getCenterLocation(for: mapView).coordinate
let startingLocation = MKPlacemark(coordinate: coordinate)
let destination = MKPlacemark(coordinate: destinationCoordinate)
let request = MKDirections.Request()
request.source = MKMapItem(placemark: startingLocation)
request.destination = MKMapItem(placemark: destination)
request.transportType = .automobile
request.requestsAlternateRoutes = true
return request
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay as! MKPolyline)
renderer.strokeColor = .blue
return renderer
}
``
So I am creating an app to track a user's running routes. I am to show a polyline that represents where a user has ran, between a start annotation that is dropped when user begins and a end annotation that is dropped when user clicks a finish button. Seeing as how someone can run pretty much anywhere, is there a way to draw a polyline that is Not limited to just main roads and streets? For example if someone runs somewhere that does not have main roads/streets, how would I show the polyline going through that area on the map? (See Image) Or is that not even possible in swift?
In addition, is it possible to fix these gaps?
Here is my code for rendering the polyline:
func drawPolyline() {
guard let startingCoordinate = self.startingCoordinate, let endingCoordinate = self.endingCoordinate else { return }
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: startingCoordinate))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: endingCoordinate))
request.transportType = .walking
let directions = MKDirections(request: request)
directions.calculate { [weak self] (response, error) in
guard let self = self else { return }
guard error == nil else { return }
guard response != nil else { return }
if let route = response?.routes.first {
self.mapView.addOverlay(route.polyline)
self.mapView.setVisibleMapRect(route.polyline.boundingMapRect, edgePadding: UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15), animated: true)
}
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay
renderer.strokeColor = .systemBlue
renderer.lineWidth = 3
return renderer
}
// code to get coordinate for annotations
guard let coordinate = locationManager.location?.coordinate else { return }
let annotation = MapAnnotation(title: title, coordinate: coordinate)
mapView.addAnnotation(annotation)
You will get an updated location every time it changes, and you can either build up an array of locations, or store them in a database for later retrieval.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
// the last location presented here is the current position of the runner
self.locations.append(locations.last)
// store this in a database, or as a class variable
}
You can set the desired accuracy when you initialise your location manager
locationManager.desiredAccuracy = kCLLocationAccuracyBest
and then when you're creating the map, create a line showing the runner's route
let polyline = MKPolyline(coordinates: self.locations, count: self.locations.count)
self.mapView.addOverlay(polyline)
Here's an example of the output - looks like the finish is in the middle of the run, but that's because I was heading back home
i am not sure how to make the MKPointAnnotations a button i want to be able to click on the pop up to send me to another screen
import UIKit
import MapKit
import Firebase
//building the pin ticker
class mapViewController: UIViewController, MKMapViewDelegate{
//class so an image can be added to point annotation
/* class CustomPointAnnotation: MKPointAnnotation{
var imageName: String!
}*/
#IBOutlet weak var map: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
//set the map location to a specific location when the view loads
let centerLocation = CLLocationCoordinate2DMake(39.863048 , -75.357583)
//logingitude and latitude that the map will cover
let mapSpan = MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001)
//range that the map will show
let mapRange = MKCoordinateRegion(center: centerLocation, span: mapSpan)
//what we will see on the map
self.map.setRegion(mapRange, animated: false)
addAnnotation()
map.delegate = self
let rotate = CGFloat(180)
let regionradius : CLLocationDistance=300.0
let region = MKCoordinateRegion(center: centerLocation, latitudinalMeters: regionradius, longitudinalMeters: regionradius)
//rotation that shows the map is aligned
map.camera.pitch = rotate;
map.setRegion(region, animated: true)
map.delegate = self
map.isUserInteractionEnabled = true
//allows user to still interact with the items on map
let pitch: CGFloat = 300
let heading = 335.0
var camera: MKMapCamera?
camera = MKMapCamera(lookingAtCenter: centerLocation, fromDistance: regionradius, pitch: pitch, heading: heading)
map.camera = camera!
//disables clickables on map
map.isRotateEnabled = false;
map.isZoomEnabled = false;
map.isScrollEnabled = false;
map.showsCompass = false;
}
//funtion to create annotations
private func addAnnotation(){
let parkSpaceOne = MKPointAnnotation()
let parkSpaceTwo = MKPointAnnotation()
let db = Firestore.firestore()
//grabs all the coordinates of the parking spaces in firebase
db.collection("ParkingSpaces").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
if let coords = document.get("coordinate") {
let point = coords as! GeoPoint
let lat = point.latitude
let lon = point.longitude
//string variable for spot field in firebase
let spotpone = document.get("spot") as! String
let spotptwo = document.get("spot") as! String
//if the spot in firbase matches the string then take the coordinates, add them to an annotation and place on the map
if (spotpone == "p1"){
parkSpaceOne.title = "P1"
parkSpaceOne.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
self.map.addAnnotation(parkSpaceOne)
}
else if (spotptwo == "p2"){
parkSpaceTwo.title = "P2"
parkSpaceTwo.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
self.map.addAnnotation(parkSpaceTwo)
}
}
}
}
}
//
}
}
Take a look at the docs
You can detect when the user selects an annotation:
func mapView(MKMapView, didSelect: MKAnnotationView)
Tells the delegate that one of its annotation views was selected.
As well as
func mapView(MKMapView, annotationView: MKAnnotationView, calloutAccessoryControlTapped: UIControl)
Tells the delegate that the user tapped one of the annotation view’s
accessory buttons.
I'm a newbie in swift and I'm trying to calculate the distance, on route, from userLocation to several Point of interest.
I don’t want, in this part of the app, “draw” the route on a map, but I only want to calculate the distance on route instead the distance between two coordinate and then show this distance as information inside the callout on the map.
The coordinate of the POI (latitude and longitude) are contained in a database.
I have read some threads about this argument here on Stack Overflow:
Measuring distance in meters from a drawn route in MKMapView
MKMapView get distance between coordinates on customized route
and other tutorials:
http://www.technetexperts.com/mobile/draw-route-between-2-points-on-map-with-ios7-mapkit-api/
https://videos.raywenderlich.com/courses/mapkit-and-core-location/lessons/9
then i wrote this code:
for item in items {
if item.latitudine != "" && item.longitudine != "" {
// Point Of Interest coordinate
let latitude = Double(item.latitude!)
let longitude = Double(item.longitude!)
let itemLocation = CLLocation(latitude: latitude!, longitude: longitude!)
let itemLocationPlacemark = MKPlacemark(coordinate: itemLocation.coordinate, addressDictionary: nil)
// user coordinate
let userLocation = CLLocation(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let userLocationPlacemark = MKPlacemark(coordinate: userLocation.coordinate, addressDictionary: nil)
// create Request object
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: userLocationPlacemark)
request.destination = MKMapItem(placemark: itemLocationPlacemark)
request.requestsAlternateRoutes = false
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate {
[weak self] (response, error) in
if error == nil {
for route in (response?.routes)! {
let distance = (route.distance)/1000
print(distance)
}
}
}
}
}
The problem is when I execute the code from the line directions.calculate.
The program run the line but then don’t execute the rest, don’t execute the control if error == nil and the instructions in the closure.
So now I wonder if my idea is wrong and, if not, how can obtain my goal.
(Posted solution on behalf of the OP).
Reading other threads I understand that the problem was the closure inside che for loop. So after several attempts I found the solution that work for me. I write it here so that can be useful to someone else:
var counter: Int!
...
for item in itemPin {
if item.latitude != "" && item.longitude != "" {
....
....
let directions = MKDirections(request: request)
directions.calculate {
(response, error) in
print("error \(error)")
if error == nil {
counter = self.counter + 1
print(self.counter)
if self.counter >= self.itemPin.count {
let result = (response?.routes[0].distance)!/1000
}
}
}
...
}
}
I am trying to get the latitude en longitude coordinates of the CLLocationManager instance of the current user's location.
I have those two lines of code in my viewWillAppear method:
locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
Then I have a method called startRoute to calc the route from the current position to some specific annotation on the map. After that I draw the route between those paths. Unfortunately, this is working with two annotations on the map, but I can't get it work with the current location of the user and some annotation.
I tried below script, but when I print print("LAT \(currentLocation.coordinate.latitude)") it will give me 0.0 as result.
func startRoute() {
// Set the latitude and longtitude of the locations (markers)
let sourceLocation = CLLocationCoordinate2D(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude)
let destinationLocation = CLLocationCoordinate2D(latitude: sightseeing.latitude!, longitude: sightseeing.longitude!)
// Create placemark objects containing the location's coordinates
let sourcePlacemark = MKPlacemark(coordinate: sourceLocation, addressDictionary: nil)
let destinationPlacemark = MKPlacemark(coordinate: destinationLocation, addressDictionary: nil)
// MKMapitems are used for routing. This class encapsulates information about a specific point on the map
let sourceMapItem = MKMapItem(placemark: sourcePlacemark)
let destinationMapItem = MKMapItem(placemark: destinationPlacemark)
// Annotations are added which shows the name of the placemarks
let sourceAnnotation = MKPointAnnotation()
sourceAnnotation.title = "Times Square"
if let location = sourcePlacemark.location {
sourceAnnotation.coordinate = location.coordinate
}
let destinationAnnotation = MKPointAnnotation()
destinationAnnotation.title = "Title"
destinationAnnotation.subtitle = "Subtitle"
if let location = destinationPlacemark.location {
destinationAnnotation.coordinate = location.coordinate
}
// The annotations are displayed on the map
self.mapView.showAnnotations([sourceAnnotation,destinationAnnotation], animated: true )
// The MKDirectionsRequest class is used to compute the route
let directionRequest = MKDirectionsRequest()
directionRequest.source = sourceMapItem
directionRequest.destination = destinationMapItem
directionRequest.transportType = .walking
// Calculate the direction
let directions = MKDirections(request: directionRequest)
// The route will be drawn using a polyline as a overlay view on top of the map.
// The region is set so both locations will be visible
directions.calculate {
(response, error) -> Void in
guard let response = response else {
if let error = error {
print("Error: \(error)")
}
return
}
let route = response.routes[0]
self.mapView.add((route.polyline), level: MKOverlayLevel.aboveRoads)
let rect = route.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegionForMapRect(rect), animated: true)
}
}
At the top, directly after the class declaration I create the reference to location manager and 'CLLocation':
var locationManager: CLLocationManager!
var currentLocation = CLLocation()
After all, this isn't working. Only when I change the sourceLocation to hardcoded coordinates, it will draw the route to the destination. What do I wrong or what am I missing?
but when I print print("LAT \(currentLocation.coordinate.latitude)") it will give me 0.0 as result
Of course it does. What else would it do? You have a property
var currentLocation = CLLocation()
That is a zero location. You have no code that ever changes this. Therefore, it is always a zero location.
You say you want the
current user's location
but nowhere do you have any code that obtains the user's location.