> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/help/ja/llms.txt)

# Add turn-by-turn navigation to an iOS app

This Mapbox tutorial walks through how to add turn-by-turn navigation to an iOS app using the [**Mapbox Navigation SDK for iOS**](https://docs.mapbox.com/ios/navigation/). The finished product shows the user a list of predefined destinations. When the user selects a destination, the app opens a navigation view with turn-by-turn directions including a map, voice prompts, printed instructions, etc, directing the user to the selected destination.

Navigation in the tutorial is *simulated*, so you can see the location update in real time in the iOS simulator. You can switch to *live* navigation with a configuration change and test the app on a real device.

You'll learn how to:

-   Create a minimal destination list view using SwiftUI, with buttons to trigger navigation to known destinations.
-   Use the device's current location as the origin point for a navigation trip.
-   Use the **Mapbox Navigation SDK for iOS** to prepare navigation and show a navigation view that can be triggered for any origin and destination.
-   Switch from simulated navigation to live navigation with a configuration change.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--ios-navigation--demo-1a09bc66fd3d3f114ef82525f8aab9ca.mp4).

If you would like to run the finished product locally before following the tutorial, you can find the source code for this tutorial on [GitHub](https://github.com/mapbox/tutorials/tree/main/ios-navigation/).

## Prerequisites

Before starting this tutorial you will need:

-   **Familiarity with iOS development**: Beginner experience with Swift, SwiftUI and iOS Development.
-   [Xcode](https://developer.apple.com/xcode/) installed on your system.
-   **Mapbox account**: [Sign up](https://account.mapbox.com/auth/signup/) for free or [log in](https://console.mapbox.com/) if you already have an account.
-   **A Mapbox access token**: You will need a secret access token to install the Navigation SDK for iOS, and a public access token to run the app.

## Install the Navigation SDK

Create a new Xcode project using the "App" template. Choose "Swift" as the language and "SwiftUI" as the interface.

Install the **Mapbox Navigation SDK for iOS** and configure your secret access token, your public access token, permissions, and background modes in your `Info.plist`. These steps are described in detail in the Navigation SDK [getting started guide](https://docs.mapbox.com/ios/navigation/guides/install/).

Complete parts 1 through 4 of the [getting started guide](https://docs.mapbox.com/ios/navigation/guides/install/) before moving ahead.

> **Related content (guide): [Install the Navigation SDK for iOS](https://docs.mapbox.com/ios/navigation/guides/install/)**
> 
> Follow this detailed guide to install the Navigation SDK for iOS dependencies and configure permissions in your Xcode project.

### Set the simulator location

Before proceeding, build and run your app in the iOS simulator. After the simulator is up and running take a moment to set the simulator's location. This location will be used as the origin point for navigation.

You can use coordinates near New York City, as the destinations in this tutorial's sample are also in New York City. The images below show setting the location to `40.759211`, `-73.98631`, a location near Union Square Park in Manhattan.

![Set the iOS simulator location](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--ios-navigation--set-location.412ea53.480.png) ![Set the iOS simulator location](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--ios-navigation--set-location-1.773dd42.480.png)

## Build a destination list

The first step is to build a view to show a list of destinations to navigate to. In a real world app, your users might select a destination from a list of saved locations, or search for a location. For this tutorial, we'll use a hardcoded list of destinations.

Create a new file `DestinationListView.swift` and add the code from the snippet below. This code includes a `LocationManager` class to get the device's current location, a `Destination` struct to represent a destination, and a `DestinationListView` struct to show the list of destinations as buttons.

When the button is tapped, the app will get the current location's coordinates from the `LocationManager` along with the coordinates for the destination. For now, you can use `print()` to log the origin and destination when the user taps the button to make sure the correct data is available for navigation. Later, you'll pass the origin and destination coordinates to the navigation view.

You can use the three destinations in New York City below, or change the names and coordinates to use your own locations. (If you use your own coordinates, be sure to update the location used by your emulator so your origin and destinations represent realistic trips.)

> **Note: Use the Location Helper to find coordinates**
> 
> If you would like to use your own list of destinations, you can use the [Mapbox Location Helper](https://labs.mapbox.com/location-helper/) tool to quickly the longitude and latitude coordinates for any location.

```swift
import SwiftUI
import CoreLocation
import Combine
import MapboxNavigationCore
import MapboxDirections

// LocationManager handles the device's location updates
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()
    
    // Publishes the current location to subscribers
    @Published var currentLocation: CLLocationCoordinate2D?
    
    // Initializes the CLLocationManager and starts updating location
    override init() {
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.requestWhenInUseAuthorization() // Request location access permission from the user
        manager.startUpdatingLocation() // Start receiving location updates
    }
    
    // Delegate method called when location is updated
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        currentLocation = locations.last?.coordinate // Store the most recent location
    }
    
    // Delegate method called when location updates fail
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("Failed to get location: \(error)") // Print error if location fails
    }
}

// Represents a destination point with name and coordinates
struct Destination: Identifiable {
    let id = UUID()
    let name: String
    let coordinates: CLLocationCoordinate2D
}

// List of predefined destination points
let destinations = [
    Destination(name: "Columbus Circle", coordinates: CLLocationCoordinate2D(latitude: 40.76804, longitude: -73.98190)),
    Destination(name: "Empire State Building", coordinates: CLLocationCoordinate2D(latitude: 40.74843, longitude: -73.98568)),
    Destination(name: "Brooklyn Bridge Park", coordinates: CLLocationCoordinate2D(latitude: 40.70223, longitude: -73.99656)),
]

// Main view that displays a list of destinations to choose from
struct DestinationListView: View {
    @StateObject private var locationManager = LocationManager() // Observes the user's current location
    
    var body: some View {
        NavigationStack {
            List(destinations) { destination in
                Button(action: {
                    // Check if the current location is available before proceeding
                    guard let origin = locationManager.currentLocation else {
                        print("Current location not available yet.")
                        return
                    }
                    
                    print("This button will trigger navigation from \(origin) to \(destination.coordinates)")
                   
                }) {
                    // Layout for each destination button
                    HStack {
                        Image(systemName: "location.fill").foregroundColor(.blue)
                        Text(destination.name)
                        Spacer()
                        Image(systemName: "arrow.turn.up.right").foregroundColor(.gray)
                    }
                    .padding(.vertical, 8)
                }
            }
            .navigationTitle("Choose Destination") // Title for the navigation stack
        }
    }
}
```

Be sure to update the app's entry point swift file to use the `DestinationListView` as the root view. The filename depends on what you named your project.

```swift
import SwiftUI

@main
struct ios_navigationApp: App {
    var body: some Scene {
        WindowGroup {
            DestinationListView()
        }
    }
}
```

Run your app and tap the destination buttons. You will see print statements in the console showing the origin and destination to be used for navigation.

In the next step, you will use these coordinates to prepare navigation.

![animated gif showing taps on the destination buttons and print statements in the console logging the origin and destination coordinates](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--ios-navigation--logging.480.gif)

## Add a Navigation Loader

With a minimal UI in place to trigger navigation to different destinations, the next step is to prepare for navigation by creating a `MapboxNavigationProvider` and calculating routes between the origin and destination coordinates.

To do this, add a new file named `NavigationLoader.swift` using the code snippet below. The `loadNavigation` method takes the origin and destination coordinates as input, creates a `MapboxNavigationProvider`, and uses it to calculate routes. The function returns a `PreparedNavigation` object containing the routes and the navigation provider.

```swift
import MapboxNavigationCore
import CoreLocation

class NavigationLoader {
    func loadNavigation(
        from origin: CLLocationCoordinate2D,
        to destination: CLLocationCoordinate2D
    ) async throws -> PreparedNavigation {
        let locationSource: LocationSource = .simulation(initialLocation: .init(CLLocation(latitude: origin.latitude, longitude: origin.longitude)))
        // to use live navigation, uncomment the line below and comment out the line above
        // let locationSource: LocationSource = .live
        
        // instantiate a MapboxNavigationProvider
        let provider = MapboxNavigationProvider(coreConfig: .init(locationSource: locationSource))
        
        // use the navigation provider to fetch routes
        let result = await provider.mapboxNavigation
            .routingProvider()
            .calculateRoutes(options: NavigationRouteOptions(coordinates: [origin, destination]))
            .result
        
        return switch result {
        case .failure(let error):
            throw error
        case .success(let routes):
            // return both the routes and the provider, to be used in presenting the navigation view controller
            PreparedNavigation(routes: routes, navigationProvider: provider)
        }
    }
}
```

To use `NavigationLoader` to prepare for navigation when the user taps a destination, you will need to make a few changes to `DestinationListView.swift`:

1.  Add a `PreparedNavigation` struct to hold the routes and navigation provider.
2.  Add a new variable `navigationLoader` to store the `NavigationLoader` instance.
3.  Add a new state variable `preparedNavigation` to store the result of the navigation loader.
4.  Update the button action: Remove the `print()` statement from the previous step and add a call to `loadNavigation` with the origin and destination coordinates and store the result in `preparedNavigation`. This requires wrapping the call in a `Task` to run it asynchronously.

To confirm that the navigation loader is working, you print the route distance to the console after `loadNavigation` completes. Copy the highlighted sections of code below into your `DestinationListView.swift` file.

```swift
...

// Represents a destination point with name and coordinates
struct Destination: Identifiable {
    let id = UUID()
    let name: String
    let coordinates: CLLocationCoordinate2D
}

// highlight-start
// Represents routes between origin and destination and the navigation provider
struct PreparedNavigation: Identifiable {
    let id = UUID()
    let routes: NavigationRoutes
    let navigationProvider: MapboxNavigationProvider
}
// highlight-end

// List of predefined destination points
let destinations = [
    Destination(name: "Columbus Circle", coordinates: CLLocationCoordinate2D(latitude: 40.76804, longitude: -73.98190)),
    Destination(name: "Empire State Building", coordinates: CLLocationCoordinate2D(latitude: 40.74843, longitude: -73.98568)),
    Destination(name: "Brooklyn Bridge Park", coordinates: CLLocationCoordinate2D(latitude: 40.70223, longitude: -73.99656)),
]

// Main view that displays a list of destinations to choose from
struct DestinationListView: View {
    // highlight-next-line
    @State private var preparedNavigation: PreparedNavigation? = nil
    @StateObject private var locationManager = LocationManager() // Observes the user's current location

    // highlight-next-line
    private let navigationLoader = NavigationLoader()
    
    var body: some View {
        NavigationStack {
            List(destinations) { destination in
                Button(action: {
                    // Check if the current location is available before proceeding
                    guard let origin = locationManager.currentLocation else {
                        print("Current location not available yet.")
                        return
                    }
                    
                    // highlight-start
                    Task {
                        if let result = try? await navigationLoader.loadNavigation(from: origin, to: destination.coordinates) {
                            preparedNavigation = result
                            let distance = result.routes.mainRoute.route.distance
                            print("✅ Navigation to \(destination.name) ready: route distance: \(distance) meters.")
                        }
                    }
                    // highlight-end
                }) {
                    // Layout for each destination button
                    HStack {
                        Image(systemName: "location.fill").foregroundColor(.blue)
                        Text(destination.name)
                        Spacer()
                        Image(systemName: "arrow.turn.up.right").foregroundColor(.gray)
                    }
                    .padding(.vertical, 8)
                }
            }
            .navigationTitle("Choose Destination") // Title for the navigation stack
        }
    }
}

```

![animated gif showing taps on the destination buttons and print statements in the console showing that navigation is prepared](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--ios-navigation--loading.480.gif)

Run your app again and tap a destination button. You should see print statements in the console showing that navigation is prepared, including the route distance.

In the next step, you will use the routes and navigation provider to present the navigation view controller to the user and start navigation.

> **Note (warning): Error about multiple NavigationProvider instances**
> 
> At this point, if you tap more than one destination button, you may see an error in the console about multiple `NavigationProvider` instances. This is because the `NavigationLoader` creates a new instance of `MapboxNavigationProvider` each time you call `loadNavigation` which is never released.
> 
> In the next step you will add the `NavigationViewController`, which will take care of releasing the `NavigationProvider` when the navigation view is dismissed before the user can tap another destination button.

## Add a Navigation View Controller

The next step is to add the [`NavigationViewController`](https://docs.mapbox.com/ios/navigation/api/3.8.0/navigation/documentation/mapboxnavigationuikit/navigationviewcontroller/), the view that displays the navigation experience to the user in your app. It takes care of displaying the map, turn-by-turn directions, and voice prompts.

To create the navigation view controller, you must pass in the routes you calculated in the previous step, along with a `NavigationOptions` object. `NavigationOptions` is derived from various properties and methods of the navigation provider you created in the previous step.

Since `NavigationViewController` is a UIKit view controller, you will need to use [`UIViewControllerRepresentable`](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable) to wrap it in a SwiftUI view.

Create a new file named `NavigationViewWrapper.swift` and add the code snippet below. This code includes the `NavigationViewWrapper` struct that wraps the `NavigationViewController`, and the `Coordinator` class that handles the dismissal of the navigation view controller.

```swift
import SwiftUI
import MapboxNavigationUIKit
import MapboxNavigationCore
import CoreLocation
import MapboxMaps
import MapboxDirections

// Coordinator to bridge UIKit's NavigationViewController delegate with SwiftUI
class Coordinator: NSObject, NavigationViewControllerDelegate {
    var onCancel: () -> Void
    
    // Initialize with a closure that will be called when navigation is cancelled or dismissed
    init(onCancel: @escaping () -> Void) {
        self.onCancel = onCancel
    }
    
    // Called when the NavigationViewController is dismissed
    func navigationViewControllerDidDismiss(
        _ navigationViewController: NavigationViewController,
        byCanceling canceled: Bool
    ) {
        print("Dismissed. Canceled: \(canceled)")
        onCancel() // Invoke the callback to dismiss the SwiftUI sheet
    }
}



// SwiftUI wrapper to embed the Mapbox NavigationViewController
struct NavigationViewWrapper: UIViewControllerRepresentable {
    let preparedNavigation: PreparedNavigation
    var onCancel: () -> Void = {}  // Callback when navigation is cancelled
    
    // Provides the coordinator instance for delegate handling
    func makeCoordinator() -> Coordinator {
        Coordinator(onCancel: onCancel)
    }
    
    // Creates the UIKit view controller that hosts the navigation experience
    func makeUIViewController(context: Context) -> UIViewController {
        let routes = preparedNavigation.routes
        let navigationProvider = preparedNavigation.navigationProvider
        let navigationOptions = NavigationOptions(
            mapboxNavigation: navigationProvider.mapboxNavigation,
            voiceController: navigationProvider.routeVoiceController,
            eventsManager: navigationProvider.eventsManager()
        )
        
        // 4. Create a NavigationViewController using the routes and options
        let navigationViewController = NavigationViewController(
            navigationRoutes: routes,
            navigationOptions: navigationOptions
        )
        
        navigationViewController.delegate = context.coordinator // Set the delegate for dismissal
        navigationViewController.routeLineTracksTraversal = true // Show route line tracking
        navigationViewController.modalPresentationStyle = .fullScreen
        
        // Optionally set the initial camera view
        if let origin = routes.waypoints.first?.coordinate {
            navigationViewController.navigationMapView?.mapView.mapboxMap.setCamera(
                to: CameraOptions(center: origin, zoom: 11.0)
            )
        }
        
        return navigationViewController
    }
    
    // No need to update the UIViewController after it's created
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
```

Next, update the `DestinationListView` to show the `NavigationViewWrapper` using [`fullScreenCover`](https://developer.apple.com/documentation/swiftui/view/fullscreencover(ispresented:ondismiss:content:)), but only after the `preparedNavigation` state variable exists. This will show the navigation view when the user taps a destination button and the navigation loader has completed. Copy the highlighted code section below into your `DestinationListView.swift` file.

```swift
...
// Main view that displays a list of destinations to choose from
struct DestinationListView: View {
    @State private var preparedNavigation: PreparedNavigation? = nil
    @StateObject private var locationManager = LocationManager() // Observes the user's current location
    private let navigationLoader = NavigationLoader()
    
    var body: some View {
        NavigationStack {
            List(destinations) { destination in
                Button(action: {
                    // Check if the current location is available before proceeding
                    guard let origin = locationManager.currentLocation else {
                        print("Current location not available yet.")
                        return
                    }
                    
                    Task {
                        let destinationCoord = destination.coordinates
                        if let result = try? await navigationLoader.loadNavigation(from: origin, to: destinationCoord) {
                            preparedNavigation = result
                            let distance = result.routes.mainRoute.route.distance
                            print("✅ Navigation to \(destination.name) ready: route distance: \(distance) meters.")
                        }
                    }
                    
                }) {
                    // Layout for each destination button
                    HStack {
                        Image(systemName: "location.fill").foregroundColor(.blue)
                        Text(destination.name)
                        Spacer()
                        Image(systemName: "arrow.turn.up.right").foregroundColor(.gray)
                    }
                    .padding(.vertical, 8)
                }
            }
            .navigationTitle("Choose Destination") // Title for the navigation stack
            // highlight-start
            // Present the navigation view full screen when OD pair is set
            .fullScreenCover(item: $preparedNavigation) { preparedNavigation in
                NavigationViewWrapper(
                    preparedNavigation: preparedNavigation,
                    onCancel: {
                        self.preparedNavigation = nil // Reset when user cancels
                    }
                )
                .edgesIgnoringSafeArea(.all) // Make the navigation view full screen
            }
            // highlight-end
        }
    }
}

```

Run your app again, and tap a destination button. You should see the navigation view appear with a map showing turn by turn directions with audio instructions to the selected destination. Because we specified *simulated* `LocationSource`, the device location will slowly move to the destination, and you will see the route update in real time. Turn up your volume to hear voice prompts for upcoming turns and maneuvers.

You can dismiss the navigation view by tapping the "X" button in the bottom right corner, and trigger it again by tapping a destination button.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--ios-navigation--demo-1a09bc66fd3d3f114ef82525f8aab9ca.mp4).

### Switch to live navigation

To switch to live navigation, you can change the `LocationSource` in `NavigationLoader` to `.live`. This will use the device's actual location as it moves instead of a simulated location. Refer to the commented code near the top of your `NavigationLoader.swift` file. This mode is ideal for testing the app on a real mobile device and trying out navigation in the real world, and you will want to use it when you publish your app.

If you want to use live navigation when using an iOS simulator, you will need to continuously update the device location. Options to update the simulator location include:

-   **Enter a location manually**: You can set a custom location in the simulator (as shown in step 3 of this tutorial) by going to `Features > Location > Custom Location...` and entering new coordinates. This manual process is time consuming and not ideal for testing.
-   **Use a GPX file**: You can create a GPX file with a series of coordinates and load it into the simulator. This will simulate a route for the device to follow. Here's [a helpful blog post](https://medium.com/@merlos/how-to-simulate-locations-in-xcode-b0f7f16e126d) on how to use a GPX file for custom simulated locations in the iOS emulator.
-   **Use a third-party tool**: There are third-party tools available, such as [RocketSim](https://www.rocketsim.app/), that can simulate GPS movement in the simulator with real-time control. These tools can be used to create a more realistic simulation of GPS movement.

## Next steps

**Congratulations!** You've built a minimal iOS app that uses the Mapbox Navigation SDK to add navigation based on an origin and destination provided from another view.

### What we covered

-   Create a minimal destination list view using SwiftUI, with buttons to trigger navigation to known destinations.
-   Use the device's simulated location as the origin point for a navigation trip.
-   Use the **Mapbox Navigation SDK for iOS** to prepare navigation and present a navigation view that can be triggered for any origin and destination.
-   Switch from simulated navigation to live navigation.

### Things to try

With this minimal implementation working, you can try to add more features to your app:

-   **Customize the Navigation UI**: The Navigation SDK for iOS provides allows for customization of the navigation view. You can change the colors, fonts, and layout of the navigation view to match your app's design. See the [User Interface guide](https://docs.mapbox.com/ios/navigation/guides/turn-by-turn-navigation/user-interface/) to learn more about UI customization.
-   **Add Location Search**: You can add a location search bar to the destination list view to allow users to search for destinations. You can use the [Mapbox Search SDK for iOS](https://docs.mapbox.com/ios/search/), which includes drop-in search components, or build your own UI and use one of our [search APIs](https://docs.mapbox.com/api/search/) to get results.
-   **Add a map view**: You could replace the destination list with a map view that shows the user's current location and allows them to select a destination by tapping a map marker. You could also use the map view to preview the route before triggering turn-by-turn navigation.

### More Resources

-   Browse the source code for this tutorial on [GitHub](https://github.com/mapbox/tutorials/tree/main/ios-navigation/)
-   Read the documentation guides for the [Navigation SDK for iOS](https://docs.mapbox.com/ios/navigation/guides/) to learn more about advanced features and customization options.
-   Explore the [Navigation SDK for iOS API reference](https://docs.mapbox.com/ios/navigation/api/) to see all available classes and methods.