All docsHelpTutorialsBuild a navigation app for iOS

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, 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 11.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:

Install the Navigation SDK for iOS

You can install the Mapbox Navigation SDK for iOS using either CocoaPods or Swift Package Manager ('SPM'). If you are new to CocoaPods or 'SPM', you can get started with documentation for CocoaPods and 'SPM'.

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', '~> 1.4.2'

'SPM':

.package(name: "MapboxNavigation", url: "https://github.com/mapbox/mapbox-navigation-ios.git", from: "1.4.2")

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 MapboxMaps
import MapboxCoreNavigation
import MapboxNavigation
import MapboxDirections

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 UIView that provides additional functionality (like displaying a route on the map) that is helpful for navigation apps specifically by including MapView on top of the display, and allowing access to it via public NavigationMapView.mapView getter. The routeOptions and routeResponse 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.

class ViewController: UIViewController, AnnotationInteractionDelegate {
var navigationMapView: NavigationMapView!
var routeOptions: NavigationRouteOptions?
var routeResponse: RouteResponse?
override func viewDidLoad() {
super.viewDidLoad()
navigationMapView = NavigationMapView(frame: view.bounds)
view.addSubview(navigationMapView)
}
}

Run your application, and you will see a new map.

a map in an iOS application
More resources

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.

Note

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. NavigationMapView is configured to fetch and display it by default. To configure the visual representation, set NavigationMapView.userLocationStyle; to allow camera tracking position changes, override default NavigationCamera.viewportDataSource to enable tracking raw location.

// Configure how map displays the user's location
navigationMapView.userLocationStyle = .puck2D()
// Switch viewport datasource to track `raw` location updates instead of `passive` mode.
navigationMapView.navigationCamera.viewportDataSource = NavigationViewportDataSource(navigationMapView.mapView, viewportDataSourceType: .raw)

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 while using the app. 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.

@objc func didLongPress(_ sender: UILongPressGestureRecognizer) {
guard sender.state == .began else { return }
// Converts point where user did a long press to map coordinates
let point = sender.location(in: navigationMapView)
let coordinate = navigationMapView.mapView.mapboxMap.coordinate(for: point)
if let origin = navigationMapView.mapView.location.latestLocation?.coordinate {
// Calculate the route from the user's location to the set destination
calculateRoute(from: origin, to: coordinate)
} else {
print("Failed to get user location, make sure to allow location access for this application.")
}
}

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.

// Add a gesture recognizer to the map view
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))
navigationMapView.addGestureRecognizer(longPress)

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 PointAnnotation. You can change the title by setting its textField property.

Begin creating a full navigation experience by creating a new calculateRoute(from:to:) method that contains the following code:

// Calculate route to be used for navigation
func 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 traffic
let routeOptions = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic)
// Generate the route object and draw it on the map
Directions.shared.calculate(routeOptions) { [weak self] (session, result) in
switch result {
case .failure(let error):
print(error.localizedDescription)
case .success(let response):
guard let route = response.routes?.first, let strongSelf = self else {
return
}
strongSelf.routeResponse = response
strongSelf.routeOptions = routeOptions
// Draw the route on the map after creating it
strongSelf.drawRoute(route: route)
if var annotation = strongSelf.navigationMapView.pointAnnotationManager?.annotations.first {
// Display callout view on destination annotation
annotation.textField = "Start navigation"
annotation.textColor = .init(color: UIColor.white)
annotation.textHaloColor = .init(color: UIColor.systemBlue)
annotation.textHaloWidth = 2
annotation.textAnchor = .top
annotation.textRadialOffset = 1.0
strongSelf.beginAnnotation = annotation
strongSelf.navigationMapView.pointAnnotationManager?.syncAnnotations([annotation])
}
}
}
}
func drawRoute(route: Route) {
navigationMapView.show([route])
navigationMapView.showRouteDurations(along: [route])
// Show destination waypoint on the map
navigationMapView.showWaypoints(on: route)
}

Note the drawRoute(route:) function above. It does step-by-step displaying of various components on the NavigationMapView. This way example creates a preview of the route line with marking destination waypoint and drawing annotation with route duration information. Alternatively, you could call NavigationMapView.showcase(_:, animated:) to let the SDK handle route displaying and camera updates. This method does not show route duration annotations though.

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.

NavigationRouteOptions

Read about all available NavigationRouteOptions in the Navigation SDK for iOS documentation.

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, set PointAnnotationManager.delegate in viewDidLoad() and implement the AnnotationInteractionDelegate.annotationManager(_:didDetectTappedAnnotations:) method to receive tap events. When the annotation is tapped, present a new NavigationViewController using the route that was generated. Add this new method within your view controller.

// Set the annotation manager's delegate
navigationMapView.mapView.mapboxMap.onNext(.mapLoaded) { [weak self] _ in
guard let self = self else { return }
self.navigationMapView.pointAnnotationManager?.delegate = self
}
// Present the navigation view controller when the annotation is selected
func annotationManager(_ manager: AnnotationManager, didDetectTappedAnnotations annotations: [Annotation]) {
guard annotations.first?.id == beginAnnotation?.id,
let routeResponse = routeResponse, let routeOptions = routeOptions else {
return
}
let navigationViewController = NavigationViewController(for: routeResponse, routeIndex: 0, routeOptions: routeOptions)
navigationViewController.modalPresentationStyle = .fullScreen
self.present(navigationViewController, animated: true, completion: nil)
}

Finished product

Run the application and then select and hold on the map to add an annotation. Then tap the annotation 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 MapboxMaps
import MapboxCoreNavigation
import MapboxNavigation
import MapboxDirections
class ViewController: UIViewController, AnnotationInteractionDelegate {
var navigationMapView: NavigationMapView!
var routeOptions: NavigationRouteOptions?
var routeResponse: RouteResponse?
var beginAnnotation: PointAnnotation?
override func viewDidLoad() {
super.viewDidLoad()
navigationMapView = NavigationMapView(frame: view.bounds)
view.addSubview(navigationMapView)
// Set the annotation manager's delegate
navigationMapView.mapView.mapboxMap.onNext(.mapLoaded) { [weak self] _ in
guard let self = self else { return }
self.navigationMapView.pointAnnotationManager?.delegate = self
}
// Configure how map displays the user's location
navigationMapView.userLocationStyle = .puck2D()
// Switch viewport datasource to track `raw` location updates instead of `passive` mode.
navigationMapView.navigationCamera.viewportDataSource = NavigationViewportDataSource(navigationMapView.mapView, viewportDataSourceType: .raw)
// Add a gesture recognizer to the map view
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))
navigationMapView.addGestureRecognizer(longPress)
}
@objc func didLongPress(_ sender: UILongPressGestureRecognizer) {
guard sender.state == .began else { return }
// Converts point where user did a long press to map coordinates
let point = sender.location(in: navigationMapView)
let coordinate = navigationMapView.mapView.mapboxMap.coordinate(for: point)
if let origin = navigationMapView.mapView.location.latestLocation?.coordinate {
// Calculate the route from the user's location to the set destination
calculateRoute(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 navigation
func 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 traffic
let routeOptions = NavigationRouteOptions(waypoints: [origin, destination], profileIdentifier: .automobileAvoidingTraffic)
// Generate the route object and draw it on the map
Directions.shared.calculate(routeOptions) { [weak self] (session, result) in
switch result {
case .failure(let error):
print(error.localizedDescription)
case .success(let response):
guard let route = response.routes?.first, let strongSelf = self else {
return
}
strongSelf.routeResponse = response
strongSelf.routeOptions = routeOptions
// Draw the route on the map after creating it
strongSelf.drawRoute(route: route)
if var annotation = strongSelf.navigationMapView.pointAnnotationManager?.annotations.first {
// Display callout view on destination annotation
annotation.textField = "Start navigation"
annotation.textColor = .init(color: UIColor.white)
annotation.textHaloColor = .init(color: UIColor.systemBlue)
annotation.textHaloWidth = 2
annotation.textAnchor = .top
annotation.textRadialOffset = 1.0
strongSelf.beginAnnotation = annotation
strongSelf.navigationMapView.pointAnnotationManager?.syncAnnotations([annotation])
}
}
}
}
func drawRoute(route: Route) {
navigationMapView.show([route])
navigationMapView.showRouteDurations(along: [route])
// Show destination waypoint on the map
navigationMapView.showWaypoints(on: route)
}
// Present the navigation view controller when the annotation is selected
func annotationManager(_ manager: AnnotationManager, didDetectTappedAnnotations annotations: [Annotation]) {
guard annotations.first?.id == beginAnnotation?.id,
let routeResponse = routeResponse, let routeOptions = routeOptions else {
return
}
let navigationViewController = NavigationViewController(for: routeResponse, routeIndex: 0, routeOptions: routeOptions)
navigationViewController.modalPresentationStyle = .fullScreen
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: