advanced
Swift or Objective-C
Build a navigation app for iOS
Prerequisite
Familiarity with Xcode, Swift or Objective-C, 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, initiate 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 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:

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.

Update package manager

You’ll need to add MapboxNavigation to your build in order to use the Mapbox Navigation SDK, map services, and directions services.

pod 'MapboxNavigation', '~> 0.13.1'
github "mapbox/mapbox-navigation-ios" ~> 0.13.1

Import dependencies

After you’ve added this to your build, you’ll need to import the following classes into your ViewController to access them in your application.


import Mapbox
import MapboxCoreNavigation
import MapboxNavigation
import MapboxDirections


#import "NavigationTutorialViewController.h"

@import Mapbox;
@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 subclass of MGLMapView, which 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.

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 delegate
        mapView.delegate = self
    }
    
}
@interface ViewController () <MGLMapViewDelegate>

@property (nonatomic) MBNavigationMapView *mapView;
@property (nonatomic) MBRoute *directionsRoute;

@end

@implementation ViewController

- (void)viewDidLoad {
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    

        self.mapView = [[MBNavigationMapView alloc] initWithFrame:self.view.bounds];

        [self.view addSubview:self.mapView];
        // Set the map view's delegate
        self.mapView.delegate = self;
    }
    
}

@end

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

a simple map in an iOS application

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.

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.

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 delegate
        mapView.delegate = self
    
        // Allow the map to display the user's location
        mapView.showsUserLocation = true
        mapView.setUserTrackingMode(.follow, animated: true)
    }
    
}
@interface ViewController () <MGLMapViewDelegate>

@property (nonatomic) MBNavigationMapView *mapView;
@property (nonatomic) MBRoute *directionsRoute;

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];

    self.mapView = [[MBNavigationMapView alloc] initWithFrame:self.view.bounds];

    [self.view addSubview:self.mapView];
    // Set the map view's delegate
    self.mapView.delegate = self;

    // Allow the map view to display the user's location
    self.mapView.showsUserLocation = YES;
    [self.mapView setUserTrackingMode:MGLUserTrackingModeFollow animated:YES];
}


@end

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.

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 function called didLongPress. Within this function, 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 function within your view controller as shown below.

class ViewController: UIViewController, MGLMapViewDelegate {
    
    @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)
    
        // Create a basic point annotation and add it to the map
        let annotation = MGLPointAnnotation()
        annotation.coordinate = coordinate
        annotation.title = "Start navigation"
        mapView.addAnnotation(annotation)
    }
    
}

-(void)didLongPress:(UITapGestureRecognizer *)sender {
    if (sender.state != UIGestureRecognizerStateEnded) {
        return;
    }
    
    // Converts point where user did a long press to map coordinates
    CGPoint point = [sender locationInView:self.mapView];
    CLLocationCoordinate2D coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
    
    // Create a basic point annotation and add it to the map
    MGLPointAnnotation *annotation = [MGLPointAnnotation alloc];
    annotation.coordinate = coordinate;
    annotation.title = @"Start navigtation";
    [self.mapView addAnnotation:annotation];
}

@end

Later on, you will add another function to calculate the route from the origin to your destination within this function.

Modify the viewDidLoad function to add a UILongPressGestureRecognizer to your NavigationMapView so it will accept the newly created gesture recognizer.

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 delegate
        mapView.delegate = self
    
        // Allow the map to display the user's location
        mapView.showsUserLocation = true
        mapView.setUserTrackingMode(.follow, animated: true)
    
        // Add a gesture recognizer to the map view
        let setDestination = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(_:)))
        mapView.addGestureRecognizer(setDestination)
    }
    
}
@interface ViewController () <MGLMapViewDelegate>

@property (nonatomic) MBNavigationMapView *mapView;
@property (nonatomic) MBRoute *directionsRoute;

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];


    self.mapView = [[MBNavigationMapView alloc] initWithFrame:self.view.bounds];

    [self.view addSubview:self.mapView];
    // Set the map view's delegate
    self.mapView.delegate = self;

    // Allow the map view to display the user's location
    self.mapView.showsUserLocation = YES;
    [self.mapView setUserTrackingMode:MGLUserTrackingModeFollow animated:YES];

    // Add a gesture recognizer to the map view
    UILongPressGestureRecognizer *setDestination = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(didLongPress:)];
    [self.mapView addGestureRecognizer:setDestination];
}


