Build a navigation app for iOS
The Mapbox Navigation SDK for iOS gives you all the tools you need to add turn-by-turn navigation to your iOS app. You can get up and running in a few minutes with our drop-in navigation view controller, or build a completely custom app with our core components for routing and navigation. In this guide, you'll create a minimal navigation app, initiate navigation by selecting a point on the map, and change the style of the navigation view controller.

Getting started
The Mapbox Navigation SDK for iOS runs on iOS 9.0 and above. It can be used with code written in either Swift 4 and above or Objective-C. Here are the resources you’ll need before getting started:
- A Mapbox account and access token. Sign up for an account at mapbox.com/signup. You can find your access tokens on your Account page. You will add your access token to your Info.plist file as described in the installation instructions.
- An application including the Mapbox Maps SDK for iOS. This guide assumes that you have already begun building an iOS application that uses the Mapbox Maps SDK for iOS. If you're new to the Mapbox Maps SDK for iOS, check out the First steps with the Mapbox Maps SDK for iOS guide to set up a simple map view first.
Install the Navigation SDK for iOS
Install the framework
You'll need to add MapboxNavigation
to your build in order to use the Mapbox Navigation SDK, map services, and directions services.
Cocoa pods:
pod 'MapboxNavigation', '~> 0.29.0'
Carthage:
github "mapbox/mapbox-navigation-ios" ~> 0.29.0
Import the framework
After you've added this to your build, you'll need to import the following modules into your ViewController to access them in your application.
import Mapboximport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirections class ViewController: UIViewController, MGLMapViewDelegate {var mapView: NavigationMapView!var directionsRoute: Route? override func viewDidLoad() {super.viewDidLoad() mapView = NavigationMapView(frame: view.bounds) view.addSubview(mapView) // Set the map view's delegatemapView.delegate = self // Allow the map to display the user's locationmapView.showsUserLocation = truemapView.setUserTrackingMode(.follow, animated: true) // Add a gesture recognizer to the map viewlet longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))mapView.addGestureRecognizer(longPress)} @objc func didLongPress(_ sender: UILongPressGestureRecognizer) {guard sender.state == .began else { return } // Converts point where user did a long press to map coordinateslet point = sender.location(in: mapView)let coordinate = mapView.convert(point, toCoordinateFrom: mapView) // Create a basic point annotation and add it to the maplet annotation = MGLPointAnnotation()annotation.coordinate = coordinateannotation.title = "Start navigation"mapView.addAnnotation(annotation) // Calculate the route from the user's location to the set destinationcalculateRoute(from: (mapView.userLocation!.coordinate), to: annotation.coordinate) { (route, error) inif error != nil {print("Error calculating route")}}} // Calculate route to be used for navigationfunc calculateRoute(from origin: CLLocationCoordinate2D,to destination: CLLocationCoordinate2D,completion: @escaping (Route?, Error?) -> ()) { // Coordinate accuracy is the maximum distance away from the waypoint that the route may still be considered viable, measured in meters. Negative values indicate that a indefinite number of meters away from the route and still be considered viable.let origin = Waypoint(coordinate: origin, coordinateAccuracy: -1, name: "Start")let destination = Waypoint(coordinate: destination, coordinateAccuracy: -1, name: "Finish") // Specify that the route is intended for automobiles avoiding trafficlet options = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic) // Generate the route object and draw it on the map_ = Directions.shared.calculate(options) { [unowned self] (waypoints, routes, error) inself.directionsRoute = routes?.first// Draw the route on the map after creating itself.drawRoute(route: self.directionsRoute!)}} func drawRoute(route: Route) {guard route.coordinateCount > 0 else { return }// Convert the route’s coordinates into a polylinevar routeCoordinates = route.coordinates!let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: route.coordinateCount) // If there's already a route line on the map, reset its shape to the new routeif let source = mapView.style?.source(withIdentifier: "route-source") as? MGLShapeSource {source.shape = polyline} else {let source = MGLShapeSource(identifier: "route-source", features: [polyline], options: nil) // Customize the route line color and widthlet lineStyle = MGLLineStyleLayer(identifier: "route-style", source: source)lineStyle.lineColor = NSExpression(forConstantValue: #colorLiteral(red: 0.1897518039, green: 0.3010634184, blue: 0.7994888425, alpha: 1))lineStyle.lineWidth = NSExpression(forConstantValue: 3) // Add the source and style layer of the route line to the mapmapView.style?.addSource(source)mapView.style?.addLayer(lineStyle)}} // Implement the delegate method that allows annotations to show callouts when tappedfunc mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {return true} // Present the navigation view controller when the callout is selectedfunc mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {let navigationViewController = NavigationViewController(for: directionsRoute!)self.present(navigationViewController, animated: true, completion: nil)}}
Initialize a map
Once you've imported the classes in the previous step, you'll be able to initialize a map. Use NavigationMapView
to display the default Mapbox Streets map style. NavigationMapView
is a subclass of MGLMapView
that provides additional functionality (like displaying a route on the map) that is helpful for navigation apps specifically. The directionsRoute
variable will be used later to reference a route that will be generated by the Mapbox Navigation SDK. Add the following code within the view controller.
import Mapboximport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirections class ViewController: UIViewController, MGLMapViewDelegate {var mapView: NavigationMapView!var directionsRoute: Route? override func viewDidLoad() {super.viewDidLoad() mapView = NavigationMapView(frame: view.bounds) view.addSubview(mapView) // Set the map view's delegatemapView.delegate = self // Allow the map to display the user's locationmapView.showsUserLocation = truemapView.setUserTrackingMode(.follow, animated: true) // Add a gesture recognizer to the map viewlet longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))mapView.addGestureRecognizer(longPress)} @objc func didLongPress(_ sender: UILongPressGestureRecognizer) {guard sender.state == .began else { return } // Converts point where user did a long press to map coordinateslet point = sender.location(in: mapView)let coordinate = mapView.convert(point, toCoordinateFrom: mapView) // Create a basic point annotation and add it to the maplet annotation = MGLPointAnnotation()annotation.coordinate = coordinateannotation.title = "Start navigation"mapView.addAnnotation(annotation) // Calculate the route from the user's location to the set destinationcalculateRoute(from: (mapView.userLocation!.coordinate), to: annotation.coordinate) { (route, error) inif error != nil {print("Error calculating route")}}} // Calculate route to be used for navigationfunc calculateRoute(from origin: CLLocationCoordinate2D,to destination: CLLocationCoordinate2D,completion: @escaping (Route?, Error?) -> ()) { // Coordinate accuracy is the maximum distance away from the waypoint that the route may still be considered viable, measured in meters. Negative values indicate that a indefinite number of meters away from the route and still be considered viable.let origin = Waypoint(coordinate: origin, coordinateAccuracy: -1, name: "Start")let destination = Waypoint(coordinate: destination, coordinateAccuracy: -1, name: "Finish") // Specify that the route is intended for automobiles avoiding trafficlet options = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic) // Generate the route object and draw it on the map_ = Directions.shared.calculate(options) { [unowned self] (waypoints, routes, error) inself.directionsRoute = routes?.first// Draw the route on the map after creating itself.drawRoute(route: self.directionsRoute!)}} func drawRoute(route: Route) {guard route.coordinateCount > 0 else { return }// Convert the route’s coordinates into a polylinevar routeCoordinates = route.coordinates!let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: route.coordinateCount) // If there's already a route line on the map, reset its shape to the new routeif let source = mapView.style?.source(withIdentifier: "route-source") as? MGLShapeSource {source.shape = polyline} else {let source = MGLShapeSource(identifier: "route-source", features: [polyline], options: nil) // Customize the route line color and widthlet lineStyle = MGLLineStyleLayer(identifier: "route-style", source: source)lineStyle.lineColor = NSExpression(forConstantValue: #colorLiteral(red: 0.1897518039, green: 0.3010634184, blue: 0.7994888425, alpha: 1))lineStyle.lineWidth = NSExpression(forConstantValue: 3) // Add the source and style layer of the route line to the mapmapView.style?.addSource(source)mapView.style?.addLayer(lineStyle)}} // Implement the delegate method that allows annotations to show callouts when tappedfunc mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {return true} // Present the navigation view controller when the callout is selectedfunc mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {let navigationViewController = NavigationViewController(for: directionsRoute!)self.present(navigationViewController, animated: true, completion: nil)}}
Run your application, and you will see a new map.

There's a lot more that you can do with the Mapbox Maps SDK for iOS. To see what's possible, check out our First steps with the Mapbox Maps SDK for iOS and Data-driven styling for iOS tutorials.
Display user location
For this project, you'll get directions between a user's location and a point they have placed on the map. To do this, you'll need to configure location permissions within your application in order to use the device's location services. Before you can draw a user’s location on the map, you must ask for their permission and give a brief explanation of how your application will use their location data.
Configure location permissions by setting the NSLocationAlwaysUsageDescription
or NSLocationWhenInUseUsageDescription
key in the Info.plist file. We recommend setting the value to the following string which represents the application's location usage description: Shows your location on the map and helps improve OpenStreetMap
. When a user opens your application for the first time, they will be presented with an alert that asks them if they would like to allow your application to access their location.
In iOS 11, if you wish to access a user's location, you must include the NSLocationWhenInUseUsageDescription
key within the Info.plist file. For more information location tracking in iOS 11, we recommend watching Apple's "What's New in Location Technologies" video.
Once you have configured your application's location permissions, display the device's current location on the map by setting the showsUserLocation
property on the map view to true
within the viewDidLoad
method, and set the MGLUserTrackingMode
to follow
so that the map view adjusts if the user's location changes. Modify the code within the viewDidLoad
method in your view controller to show the user's location and set the tracking mode.
import Mapboximport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirections class ViewController: UIViewController, MGLMapViewDelegate {var mapView: NavigationMapView!var directionsRoute: Route? override func viewDidLoad() {super.viewDidLoad() mapView = NavigationMapView(frame: view.bounds) view.addSubview(mapView) // Set the map view's delegatemapView.delegate = self // Allow the map to display the user's locationmapView.showsUserLocation = truemapView.setUserTrackingMode(.follow, animated: true) // Add a gesture recognizer to the map viewlet longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))mapView.addGestureRecognizer(longPress)} @objc func didLongPress(_ sender: UILongPressGestureRecognizer) {guard sender.state == .began else { return } // Converts point where user did a long press to map coordinateslet point = sender.location(in: mapView)let coordinate = mapView.convert(point, toCoordinateFrom: mapView) // Create a basic point annotation and add it to the maplet annotation = MGLPointAnnotation()annotation.coordinate = coordinateannotation.title = "Start navigation"mapView.addAnnotation(annotation) // Calculate the route from the user's location to the set destinationcalculateRoute(from: (mapView.userLocation!.coordinate), to: annotation.coordinate) { (route, error) inif error != nil {print("Error calculating route")}}} // Calculate route to be used for navigationfunc calculateRoute(from origin: CLLocationCoordinate2D,to destination: CLLocationCoordinate2D,completion: @escaping (Route?, Error?) -> ()) { // Coordinate accuracy is the maximum distance away from the waypoint that the route may still be considered viable, measured in meters. Negative values indicate that a indefinite number of meters away from the route and still be considered viable.let origin = Waypoint(coordinate: origin, coordinateAccuracy: -1, name: "Start")let destination = Waypoint(coordinate: destination, coordinateAccuracy: -1, name: "Finish") // Specify that the route is intended for automobiles avoiding trafficlet options = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic) // Generate the route object and draw it on the map_ = Directions.shared.calculate(options) { [unowned self] (waypoints, routes, error) inself.directionsRoute = routes?.first// Draw the route on the map after creating itself.drawRoute(route: self.directionsRoute!)}} func drawRoute(route: Route) {guard route.coordinateCount > 0 else { return }// Convert the route’s coordinates into a polylinevar routeCoordinates = route.coordinates!let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: route.coordinateCount) // If there's already a route line on the map, reset its shape to the new routeif let source = mapView.style?.source(withIdentifier: "route-source") as? MGLShapeSource {source.shape = polyline} else {let source = MGLShapeSource(identifier: "route-source", features: [polyline], options: nil) // Customize the route line color and widthlet lineStyle = MGLLineStyleLayer(identifier: "route-style", source: source)lineStyle.lineColor = NSExpression(forConstantValue: #colorLiteral(red: 0.1897518039, green: 0.3010634184, blue: 0.7994888425, alpha: 1))lineStyle.lineWidth = NSExpression(forConstantValue: 3) // Add the source and style layer of the route line to the mapmapView.style?.addSource(source)mapView.style?.addLayer(lineStyle)}} // Implement the delegate method that allows annotations to show callouts when tappedfunc mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {return true} // Present the navigation view controller when the callout is selectedfunc mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {let navigationViewController = NavigationViewController(for: directionsRoute!)self.present(navigationViewController, animated: true, completion: nil)}}
When you run your app in Simulator or on your phone, you’ll be presented with a dialog box asking for permission to use Location Services. Click Allow. You won’t see your location on the map until you go to Simulator’s menu bar and select Debug ‣ Location ‣ Custom Location. Enter 38.909
for latitude, -77.036
for longitude.

Add a destination point
In this application, you'll assume that the user wants to retrieve a route between their current location and any point that they select on the map. Next, you'll create the ability to add a marker, or annotation, to the map when the user taps and holds down on the map (a long press). This destination will be used when calculating the route for navigation.
Create a gesture recognizer
Start by creating a new method called didLongPress
. Within this method, get the point that has been selected by the user and store it in a variable called point
. Then, convert that point into a geographic coordinate and store it in a variable called coordinate
. Create a new MGLPointAnnotation
at the coordinate you just defined, and set a title. The title will later be displayed within a callout that appears when the annotation is selected. One the annotation has been configured, add it to the map. Add a new didLongPress
method within your view controller as shown below.
import Mapboximport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirections class ViewController: UIViewController, MGLMapViewDelegate {var mapView: NavigationMapView!var directionsRoute: Route? override func viewDidLoad() {super.viewDidLoad() mapView = NavigationMapView(frame: view.bounds) view.addSubview(mapView) // Set the map view's delegatemapView.delegate = self // Allow the map to display the user's locationmapView.showsUserLocation = truemapView.setUserTrackingMode(.follow, animated: true) // Add a gesture recognizer to the map viewlet longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))mapView.addGestureRecognizer(longPress)} @objc func didLongPress(_ sender: UILongPressGestureRecognizer) {guard sender.state == .began else { return } // Converts point where user did a long press to map coordinateslet point = sender.location(in: mapView)let coordinate = mapView.convert(point, toCoordinateFrom: mapView) // Create a basic point annotation and add it to the maplet annotation = MGLPointAnnotation()annotation.coordinate = coordinateannotation.title = "Start navigation"mapView.addAnnotation(annotation) // Calculate the route from the user's location to the set destinationcalculateRoute(from: (mapView.userLocation!.coordinate), to: annotation.coordinate) { (route, error) inif error != nil {print("Error calculating route")}}} // Calculate route to be used for navigationfunc calculateRoute(from origin: CLLocationCoordinate2D,to destination: CLLocationCoordinate2D,completion: @escaping (Route?, Error?) -> ()) { // Coordinate accuracy is the maximum distance away from the waypoint that the route may still be considered viable, measured in meters. Negative values indicate that a indefinite number of meters away from the route and still be considered viable.let origin = Waypoint(coordinate: origin, coordinateAccuracy: -1, name: "Start")let destination = Waypoint(coordinate: destination, coordinateAccuracy: -1, name: "Finish") // Specify that the route is intended for automobiles avoiding trafficlet options = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic) // Generate the route object and draw it on the map_ = Directions.shared.calculate(options) { [unowned self] (waypoints, routes, error) inself.directionsRoute = routes?.first// Draw the route on the map after creating itself.drawRoute(route: self.directionsRoute!)}} func drawRoute(route: Route) {guard route.coordinateCount > 0 else { return }// Convert the route’s coordinates into a polylinevar routeCoordinates = route.coordinates!let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: route.coordinateCount) // If there's already a route line on the map, reset its shape to the new routeif let source = mapView.style?.source(withIdentifier: "route-source") as? MGLShapeSource {source.shape = polyline} else {let source = MGLShapeSource(identifier: "route-source", features: [polyline], options: nil) // Customize the route line color and widthlet lineStyle = MGLLineStyleLayer(identifier: "route-style", source: source)lineStyle.lineColor = NSExpression(forConstantValue: #colorLiteral(red: 0.1897518039, green: 0.3010634184, blue: 0.7994888425, alpha: 1))lineStyle.lineWidth = NSExpression(forConstantValue: 3) // Add the source and style layer of the route line to the mapmapView.style?.addSource(source)mapView.style?.addLayer(lineStyle)}} // Implement the delegate method that allows annotations to show callouts when tappedfunc mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {return true} // Present the navigation view controller when the callout is selectedfunc mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {let navigationViewController = NavigationViewController(for: directionsRoute!)self.present(navigationViewController, animated: true, completion: nil)}}
Later on, you will add another method to calculate the route from the origin to your destination within this method.
Modify the viewDidLoad
method to add a UILongPressGestureRecognizer
to your NavigationMapView
so it will accept the newly created gesture recognizer.
import Mapboximport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirections class ViewController: UIViewController, MGLMapViewDelegate {var mapView: NavigationMapView!var directionsRoute: Route? override func viewDidLoad() {super.viewDidLoad() mapView = NavigationMapView(frame: view.bounds) view.addSubview(mapView) // Set the map view's delegatemapView.delegate = self // Allow the map to display the user's locationmapView.showsUserLocation = truemapView.setUserTrackingMode(.follow, animated: true) // Add a gesture recognizer to the map viewlet longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))mapView.addGestureRecognizer(longPress)} @objc func didLongPress(_ sender: UILongPressGestureRecognizer) {guard sender.state == .began else { return } // Converts point where user did a long press to map coordinateslet point = sender.location(in: mapView)let coordinate = mapView.convert(point, toCoordinateFrom: mapView) // Create a basic point annotation and add it to the maplet annotation = MGLPointAnnotation()annotation.coordinate = coordinateannotation.title = "Start navigation"mapView.addAnnotation(annotation) // Calculate the route from the user's location to the set destinationcalculateRoute(from: (mapView.userLocation!.coordinate), to: annotation.coordinate) { (route, error) inif error != nil {print("Error calculating route")}}} // Calculate route to be used for navigationfunc calculateRoute(from origin: CLLocationCoordinate2D,to destination: CLLocationCoordinate2D,completion: @escaping (Route?, Error?) -> ()) { // Coordinate accuracy is the maximum distance away from the waypoint that the route may still be considered viable, measured in meters. Negative values indicate that a indefinite number of meters away from the route and still be considered viable.let origin = Waypoint(coordinate: origin, coordinateAccuracy: -1, name: "Start")let destination = Waypoint(coordinate: destination, coordinateAccuracy: -1, name: "Finish") // Specify that the route is intended for automobiles avoiding trafficlet options = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic) // Generate the route object and draw it on the map_ = Directions.shared.calculate(options) { [unowned self] (waypoints, routes, error) inself.directionsRoute = routes?.first// Draw the route on the map after creating itself.drawRoute(route: self.directionsRoute!)}} func drawRoute(route: Route) {guard route.coordinateCount > 0 else { return }// Convert the route’s coordinates into a polylinevar routeCoordinates = route.coordinates!let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: route.coordinateCount) // If there's already a route line on the map, reset its shape to the new routeif let source = mapView.style?.source(withIdentifier: "route-source") as? MGLShapeSource {source.shape = polyline} else {let source = MGLShapeSource(identifier: "route-source", features: [polyline], options: nil) // Customize the route line color and widthlet lineStyle = MGLLineStyleLayer(identifier: "route-style", source: source)lineStyle.lineColor = NSExpression(forConstantValue: #colorLiteral(red: 0.1897518039, green: 0.3010634184, blue: 0.7994888425, alpha: 1))lineStyle.lineWidth = NSExpression(forConstantValue: 3) // Add the source and style layer of the route line to the mapmapView.style?.addSource(source)mapView.style?.addLayer(lineStyle)}} // Implement the delegate method that allows annotations to show callouts when tappedfunc mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {return true} // Present the navigation view controller when the callout is selectedfunc mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {let navigationViewController = NavigationViewController(for: directionsRoute!)self.present(navigationViewController, animated: true, completion: nil)}}
When you run your app again, you will see the map centered on the user's location just like before. However this time, if you tap and hold anywhere on the map, a new marker will be dropped at the location the user specifies. This point marks the desired final destination for the route. Next, you'll create a method to calculate the route itself.
Generate a route
To generate a route, you'll need to create a new Route
object. When you install the Navigation SDK, it also includes MapboxDirections.swift
. This provides a convenient way to access the Mapbox Directions API in iOS applications, and is used to generate a Route
object that can be used by the Navigation SDK to display turn-by-turn directions.
The Mapbox Directions API requires at least two waypoints to generate a route. You can include up to 25 waypoints in total. Read more about waypoints in the MapboxDirections.swift documentation.
For this project, you'll use two waypoints: an origin and a destination. For this case, the origin will be user's location. The destination will be a point specified by the user when they drop a point on the map. For each waypoint, you'll specify the latitude and longitude of the location and a name that describes the location and add them using the Waypoint
class.
Begin creating a full navigation experience by creating a new calculateRoute
method that contains the following code:
import Mapboximport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirections class ViewController: UIViewController, MGLMapViewDelegate {var mapView: NavigationMapView!var directionsRoute: Route? override func viewDidLoad() {super.viewDidLoad() mapView = NavigationMapView(frame: view.bounds) view.addSubview(mapView) // Set the map view's delegatemapView.delegate = self // Allow the map to display the user's locationmapView.showsUserLocation = truemapView.setUserTrackingMode(.follow, animated: true) // Add a gesture recognizer to the map viewlet longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))mapView.addGestureRecognizer(longPress)} @objc func didLongPress(_ sender: UILongPressGestureRecognizer) {guard sender.state == .began else { return } // Converts point where user did a long press to map coordinateslet point = sender.location(in: mapView)let coordinate = mapView.convert(point, toCoordinateFrom: mapView) // Create a basic point annotation and add it to the maplet annotation = MGLPointAnnotation()annotation.coordinate = coordinateannotation.title = "Start navigation"mapView.addAnnotation(annotation) // Calculate the route from the user's location to the set destinationcalculateRoute(from: (mapView.userLocation!.coordinate), to: annotation.coordinate) { (route, error) inif error != nil {print("Error calculating route")}}} // Calculate route to be used for navigationfunc calculateRoute(from origin: CLLocationCoordinate2D,to destination: CLLocationCoordinate2D,completion: @escaping (Route?, Error?) -> ()) { // Coordinate accuracy is the maximum distance away from the waypoint that the route may still be considered viable, measured in meters. Negative values indicate that a indefinite number of meters away from the route and still be considered viable.let origin = Waypoint(coordinate: origin, coordinateAccuracy: -1, name: "Start")let destination = Waypoint(coordinate: destination, coordinateAccuracy: -1, name: "Finish") // Specify that the route is intended for automobiles avoiding trafficlet options = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic) // Generate the route object and draw it on the map_ = Directions.shared.calculate(options) { [unowned self] (waypoints, routes, error) inself.directionsRoute = routes?.first// Draw the route on the map after creating itself.drawRoute(route: self.directionsRoute!)}} func drawRoute(route: Route) {guard route.coordinateCount > 0 else { return }// Convert the route’s coordinates into a polylinevar routeCoordinates = route.coordinates!let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: route.coordinateCount) // If there's already a route line on the map, reset its shape to the new routeif let source = mapView.style?.source(withIdentifier: "route-source") as? MGLShapeSource {source.shape = polyline} else {let source = MGLShapeSource(identifier: "route-source", features: [polyline], options: nil) // Customize the route line color and widthlet lineStyle = MGLLineStyleLayer(identifier: "route-style", source: source)lineStyle.lineColor = NSExpression(forConstantValue: #colorLiteral(red: 0.1897518039, green: 0.3010634184, blue: 0.7994888425, alpha: 1))lineStyle.lineWidth = NSExpression(forConstantValue: 3) // Add the source and style layer of the route line to the mapmapView.style?.addSource(source)mapView.style?.addLayer(lineStyle)}} // Implement the delegate method that allows annotations to show callouts when tappedfunc mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {return true} // Present the navigation view controller when the callout is selectedfunc mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {let navigationViewController = NavigationViewController(for: directionsRoute!)self.present(navigationViewController, animated: true, completion: nil)}}
Route options
After creating the method that will create your route, set a few options to generate a route from the origin to the destination using the NavigationRouteOptions
class. This class is a subclass of RouteOptions
from MapboxDirections.swift. You can specify any of the same options you could using the RouteOptions
class found in MapboxDirections.swift, but the defaults are better suited to how the Navigation SDK uses the resulting routes. In this example, you'll use the default options, which include high-resolution routes and steps used for turn-by-turn instruction.
Read about all available NavigationRouteOptions
in the Navigation SDK for iOS documentation.
Drawing the route on the map
Now that you've created a method to calculate the route, create another method to draw the route on the map. Start by creating a variable to reference your route object within your entire view controller class. Then, create a method that accepts in a Route
object. Use the incoming Route
object to create a new MGLPolylineFeature
from the route's coordinates. After creating the MGLPolylineFeature
, add it to the map with a corresponding style layer while configuring various style properties such as line color and line width. Create a new method called drawRoute
within your view controller.
MGLPolylineFeature
is one of many available primitive shape objects that can be added to a map. Read more about adding shapes to a map in the Maps SDK for iOS documentation.
Now, allow your user to see the route from their selected destination, modify the calculateRoute
method to call the drawRoute
method within it.
import Mapboximport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirections class ViewController: UIViewController, MGLMapViewDelegate {var mapView: NavigationMapView!var directionsRoute: Route? override func viewDidLoad() {super.viewDidLoad() mapView = NavigationMapView(frame: view.bounds) view.addSubview(mapView) // Set the map view's delegatemapView.delegate = self // Allow the map to display the user's locationmapView.showsUserLocation = truemapView.setUserTrackingMode(.follow, animated: true) // Add a gesture recognizer to the map viewlet longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))mapView.addGestureRecognizer(longPress)} @objc func didLongPress(_ sender: UILongPressGestureRecognizer) {guard sender.state == .began else { return } // Converts point where user did a long press to map coordinateslet point = sender.location(in: mapView)let coordinate = mapView.convert(point, toCoordinateFrom: mapView) // Create a basic point annotation and add it to the maplet annotation = MGLPointAnnotation()annotation.coordinate = coordinateannotation.title = "Start navigation"mapView.addAnnotation(annotation) // Calculate the route from the user's location to the set destinationcalculateRoute(from: (mapView.userLocation!.coordinate), to: annotation.coordinate) { (route, error) inif error != nil {print("Error calculating route")}}} // Calculate route to be used for navigationfunc calculateRoute(from origin: CLLocationCoordinate2D,to destination: CLLocationCoordinate2D,completion: @escaping (Route?, Error?) -> ()) { // Coordinate accuracy is the maximum distance away from the waypoint that the route may still be considered viable, measured in meters. Negative values indicate that a indefinite number of meters away from the route and still be considered viable.let origin = Waypoint(coordinate: origin, coordinateAccuracy: -1, name: "Start")let destination = Waypoint(coordinate: destination, coordinateAccuracy: -1, name: "Finish") // Specify that the route is intended for automobiles avoiding trafficlet options = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic) // Generate the route object and draw it on the map_ = Directions.shared.calculate(options) { [unowned self] (waypoints, routes, error) inself.directionsRoute = routes?.first// Draw the route on the map after creating itself.drawRoute(route: self.directionsRoute!)}} func drawRoute(route: Route) {guard route.coordinateCount > 0 else { return }// Convert the route’s coordinates into a polylinevar routeCoordinates = route.coordinates!let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: route.coordinateCount) // If there's already a route line on the map, reset its shape to the new routeif let source = mapView.style?.source(withIdentifier: "route-source") as? MGLShapeSource {source.shape = polyline} else {let source = MGLShapeSource(identifier: "route-source", features: [polyline], options: nil) // Customize the route line color and widthlet lineStyle = MGLLineStyleLayer(identifier: "route-style", source: source)lineStyle.lineColor = NSExpression(forConstantValue: #colorLiteral(red: 0.1897518039, green: 0.3010634184, blue: 0.7994888425, alpha: 1))lineStyle.lineWidth = NSExpression(forConstantValue: 3) // Add the source and style layer of the route line to the mapmapView.style?.addSource(source)mapView.style?.addLayer(lineStyle)}} // Implement the delegate method that allows annotations to show callouts when tappedfunc mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {return true} // Present the navigation view controller when the callout is selectedfunc mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {let navigationViewController = NavigationViewController(for: directionsRoute!)self.present(navigationViewController, animated: true, completion: nil)}}
Modify the didLongPress
method so that the calculateRoute
method is called within it. This will allow the route to be calculated and drawn when the user performs a long press gesture on the map.
import Mapboximport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirections class ViewController: UIViewController, MGLMapViewDelegate {var mapView: NavigationMapView!var directionsRoute: Route? override func viewDidLoad() {super.viewDidLoad() mapView = NavigationMapView(frame: view.bounds) view.addSubview(mapView) // Set the map view's delegatemapView.delegate = self // Allow the map to display the user's locationmapView.showsUserLocation = truemapView.setUserTrackingMode(.follow, animated: true) // Add a gesture recognizer to the map viewlet longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))mapView.addGestureRecognizer(longPress)} @objc func didLongPress(_ sender: UILongPressGestureRecognizer) {guard sender.state == .began else { return } // Converts point where user did a long press to map coordinateslet point = sender.location(in: mapView)let coordinate = mapView.convert(point, toCoordinateFrom: mapView) // Create a basic point annotation and add it to the maplet annotation = MGLPointAnnotation()annotation.coordinate = coordinateannotation.title = "Start navigation"mapView.addAnnotation(annotation) // Calculate the route from the user's location to the set destinationcalculateRoute(from: (mapView.userLocation!.coordinate), to: annotation.coordinate) { (route, error) inif error != nil {print("Error calculating route")}}} // Calculate route to be used for navigationfunc calculateRoute(from origin: CLLocationCoordinate2D,to destination: CLLocationCoordinate2D,completion: @escaping (Route?, Error?) -> ()) { // Coordinate accuracy is the maximum distance away from the waypoint that the route may still be considered viable, measured in meters. Negative values indicate that a indefinite number of meters away from the route and still be considered viable.let origin = Waypoint(coordinate: origin, coordinateAccuracy: -1, name: "Start")let destination = Waypoint(coordinate: destination, coordinateAccuracy: -1, name: "Finish") // Specify that the route is intended for automobiles avoiding trafficlet options = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic) // Generate the route object and draw it on the map_ = Directions.shared.calculate(options) { [unowned self] (waypoints, routes, error) inself.directionsRoute = routes?.first// Draw the route on the map after creating itself.drawRoute(route: self.directionsRoute!)}} func drawRoute(route: Route) {guard route.coordinateCount > 0 else { return }// Convert the route’s coordinates into a polylinevar routeCoordinates = route.coordinates!let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: route.coordinateCount) // If there's already a route line on the map, reset its shape to the new routeif let source = mapView.style?.source(withIdentifier: "route-source") as? MGLShapeSource {source.shape = polyline} else {let source = MGLShapeSource(identifier: "route-source", features: [polyline], options: nil) // Customize the route line color and widthlet lineStyle = MGLLineStyleLayer(identifier: "route-style", source: source)lineStyle.lineColor = NSExpression(forConstantValue: #colorLiteral(red: 0.1897518039, green: 0.3010634184, blue: 0.7994888425, alpha: 1))lineStyle.lineWidth = NSExpression(forConstantValue: 3) // Add the source and style layer of the route line to the mapmapView.style?.addSource(source)mapView.style?.addLayer(lineStyle)}} // Implement the delegate method that allows annotations to show callouts when tappedfunc mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {return true} // Present the navigation view controller when the callout is selectedfunc mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {let navigationViewController = NavigationViewController(for: directionsRoute!)self.present(navigationViewController, animated: true, completion: nil)}}
Run your application, and perform a long press on the map to set your destination. When the marker is added to the map, the route is calculated and drawn on the map.

Starting navigation
To start the turn-by-turn navigation sequence, allow the marker to show a callout and allow that callout to be selected by implementing the annotationCanShowCallout
and tapOnCalloutFor
delegate methods. When the callout is tapped, present a new NavigationViewController
using the route that was generated. Add these two new methods within your view controller.
import Mapboximport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirections class ViewController: UIViewController, MGLMapViewDelegate {var mapView: NavigationMapView!var directionsRoute: Route? override func viewDidLoad() {super.viewDidLoad() mapView = NavigationMapView(frame: view.bounds) view.addSubview(mapView) // Set the map view's delegatemapView.delegate = self // Allow the map to display the user's locationmapView.showsUserLocation = truemapView.setUserTrackingMode(.follow, animated: true) // Add a gesture recognizer to the map viewlet longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))mapView.addGestureRecognizer(longPress)} @objc func didLongPress(_ sender: UILongPressGestureRecognizer) {guard sender.state == .began else { return } // Converts point where user did a long press to map coordinateslet point = sender.location(in: mapView)let coordinate = mapView.convert(point, toCoordinateFrom: mapView) // Create a basic point annotation and add it to the maplet annotation = MGLPointAnnotation()annotation.coordinate = coordinateannotation.title = "Start navigation"mapView.addAnnotation(annotation) // Calculate the route from the user's location to the set destinationcalculateRoute(from: (mapView.userLocation!.coordinate), to: annotation.coordinate) { (route, error) inif error != nil {print("Error calculating route")}}} // Calculate route to be used for navigationfunc calculateRoute(from origin: CLLocationCoordinate2D,to destination: CLLocationCoordinate2D,completion: @escaping (Route?, Error?) -> ()) { // Coordinate accuracy is the maximum distance away from the waypoint that the route may still be considered viable, measured in meters. Negative values indicate that a indefinite number of meters away from the route and still be considered viable.let origin = Waypoint(coordinate: origin, coordinateAccuracy: -1, name: "Start")let destination = Waypoint(coordinate: destination, coordinateAccuracy: -1, name: "Finish") // Specify that the route is intended for automobiles avoiding trafficlet options = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic) // Generate the route object and draw it on the map_ = Directions.shared.calculate(options) { [unowned self] (waypoints, routes, error) inself.directionsRoute = routes?.first// Draw the route on the map after creating itself.drawRoute(route: self.directionsRoute!)}} func drawRoute(route: Route) {guard route.coordinateCount > 0 else { return }// Convert the route’s coordinates into a polylinevar routeCoordinates = route.coordinates!let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: route.coordinateCount) // If there's already a route line on the map, reset its shape to the new routeif let source = mapView.style?.source(withIdentifier: "route-source") as? MGLShapeSource {source.shape = polyline} else {let source = MGLShapeSource(identifier: "route-source", features: [polyline], options: nil) // Customize the route line color and widthlet lineStyle = MGLLineStyleLayer(identifier: "route-style", source: source)lineStyle.lineColor = NSExpression(forConstantValue: #colorLiteral(red: 0.1897518039, green: 0.3010634184, blue: 0.7994888425, alpha: 1))lineStyle.lineWidth = NSExpression(forConstantValue: 3) // Add the source and style layer of the route line to the mapmapView.style?.addSource(source)mapView.style?.addLayer(lineStyle)}} // Implement the delegate method that allows annotations to show callouts when tappedfunc mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {return true} // Present the navigation view controller when the callout is selectedfunc mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {let navigationViewController = NavigationViewController(for: directionsRoute!)self.present(navigationViewController, animated: true, completion: nil)}}
Finished product
Run the application and then select and hold on the map to add an annotation. Select the annotation to display a callout, and then select the callout to initialize navigation sequence between the origin and your specified destination. Congratulations! You just built a small navigation app with the Mapbox Navigation SDK for iOS. The full code for this tutorial can be found here.

import Mapboximport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirections class ViewController: UIViewController, MGLMapViewDelegate {var mapView: NavigationMapView!var directionsRoute: Route? override func viewDidLoad() {super.viewDidLoad() mapView = NavigationMapView(frame: view.bounds) view.addSubview(mapView) // Set the map view's delegatemapView.delegate = self // Allow the map to display the user's locationmapView.showsUserLocation = truemapView.setUserTrackingMode(.follow, animated: true) // Add a gesture recognizer to the map viewlet longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))mapView.addGestureRecognizer(longPress)} @objc func didLongPress(_ sender: UILongPressGestureRecognizer) {guard sender.state == .began else { return } // Converts point where user did a long press to map coordinateslet point = sender.location(in: mapView)let coordinate = mapView.convert(point, toCoordinateFrom: mapView) // Create a basic point annotation and add it to the maplet annotation = MGLPointAnnotation()annotation.coordinate = coordinateannotation.title = "Start navigation"mapView.addAnnotation(annotation) // Calculate the route from the user's location to the set destinationcalculateRoute(from: (mapView.userLocation!.coordinate), to: annotation.coordinate) { (route, error) inif error != nil {print("Error calculating route")}}} // Calculate route to be used for navigationfunc calculateRoute(from origin: CLLocationCoordinate2D,to destination: CLLocationCoordinate2D,completion: @escaping (Route?, Error?) -> ()) { // Coordinate accuracy is the maximum distance away from the waypoint that the route may still be considered viable, measured in meters. Negative values indicate that a indefinite number of meters away from the route and still be considered viable.let origin = Waypoint(coordinate: origin, coordinateAccuracy: -1, name: "Start")let destination = Waypoint(coordinate: destination, coordinateAccuracy: -1, name: "Finish") // Specify that the route is intended for automobiles avoiding trafficlet options = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic) // Generate the route object and draw it on the map_ = Directions.shared.calculate(options) { [unowned self] (waypoints, routes, error) inself.directionsRoute = routes?.first// Draw the route on the map after creating itself.drawRoute(route: self.directionsRoute!)}} func drawRoute(route: Route) {guard route.coordinateCount > 0 else { return }// Convert the route’s coordinates into a polylinevar routeCoordinates = route.coordinates!let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: route.coordinateCount) // If there's already a route line on the map, reset its shape to the new routeif let source = mapView.style?.source(withIdentifier: "route-source") as? MGLShapeSource {source.shape = polyline} else {let source = MGLShapeSource(identifier: "route-source", features: [polyline], options: nil) // Customize the route line color and widthlet lineStyle = MGLLineStyleLayer(identifier: "route-style", source: source)lineStyle.lineColor = NSExpression(forConstantValue: #colorLiteral(red: 0.1897518039, green: 0.3010634184, blue: 0.7994888425, alpha: 1))lineStyle.lineWidth = NSExpression(forConstantValue: 3) // Add the source and style layer of the route line to the mapmapView.style?.addSource(source)mapView.style?.addLayer(lineStyle)}} // Implement the delegate method that allows annotations to show callouts when tappedfunc mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {return true} // Present the navigation view controller when the callout is selectedfunc mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {let navigationViewController = NavigationViewController(for: directionsRoute!)self.present(navigationViewController, animated: true, completion: nil)}}
Next steps
There are many other ways you can customize the Mapbox Navigation SDK for iOS beyond what you've done in this tutorial. For a complete reference of customization options see the Navigation SDK for iOS documentation. Options include:
- Walking and cycling modes.
- Delivering voice instructions using third-party speech synthesizer.
- Building a custom turn-by-turn experience using Core Navigation.