Build a navigation app for iOS

advanced
Swift
Prerequisite

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.

an animated GIF of a navigation iOS app with turn-by-turn instruction

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:

Install the Navigation SDK for iOS

You can install the Mapbox Navigation SDK for iOS using either CocoaPods or Carthage. If you are new to CocoaPods or Carthage, you can get started with documentation for CocoaPods and Carthage.

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 Mapbox
import MapboxCoreNavigation
import MapboxNavigation
import MapboxDirections
show 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 lines
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 delegate
mapView.delegate = self
show hidden lines
}
show hidden lines
}

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 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 location
mapView.showsUserLocation = true
mapView.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.

a map displaying a user's location on an iOS device

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 coordinates
let 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 destination
calculateRoute(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 view
let 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 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.route = route
strongSelf.routeOptions = routeOptions
// Draw the route on the map after creating it
strongSelf.drawRoute(route: route)
// Show destination waypoint on the map
strongSelf.mapView.showWaypoints(on: route)
// Display callout view on destination annotation
if 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.

NavigationRouteOptions

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.

Note

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 it
strongSelf.drawRoute(route: route)
show hidden lines
func drawRoute(route: Route) {
guard let routeShape = route.shape, routeShape.coordinates.count > 0 else { return }
// Convert the route’s coordinates into a polyline
var routeCoordinates = routeShape.coordinates
let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: UInt(routeCoordinates.count))
// If there's already a route line on the map, reset its shape to the new route
if 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 width
let 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 map
mapView.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 destination
calculateRoute(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.

drawing a navigation route in an iOS app

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 tapped
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
// Present the navigation view controller when the callout is selected
func mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {
guard let route = route, let routeOptions = routeOptions else {
return
}
let navigationViewController = NavigationViewController(for: route, routeOptions: routeOptions)
navigationViewController.modalPresentationStyle = .fullScreen
self.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.

an animated GIF of a navigation iOS app with turn-by-turn instruction
import Mapbox
import MapboxCoreNavigation
import MapboxNavigation
import 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 delegate
mapView.delegate = self
// Allow the map to display the user's location
mapView.showsUserLocation = true
mapView.setUserTrackingMode(.follow, animated: true, completionHandler: nil)
// Add a gesture recognizer to the map view
let 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 coordinates
let 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 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.route = route
strongSelf.routeOptions = routeOptions
// Draw the route on the map after creating it
strongSelf.drawRoute(route: route)
// Show destination waypoint on the map
strongSelf.mapView.showWaypoints(on: route)
// Display callout view on destination annotation
if 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 polyline
var routeCoordinates = routeShape.coordinates
let polyline = MGLPolylineFeature(coordinates: &routeCoordinates, count: UInt(routeCoordinates.count))
// If there's already a route line on the map, reset its shape to the new route
if 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 width
let 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 map
mapView.style?.addSource(source)
mapView.style?.addLayer(lineStyle)
}
}
// Implement the delegate method that allows annotations to show callouts when tapped
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
// Present the navigation view controller when the callout is selected
func mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {
guard let route = route, let routeOptions = routeOptions else {
return
}
let navigationViewController = NavigationViewController(for: route, 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:

Was this page helpful?