@end

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 function 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 function that contains the following code:

class ViewController: UIViewController, MGLMapViewDelegate {
  
  // Calculate route to be used for navigation
  func 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 traffic
      let 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) in
          self.directionsRoute = routes?.first
      }
  }
  
}

-(void)calculateRoutefromOrigin:(CLLocationCoordinate2D)origin
                  toDestination:(CLLocationCoordinate2D)destination
                     completion:(void(^)(MBRoute *_Nullable route, NSError *_Nullable error))completion {
    
    // 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.
    MBWaypoint *originWaypoint = [[MBWaypoint alloc] initWithCoordinate:origin coordinateAccuracy:-1 name:@"Start"];
    
    MBWaypoint *destinationWaypoint = [[MBWaypoint alloc] initWithCoordinate:destination coordinateAccuracy:-1 name:@"Finish"];
    
    // Specify that the route is intended for automobiles avoiding traffic
    MBNavigationRouteOptions *options = [[MBNavigationRouteOptions alloc] initWithWaypoints:@[originWaypoint, destinationWaypoint] profileIdentifier:MBDirectionsProfileIdentifierAutomobileAvoidingTraffic];
    
    // Generate the route object and draw it on the map
    (void)[[MBDirections sharedDirections] calculateDirectionsWithOptions:options completionHandler:^(
        NSArray<MBWaypoint *> *waypoints,
        NSArray<MBRoute *> *routes,
        NSError *error) {
        
        if (!routes.firstObject) {
            return;
        }
        
        MBRoute *route = routes.firstObject;
        self.directionsRoute = route;
        CLLocationCoordinate2D *routeCoordinates = malloc(route.coordinateCount * sizeof(CLLocationCoordinate2D));
        [route getCoordinates:routeCoordinates];
    }];
}

Route options

After creating the function 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.

Drawing the route on the map

Now that you’ve created a function to calculate the route, create another function 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 function 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 function called drawRoute within your view controller.

class ViewController: UIViewController, MGLMapViewDelegate {
    
    var mapView: NavigationMapView!
    var directionsRoute: Route?
    
    
    func drawRoute(route: Route) {
        guard route.coordinateCount > 0 else { return }
        // Convert the route’s coordinates into a polyline
        var 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 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 = MGLStyleValue(rawValue: #colorLiteral(red: 0.1897518039, green: 0.3010634184, blue: 0.7994888425, alpha: 1))
            lineStyle.lineWidth = MGLStyleValue(rawValue: 3)
        
            // Add the source and style layer of the route line to the map
            mapView.style?.addSource(source)
            mapView.style?.addLayer(lineStyle)
        }
    }
    
}

-(void)drawRoute:(CLLocationCoordinate2D *)route {
    if (self.directionsRoute.coordinateCount == 0) {
        return;
    }

    // Convert the route’s coordinates into a polyline.
    MGLPolylineFeature *polyline = [MGLPolylineFeature polylineWithCoordinates:route count:self.directionsRoute.coordinateCount];

    if ([self.mapView.style sourceWithIdentifier:@"route-source"]) {
        // If there's already a route line on the map, reset its shape to the new route
        MGLShapeSource *source = [self.mapView.style sourceWithIdentifier:@"route-source"];
        source.shape = polyline;
    } else {
        MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"route-source" shape:polyline options:nil];
        MGLLineStyleLayer *lineStyle = [[MGLLineStyleLayer alloc] initWithIdentifier:@"route-style" source:source];
    
        // Customize the route line color and width
        lineStyle.lineColor = [MGLStyleValue valueWithRawValue:[UIColor blueColor]];
        lineStyle.lineWidth = [MGLStyleValue valueWithRawValue:@"3"];
    
        // Add the source and style layer of the route line to the map
        [self.mapView.style addSource:source];
        [self.mapView.style addLayer:lineStyle];
    }
}

Now, allow your user to see the route from their selected destination, modify the calculateRoute function to call the drawRoute within it.

class ViewController: UIViewController, MGLMapViewDelegate {
  
  // Calculate route to be used for navigation
  func 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 traffic
      let 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) in
          self.directionsRoute = routes?.first
          // Draw the route on the map after creating it
          self.drawRoute(route: self.directionsRoute!)
      }
  }
  
}

-(void)calculateRoutefromOrigin:(CLLocationCoordinate2D)origin
                  toDestination:(CLLocationCoordinate2D)destination
                     completion:(void(^)(MBRoute *_Nullable route, NSError *_Nullable error))completion {
    
    // 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.
    MBWaypoint *originWaypoint = [[MBWaypoint alloc] initWithCoordinate:origin coordinateAccuracy:-1 name:@"Start"];
    
    MBWaypoint *destinationWaypoint = [[MBWaypoint alloc] initWithCoordinate:destination coordinateAccuracy:-1 name:@"Finish"];
    
    // Specify that the route is intended for automobiles avoiding traffic
    MBNavigationRouteOptions *options = [[MBNavigationRouteOptions alloc] initWithWaypoints:@[originWaypoint, destinationWaypoint] profileIdentifier:MBDirectionsProfileIdentifierAutomobileAvoidingTraffic];
    
    // Generate the route object and draw it on the map
    (void)[[MBDirections sharedDirections] calculateDirectionsWithOptions:options completionHandler:^(
        NSArray<MBWaypoint *> *waypoints,
        NSArray<MBRoute *> *routes,
        NSError *error) {
        
        if (!routes.firstObject) {
            return;
        }
        
        MBRoute *route = routes.firstObject;
        self.directionsRoute = route;
        CLLocationCoordinate2D *routeCoordinates = malloc(route.coordinateCount * sizeof(CLLocationCoordinate2D));
        [route getCoordinates:routeCoordinates];
        // Draw the route on the map after creating it
        [self drawRoute:routeCoordinates];
    }];
}

Modify the didLongPress function so that the calculateRoute function 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.

class ViewController: UIViewController, MGLMapViewDelegate {
    
    @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)
    
        // Create a basic point annotation and add it to the map
        let annotation = MGLPointAnnotation()
        annotation.coordinate = coordinate
        annotation.title = "Start navigation"
        mapView.addAnnotation(annotation)

        // Calculate the route from the user's location to the set destination
        calculateRoute(from: (mapView.userLocation!.coordinate), to: annotation.coordinate) { (route, error) in
            if error != nil {
                print("Error calculating route")
            }
        }
    }
    
}

-(void)didLongPress:(UITapGestureRecognizer *)sender {
    if (sender.state != UIGestureRecognizerStateEnded) {
        return;
    }
    
    // Converts point where user did a long press to map coordinates
    CGPoint point = [sender locationInView:self.mapView];
    CLLocationCoordinate2D coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
    
    // Create a basic point annotation and add it to the map
    MGLPointAnnotation *annotation = [MGLPointAnnotation alloc];
    annotation.coordinate = coordinate;
    annotation.title = @"Start navigtation";
    [self.mapView addAnnotation:annotation];
    
    // Calculate the route from the user's location to the set destination
    [self calculateRoutefromOrigin:self.mapView.userLocation.coordinate
                     toDestination:annotation.coordinate
                        completion:^(MBRoute * _Nullable route, NSError * _Nullable error) {
                            if (error != nil) {
                                NSLog(@"Error calculating route: %@", error);
                            }
    }];
}

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.

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 annotationCanShowCallout and tapOnCalloutFor delegate methods. When the callout is tapped, present a new NavigationViewController using the route that was generated. Add these two new functions within your view controller.

class ViewController: UIViewController, MGLMapViewDelegate {
  
  // 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) {
      let navigationViewController = NavigationViewController(for: directionsRoute!)
      self.present(navigationViewController, animated: true, completion: nil)
  }
  
}

// Implement the delegate method that allows annotations to show callouts when tapped
-(BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id<MGLAnnotation>)annotation {
    return true;
}

// Present the navigation view controller when the callout is selected
-(void)mapView:(MGLMapView *)mapView tapOnCalloutForAnnotation:(id<MGLAnnotation>)annotation {
    MBNavigationViewController *navigationViewController = [[MBNavigationViewController alloc] initWithRoute:_directionsRoute directions:[MBDirections sharedDirections] style:nil locationManager:nil];
    [self presentViewController:navigationViewController animated:YES 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.

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

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: