iOS Swift Getting directions between two points and drawing to map - ios

I want to get the directions between two points and draw them to my MKMapView. This is my implementation:
First, at my controller's viewDidLoad callback, I set map's view delegate to self
map.delegate = self
Then, I request all routes:
func showDirections() {
let currentPlace: MKPlacemark = MKPlacemark(coordinate: location.coordinate, addressDictionary: nil)
let place: MKPlacemark = MKPlacemark(coordinate: annotation.coordinate, addressDictionary: nil)
println(currentPlace.coordinate.latitude)
println(currentPlace.coordinate.longitude)
println(place.coordinate.latitude)
println(place.coordinate.longitude)
var request: MKDirectionsRequest = MKDirectionsRequest.new()
request.setSource(MKMapItem(placemark: currentPlace))
request.setDestination(MKMapItem(placemark: place))
request.transportType = MKDirectionsTransportType.Any
request.requestsAlternateRoutes = true
var directions: MKDirections = MKDirections(request: request)
directions.calculateDirectionsWithCompletionHandler() {
(response, error) in
println(error)
if(error == nil && response != nil) {
println("Numero de rutas: " + String(response.routes.count))
println(response.routes[0].coordinate)
for(var i = 0;i < response.routes.count; i++) {
self.map.addOverlays(response.routes[i].overlays, level: MKOverlayLevel.AboveRoads)
self.map.setNeedsDisplay()
}
for r in response.routes { println("route = \(r)") }
}
}
}
And implement this two callbacks too:
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
println("redenrerForOverlay")
if(overlay.isKindOfClass(MKPolyline)) {
var renderer: MKPolylineRenderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = Colors.psnGreen
renderer.lineWidth = 5
return renderer
}
return nil
}
func mapView(mapView: MKMapView!, viewForOverlay overlay: MKOverlay!) -> MKOverlayView! {
println("ViewForOverlay")
if (overlay.isKindOfClass(MKPolyline)) {
var lineView: MKPolylineView = MKPolylineView(overlay: overlay)
lineView.backgroundColor = Colors.psnGreen
return lineView;
}
return nil;
}
When i run my application, I get two routes to draw, but when I call map.addOverlay, nothing happens. My rendererForOverlay/viewForOverlay are never called. Somenone know how to make it work?
Thanks in advance

I finnaly got the solution
For those who are interested, only change the directions callback to this:
directions.calculateDirectionsWithCompletionHandler() {
(response, error) in
if(error == nil && response != nil) {
for route in response.routes {
var r: MKRoute = route as! MKRoute
self.map.addOverlay(r.polyline, level: MKOverlayLevel.AboveRoads)
}
}
}

Related

MK Directions Request - direction line is not shown

I have successfully implemented location of a point of interest and my location. Both is shown. Now, I would like to get calculated the route between two points and a blue line should be shown. Unfortunately, when I am clicking on the button, no line is being shown.
I really appreciate help/hints. Thanks so much.
import UIKit
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate
{
//outlet variable is used for establishing a connection with the
// map view in the storyboard
#IBOutlet var mapView: MKMapView!
var spot = Spot()
let locationManager = CLLocationManager()
var currentPlacemark:CLPlacemark?// it is used to save the selected spot
override func viewDidLoad()
{
super.viewDidLoad()
//request for a user's authorization for lacation services
locationManager.requestWhenInUseAuthorization()
let status = CLLocationManager.authorizationStatus()
if status == CLAuthorizationStatus.authorizedWhenInUse
{
mapView.showsUserLocation = true
}
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(spot.location,
completionHandler:
{ placemarks, error in
if let error = error {
print(error)
return
}
if let placemarks = placemarks{
//get the first placemark
let placemark = placemarks[0]
// value of current Placemark
self.currentPlacemark = placemark
// add annotation
let annotation = MKPointAnnotation()
annotation.title = self.spot.name
annotation.subtitle = self.spot.type
if let location = placemark.location{
annotation.coordinate = location.coordinate
//display the annotation
self.mapView.showAnnotations([annotation],animated:true)
self.mapView.selectAnnotation(annotation, animated: true)
}
}
})
mapView.showsCompass = true
mapView.showsTraffic = true
mapView.showsScale = true
// we want to show the users location
mapView.showsUserLocation = true
}
#IBAction func showDirection(sender:AnyObject)
{
// we make sure if current placemark contains a value using a guard statement. Otherwise just
// skip everything
guard let currentPlacemark = currentPlacemark else
{
return
}
// creating an instance of MKDirectionsRequest to request directions
let directionRequest = MKDirectionsRequest()
// set the source(where the user currently is) and destination of the route
directionRequest.source = MKMapItem.forCurrentLocation()// retrieving the current location
let destinationPlacemark = MKPlacemark(placemark:currentPlacemark)
directionRequest.destination = MKMapItem(placemark: destinationPlacemark)
directionRequest.transportType = MKDirectionsTransportType.automobile// later change for transit
// calculate the direction
let directions = MKDirections(request: directionRequest)
// this method initiates an asynchronous request for directions and calls
// your completion handler when the request is conpleted. The MKDirections object
//passes my request to the Apple servers ans asks for route-based directions data
directions.calculate { (routeRepsonse, routeError) -> Void in
guard let routeResponse = routeRepsonse else
{
if let routeError = routeError
{
print("Error:\(routeError)")
}
return
}
let route = routeRepsonse?.routes[0]// provides a container for saving the route information so that the routes are saved in the routes property
// The detailed route geometry is e.g. route.polyline is represented by an MKPolyline object
// the add level method is used to add an MKPolyline object to the existing map view
self.mapView.add((route?.polyline)!,level: MKOverlayLevel.aboveRoads)
}
}
// implementing a mapView method which draws the route
func mapView(_mapView:MKMapView,rendererFor overlay: MKOverlay) -> MKOverlayRenderer
{
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 3.0
return renderer
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources hat can be recreated.
}
}
Maybe delegate method name is wrong. Rename
func mapView(_mapView:MKMapView,rendererFor overlay: MKOverlay) -> MKOverlayRenderer
with
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer

Remove Polylines at swift

I'm drawing navigation road at Swift. I'm using current location to another location and made a draw. Afterward, I select another location and redraw it. But even if I write mapView.remove(rotapoly) in my code, it doesnt remove it. How can I solve this?
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
cizim = 1;
let capital = view.annotation as! Station
guard let locValue: CLLocationCoordinate2D = locationManager.location?.coordinate else { return }
let neresi = CLLocationCoordinate2D(latitude: capital.latitude, longitude: capital.longitude)
let nerdeyim = CLLocationCoordinate2D(latitude: locValue.latitude, longitude: locValue.longitude)
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: nerdeyim, addressDictionary: nil))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: neresi, addressDictionary: nil))
request.requestsAlternateRoutes = true
request.transportType = .walking
let directions = MKDirections(request: request)
directions.calculate { [unowned self] response, error in
guard let unwrappedResponse = response else { return }
if (unwrappedResponse.routes.count > 0) {
self.showRoute(response!)
}
}
}
func showRoute(_ response: MKDirectionsResponse) {
mapView.remove(rotapoly)
for route in response.routes {
rotapoly = route.polyline
mapView.add(rotapoly, level: MKOverlayLevel.aboveRoads)
for step in route.steps {
print(step.instructions)
}
}
}
use map view method
self.mapview.removeOverlays(self.mapview.overlays)
this will remove all overlays you have added so you have to do whole process again its like reloading map view
Below is the approach to remove travelled polyline from google map iOS Swift:
var oldPolyLines = [GMSPolyline]() /* Global Array Variable of your Class */
Put below code where you are parsing routes and getting new polyline from direction API.
if self.oldPolyLines.count > 0 {
for polyline in self.oldPolyLines {
polyline.map = nil
}
}
self.oldPolyLines.append(yourNewPolyline)
yourNewPolyLine.map = self.mapView

How to draw or make custom route using MKMapView

I am trying to write an app that will show the route of a tram line. I have added the map, but I am having problems trying to find how to add this route to my app.
I have looked at MKOverlayRenderer and I think I have to add an image on top of the map to do this. There are some tutorials but they are outdated.
Can someone help me with this. Thank you
As a beginner you should check the https://www.raywenderlich.com/90971/introduction-mapkit-swift-tutorial.
example
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer! {
if overlay is MKPolyline {
let lineView = MKPolylineRenderer(overlay: overlay)
lineView.strokeColor = UIColor.redColor()
lineView.lineWidth = 1
return lineView
}
return nil
}
func addRoute() {
mapView.deselectAnnotation(selectedAnnotationView.annotation, animated: true)
let track = Track.GetAll()// to get list of coordinates you should write your own way to store
if track.count == 0 {
return
}
var pointsToUse: [CLLocationCoordinate2D] = []
var isTrackChanged = false
for i in 0...track.count-1 {
let x = CLLocationDegrees((track[i].Latitude as NSString).doubleValue)
let y = CLLocationDegrees((track[i].Longitude as NSString).doubleValue)
pointsToUse += [CLLocationCoordinate2DMake(x, y)]
if i > 0 {
if pointsToUse[i-1].latitude != pointsToUse[i].latitude || pointsToUse[i-1].longitude != pointsToUse[i].longitude {
isTrackChanged = true
}
}
}
let myPolyline = MKGeodesicPolyline(coordinates: &pointsToUse, count: track.count)
mapView.addOverlay(myPolyline)
}
//model
class Track{
var latitude =""
var longitude=""
}

Swift - fatal error: unexpectedly found nil while unwrapping an Optional value on MKCoordinateRegionMakeWithDistance

I'm trying to get my map to show directions to a local searched location from the current user location.
I'm getting an EXC_BAD_INSTRUCTION on the line:
let region = MKCoordinateRegionMakeWithDistance(userLocation.location.coordinate, 2000, 2000)`
And on the line:
else {
self.showRoute(response)
}
I have a feeling the nil it's receiving is from the user location, which I'm not sure why it would be receiving a nil there.
Here is the full code for my view controller if needed:
class RouteViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var routeMap: MKMapView!
var destination = MKMapItem?()
override func viewDidLoad() {
super.viewDidLoad()
routeMap.showsUserLocation = true
routeMap.delegate = self
self.getDirections()
}
func getDirections() {
let request = MKDirectionsRequest()
request.setSource(MKMapItem.mapItemForCurrentLocation())
request.setDestination(destination!)
request.requestsAlternateRoutes = false
let directions = MKDirections(request: request)
directions.calculateDirectionsWithCompletionHandler({(response:
MKDirectionsResponse!, error: NSError!) in
if error != nil {
println("Error getting directions")
} else {
self.showRoute(response)
}
})
}
func showRoute(response: MKDirectionsResponse) {
for route in response.routes as! [MKRoute] {
routeMap.addOverlay(route.polyline,
level: MKOverlayLevel.AboveRoads)
for step in route.steps {
println(step.instructions)
}
}
let userLocation = routeMap.userLocation
let region = MKCoordinateRegionMakeWithDistance(
userLocation.location.coordinate, 2000, 2000)
routeMap.setRegion(region, animated: true)
}
func mapView(mapView: MKMapView!, rendererForOverlay
overlay: MKOverlay!) -> MKOverlayRenderer! {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blueColor()
renderer.lineWidth = 5.0
return renderer
}
}
And the segue code:
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject?) {
let routeViewController = segue.destinationViewController
as! RouteViewController
let indexPath = self.tableView.indexPathForSelectedRow()
let row = indexPath?.row
routeViewController.destination = mapItems[row!]
}
Any help would be appreciated, thanks!
The problem is this line:
let userLocation = routeMap.userLocation
The result, userLocation might be nil, and its location might be nil, because the map might not have been told to, or succeeded in acquiring, the user's location. You are not taking into account that possibility.
The way to do that is to unwrap the Optionals safely and proceed only if the unwrapping succeeded:
if let userLocation = routeMap.userLocation, loc = userLocation.location {
let region = MKCoordinateRegionMakeWithDistance(
loc.coordinate, 2000, 2000)
routeMap.setRegion(region, animated: true)
}
We don't need the userLocation separately for anything, so we can collapse that into a single test:
if let loc = routeMap.userLocation.location {
let region = MKCoordinateRegionMakeWithDistance(
loc.coordinate, 2000, 2000)
routeMap.setRegion(region, animated: true)
}

Polyline Overlay in Swift

I have my MKMapViewDelegate in place. Also, MapView.delegate = self
let c1 = myCLLocationCoodinate
let c2 = myCLLocationCoodinate2
var a = [c1, c2]
var polyline = MKPolyline(coordinates: &a, count: a.count)
self.MapView.addOverlay(polyline)
With this Delegate Method:
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if overlay is MKPolyline {
var polylineRenderer = MKPolylineRenderer(overlay: overlay)
polylineRenderer.strokeColor = UIColor.whiteColor()
polylineRenderer.lineWidth = 2
return polylineRenderer
}
return nil
}
I get this: EXC BAD ACCESS Thread 8 on
self.MapView.addOverlay(polyline)
I think issue here is with the line:
var a = [c1, c2]
Here you directly created array without specifying its type.
See below reference code to create Polyline overlay and related delegate method:
let c1 = myCLLocationCoodinate
let c2 = myCLLocationCoodinate2
var points: [CLLocationCoordinate2D]
points = [c1, c2]
var geodesic = MKGeodesicPolyline(coordinates: &points[0], count: 2)
mapView.add(geodesic)
UIView.animate(withDuration: 1.5, animations: { () -> Void in
let span = MKCoordinateSpanMake(20, 20)
let region1 = MKCoordinateRegion(center: c1, span: span)
mapView.setRegion(region1, animated: true)
})
A delegate method to render overlay:
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if overlay is MKPolyline {
var polylineRenderer = MKPolylineRenderer(overlay: overlay)
polylineRenderer.strokeColor = UIColor.whiteColor()
polylineRenderer.lineWidth = 2
return polylineRenderer
}
return nil
}
It seems that your map view has been deallocated. The polyline construction is OK.
Normally, variables start with lowercase. Have you subclassed the map view and are trying to access the class?
I spent WAAAAAAAAYYYY too much time on this so I thought I would add the solution to a similar issue. I was getting a EXC BAD ACCESS on addOverlay w/ a MKPolygon. Turns out I was just on the wrong thread the whole time. Fixed it with:
var points = [MKMapPoint]()
for var i = 0; i < area.coordinates.count; i+=2 {
let c = CLLocationCoordinate2DMake(area.coordinates[i], area.coordinates[i+1])
points.append(MKMapPointForCoordinate(c))
}
let polygon = MKPolygon(points: &points, count: points.count)
dispatch_async(dispatch_get_main_queue(), {
self.mapView.addOverlay(polygon)
})
let firstlat : string = "12.9166"
let firstlon : string = "77.6101"
let secondlat : string = "12.9610"
let secondLon : string = "77.6387"
let point1 = CLLocationCoordinate2DMake(Double(firstlat)!, Double(firstlon)!)
let point2 = CLLocationCoordinate2DMake(Double(secondlat as String)!, Double(secondLon)!)
let pickAnnotation : MKPointAnnotation = MKPointAnnotation()
pickAnnotation.coordinate = point1
pickAnnotation.title = "pick"
displayMapView.addAnnotation(pickAnnotation)
let dropAnnotation : MKPointAnnotation = MKPointAnnotation()
dropAnnotation.coordinate = point2
dropAnnotation.title = "drop"
displayMapView.addAnnotation(dropAnnotation)
displayMapView.showAnnotations(displayMapView.annotations, animated: true)
var points: [CLLocationCoordinate2D]
points = [point1, point2]
routeLine = MKPolyline(coordinates: &points[0] , count: 2)
displayMapView.add(routeLine)
func showRouteOnMap(_ pickCoordinate: CLLocationCoordinate2D, _ destinationCoordinate: CLLocationCoordinate2D) {
let request = MKDirections.Request()
let sourcePlacemark = MKPlacemark(coordinate: pickCoordinate)
let sourceMapItem = MKMapItem(placemark: sourcePlacemark)
request.source = sourceMapItem
let myPlacemark = MKPlacemark(coordinate: destinationCoordinate)
let destinationMapItem = MKMapItem(placemark: myPlacemark)
request.destination = destinationMapItem
request.requestsAlternateRoutes = false
let directions = MKDirections(request: request)
directions.calculate(completionHandler: {(response, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let response = response {
self.showRoute(response)
}
}
})
}
func showRoute(_ response: MKDirections.Response) {
for route in response.routes {
routeMap.addOverlay(route.polyline,
level: MKOverlayLevel.aboveRoads)
self.routeMap.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)
}
}
// MARK: - MKMapViewDelegate
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor(red: 17.0/255.0, green: 147.0/255.0, blue: 255.0/255.0, alpha: 1)
renderer.lineWidth = 5.0
return renderer
}

Resources