Build a navigation app for iOS
Familiarity with Xcode, Swift, and CocoaPods or Carthage.
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, begin 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 10.0 and above. It can be used with code written in Swift 5 and above. 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, complete the First steps with the Mapbox Maps SDK for iOS guide to set up a map view first.
Install the Navigation SDK for iOS
Install the framework
You'll need to add MapboxNavigation
to your build to use the Mapbox Navigation SDK, map services, and directions services.
CocoaPods:
pod 'MapboxNavigation', '~> 0.40.0'
Carthage:
github "mapbox/mapbox-navigation-ios" ~> 0.40.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 MapboxDirectionsshow hidden lines
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 routeOptions
and route
variables 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.
show hidden linesclass ViewController: UIViewController, MGLMapViewDelegate {var mapView: NavigationMapView!var routeOptions: NavigationRouteOptions?var route: Route? override func viewDidLoad() {super.viewDidLoad() mapView = NavigationMapView(frame: view.bounds) view.addSubview(mapView) // Set the map view's delegatemapView.delegate = selfshow hidden lines}show hidden lines}
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, explore 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 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 NSLocationWhenInUseUsageDescription
key in the Info.plist file. We recommend setting the value to the following string, which will become the application's location usage description: Shows your location on the map and helps improve the map
. 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.
Additionally, you may also choose to include the NSLocationAlwaysAndWhenInUseUsageDescription
key within your Info.plist file. We recommend providing a different string when using this key to help your users decide which level of permission they wish to grant to your application. For more information about the differences in these permission levels, see Apple’s Choosing the Location Services Authorization to Request document.
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. Update the code within the viewDidLoad
method in your view controller to show the user's location and set the tracking mode.
show hidden lines// Allow the map to display the user's locationmapView.showsUserLocation = truemapView.setUserTrackingMode(.follow, animated: true, completionHandler: nil)show hidden lines
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 (Xcode 11.3 and prior versions). On Simulator shipped with Xcode 11.4 and higher select Features ‣ Location ‣ Custom Location. Enter 37.773
for latitude, -122.411
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
. Add a new didLongPress(_:)
method within your view controller as shown below.
show hidden lines@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) if let origin = mapView.userLocation?.coordinate {// Calculate the route from the user's location to the set destinationcalculateRoute(from: origin, to: coordinate)} else {print("Failed to get user location, make sure to allow location access for this application.")}}show hidden lines
Later on, you will add another method to calculate the route from the origin to your destination within this method.
Update the viewDidLoad
method to add a UILongPressGestureRecognizer
to your NavigationMapView
so it will accept the newly created gesture recognizer.
show hidden lines// Add a gesture recognizer to the map viewlet longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))mapView.addGestureRecognizer(longPress)show hidden lines
When you run your app again, you will see the map centered on the user's location. But 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 Mapbox Directions for Swift. This library provides a convenient way to access the Mapbox Directions API in iOS applications. It will generate a Route
object that the Navigation SDK will use to display turn-by-turn directions.
The Mapbox Directions API requires at least two waypoints to generate a route. There is a limit to the number of waypoints you can include, depending on the profile you specify. Read more about waypoints in the Mapbox Directions for 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.
To mark the destination on the map, call the NavigationMapView.showWaypoints(on:legIndex:)
method. This method adds an MGLPointAnnotation
. You can change the title by setting its title
property; this title will appear in a callout that appears when the user taps the annotation.
Begin creating a full navigation experience by creating a new calculateRoute(from:to:)
method that contains the following code:
show hidden lines// Calculate route to be used for navigationfunc calculateRoute(from origin: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D) {// Coordinate accuracy is how close the route must come to the waypoint in order to be considered viable. It is measured in meters. A negative value indicates that the route is viable regardless of how far the route is from the waypoint.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 routeOptions = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic) // Generate the route object and draw it on the mapDirections.shared.calculate(routeOptions) { [weak self] (session, result) inswitch result {case .failure(let error):print(error.localizedDescription)case .success(let response):guard let route = response.routes?.first, let strongSelf = self else {return} strongSelf.route = routestrongSelf.routeOptions = routeOptions // Draw the route on the map after creating itstrongSelf.drawRoute(route: route) // Show destination waypoint on the mapstrongSelf.mapView.showWaypoints(on: route) // Display callout view on destination annotationif let annotation = strongSelf.mapView.annotations?.first as? MGLPointAnnotation {annotation.title = "Start navigation"strongSelf.mapView.selectAnnotation(annotation, animated: true, completionHandler: nil)}}}}show hidden lines
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 Mapbox Directions for Swift. You can specify any of the same options you could using the RouteOptions
class found in Mapbox Directions for 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(route:)
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, change the calculateRoute(from:to:)
method to call the drawRoute(route:)
and showWaypoints(on:legIndex:)
methods within it.
show hidden lines// Draw the route on the map after creating itstrongSelf.drawRoute(route: route)show hidden linesfunc drawRoute(route: Route) {guard let routeShape = route.shape, routeShape.coordinates.count > 0 else { return }// Convert the route’s coordinates into a polylinevar routeCoordinates = routeShape.coordinateslet polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: UInt(routeCoordinates.count)) // 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)}}show hidden lines
Update the didLongPress(_:)
method so that the calculateRoute(from:to:)
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.
show hidden lines// Calculate the route from the user's location to the set destinationcalculateRoute(from: origin, to: coordinate)show hidden lines
Run your application, and do 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 mapView(_:annotationCanShowCallout:)
and mapView(_: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.
show hidden lines// 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) {guard let route = route, let routeOptions = routeOptions else {return}let navigationViewController = NavigationViewController(for: route, routeOptions: routeOptions)navigationViewController.modalPresentationStyle = .fullScreenself.present(navigationViewController, animated: true, completion: nil)}show hidden lines
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. You built a small navigation app with the Mapbox Navigation SDK for iOS. You can find the full code for this tutorial on GitHub.
import Mapboximport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirections class ViewController: UIViewController, MGLMapViewDelegate {var mapView: NavigationMapView!var routeOptions: NavigationRouteOptions?var route: 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, completionHandler: nil) // 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) if let origin = mapView.userLocation?.coordinate {// Calculate the route from the user's location to the set destinationcalculateRoute(from: origin, to: coordinate)} else {print("Failed to get user location, make sure to allow location access for this application.")}} // Calculate route to be used for navigationfunc calculateRoute(from origin: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D) {// Coordinate accuracy is how close the route must come to the waypoint in order to be considered viable. It is measured in meters. A negative value indicates that the route is viable regardless of how far the route is from the waypoint.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 routeOptions = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic) // Generate the route object and draw it on the mapDirections.shared.calculate(routeOptions) { [weak self] (session, result) inswitch result {case .failure(let error):print(error.localizedDescription)case .success(let response):guard let route = response.routes?.first, let strongSelf = self else {return} strongSelf.route = routestrongSelf.routeOptions = routeOptions // Draw the route on the map after creating itstrongSelf.drawRoute(route: route) // Show destination waypoint on the mapstrongSelf.mapView.showWaypoints(on: route) // Display callout view on destination annotationif let annotation = strongSelf.mapView.annotations?.first as? MGLPointAnnotation {annotation.title = "Start navigation"strongSelf.mapView.selectAnnotation(annotation, animated: true, completionHandler: nil)}}}} func drawRoute(route: Route) {guard let routeShape = route.shape, routeShape.coordinates.count > 0 else { return }// Convert the route’s coordinates into a polylinevar routeCoordinates = routeShape.coordinateslet polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: UInt(routeCoordinates.count)) // 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) {guard let route = route, let routeOptions = routeOptions else {return}let navigationViewController = NavigationViewController(for: route, routeOptions: routeOptions)navigationViewController.modalPresentationStyle = .fullScreenself.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.