Building a navigation app
This tutorial shows how to build a simple navigation application using the TomTom Navigation SDK for iOS. The app uses built-in UI components. However, you can build custom components and integrate them with the SDK.
The application displays a map that shows the user’s location and configures the components needed for online navigation. After the user selects a destination with a long click, the app plans a route and draws it on the map. Navigation is started automatically using the route simulation.
This tutorial on GitHubProject setup
- Create an empty SwiftUI application project with Xcode 16.0 or newer. Set the deployment target to iOS 14.
- Complete the project setup guide for the project you’ve created.
- Import the necessary frameworks using the following instructions, based on your preferred package manager:
Swift Package Manager
- Open your App’s target and navigate to General > Frameworks, Libraries, and Embedded Content.
- Add the following TomTomSDK libraries from the provided code snippet. Once the project is set up, import the mentioned frameworks into your code.
// System modules.import Combineimport CoreLocationimport SwiftUI
// TomTomSDK modules.import TomTomSDKCommonUIimport TomTomSDKDefaultTextToSpeechimport TomTomSDKLocationProviderimport TomTomSDKMapDisplayimport TomTomSDKNavigationimport TomTomSDKNavigationEnginesimport TomTomSDKNavigationOnlineimport TomTomSDKNavigationTileStoreimport TomTomSDKNavigationUIimport TomTomSDKRouteimport TomTomSDKRoutePlannerimport TomTomSDKRoutePlannerOnlineimport TomTomSDKRoutingCommon CocoaPods
- Open your project’s
Podfileand add the required modules to the project’s target:TOMTOM_SDK_VERSION = '0.71.1'target 'YourAppTarget' douse_frameworks!pod 'TomTomSDKCommon', TOMTOM_SDK_VERSIONpod 'TomTomSDKCommonUI', TOMTOM_SDK_VERSIONpod 'TomTomSDKDefaultTextToSpeech', TOMTOM_SDK_VERSIONpod 'TomTomSDKLocationProvider', TOMTOM_SDK_VERSIONpod 'TomTomSDKMapDisplay', TOMTOM_SDK_VERSIONpod 'TomTomSDKNavigation', TOMTOM_SDK_VERSIONpod 'TomTomSDKNavigationEngines', TOMTOM_SDK_VERSIONpod 'TomTomSDKNavigationOnline', TOMTOM_SDK_VERSIONpod 'TomTomSDKNavigationUI', TOMTOM_SDK_VERSIONpod 'TomTomSDKRoute', TOMTOM_SDK_VERSIONpod 'TomTomSDKRoutePlanner', TOMTOM_SDK_VERSIONpod 'TomTomSDKRoutePlannerOnline', TOMTOM_SDK_VERSIONpod 'TomTomSDKRouteReplannerDefault', TOMTOM_SDK_VERSIONpod 'TomTomSDKSearch', TOMTOM_SDK_VERSIONend - Install the dependencies by executing the following commands in the project directory:
pod repo update tomtom-sdk-cocoapodspod install --repo-update
- Import the following frameworks in your project’s
<ProjectName>App.swiftfile:// System modules.import Combineimport CoreLocationimport SwiftUI// TomTomSDK modules.import TomTomSDKCommonUIimport TomTomSDKDefaultTextToSpeechimport TomTomSDKLocationProviderimport TomTomSDKMapDisplayimport TomTomSDKNavigationimport TomTomSDKNavigationEnginesimport TomTomSDKNavigationOnlineimport TomTomSDKNavigationTileStoreimport TomTomSDKNavigationUIimport TomTomSDKRouteimport TomTomSDKRoutePlannerimport TomTomSDKRoutePlannerOnlineimport TomTomSDKRoutingCommon
With the dependencies set up, you can proceed to the next step and start displaying the map.
Displaying a map
After successfully creating your iOS application, you can initialize and display the map with the following steps:
Step 1: Assigning the API Key
Assign the key for TomTomSDKMapDisplay by adding the following line to your app’s initializer method:
// Replace the content of the App struct in your Xcode project with the content of the provided snippet.@mainstruct MyNavigationApp: App { var body: some Scene { WindowGroup { ContentView() .ignoresSafeArea(edges: [.top, .bottom]) } }
init() { TomTomSDKMapDisplay.MapsDisplayService.apiKey = Keys.tomtomApiKey }}Step 2: Map UI setup
Construct the MapCoordinator class that is used to handle map events:
final class MapCoordinator { init(mapView: TomTomSDKMapDisplay.MapView) { self.mapView = mapView }
private let mapView: TomTomSDKMapDisplay.MapView private var map: TomTomSDKMapDisplay.TomTomMap?}Construct the TomTomMapView structure, which contains the map view setup:
struct TomTomMapView { var mapView = TomTomSDKMapDisplay.MapView()}Step 3: SwiftUI integration
Extend the TomTomMapView struct to conform to the UIViewRepresentable protocol. This allows you to use the TomTomMapView in SwiftUI:
extension TomTomMapView: UIViewRepresentable { func makeUIView(context: Context) -> TomTomSDKMapDisplay.MapView { return mapView }
func updateUIView(_: TomTomSDKMapDisplay.MapView, context: Context) {}
func makeCoordinator() -> MapCoordinator { MapCoordinator(mapView: mapView) }}Step 4: Displaying the map
Replace the ContentView structure declaration, enabling the presentation of the TomTomMapView on the application screen:
struct ContentView: View { var body: some View { ZStack(alignment: .bottom) { TomTomMapView() } }}Once you have the TomTomMap object, you can perform various actions such as adding markers or drawing routes .
Build and run your application. The application screen should have a globe on it as follows:

Showing user location
This section guides you through displaying the user’s location on the map and adjusting the camera to focus on the user’s position. To enable this functionality, use the following steps:
Step 1: Enabling location services
Before proceeding with code changes, you must properly configure your app’s Info.plist according to the Apple documentation , for the following keys:
NSLocationAlwaysAndWhenInUseUsageDescriptionNSLocationWhenInUseUsageDescription
Step 2: Requesting authorization to use location services
To get appropriate permissions, you must send authorization request by calling requestWhenInUseAuthorization() or requestAlwaysAuthorization() on the CLLocationManager instance respectively. Complete instructions to request location permissions on iOS can be found at Requesting authorization for location services .
Step 3: Setting up map coordinator
Add the cameraUpdated parameter to MapCoordinator to handle camera updates:
final class MapCoordinator { init( mapView: TomTomSDKMapDisplay.MapView, locationManager: CLLocationManager = CLLocationManager() ) { self.mapView = mapView self.locationManager = locationManager locationManager.requestWhenInUseAuthorization() }
private let locationManager: CLLocationManager private let mapView: TomTomSDKMapDisplay.MapView private var map: TomTomSDKMapDisplay.TomTomMap? private var cameraUpdated = false}Step 4: Enabling camera utility functions
Extend the MapCoordinator class with utility functions that control the camera’s behavior. These functions enable: * zooming, * panning, * rotating, and * tilting the map. More information about camera controls on CameraUpdate :
extension MapCoordinator { private var defaultCameraUpdate: CameraUpdate { let defaultLocation = CLLocation(latitude: 0.0, longitude: 0.0) return CameraUpdate( position: defaultLocation.coordinate, zoom: 1.0, tilt: 0.0, rotation: 0.0, positionMarkerVerticalOffset: 0.0 ) }
func animateCamera( zoom: Double, position: CLLocationCoordinate2D, animationDurationInSeconds: TimeInterval, onceOnly: Bool ) { if onceOnly, cameraUpdated { return } cameraUpdated = true var cameraUpdate = defaultCameraUpdate cameraUpdate.zoom = zoom cameraUpdate.position = position map?.applyCamera( cameraUpdate, animationDuration: animationDurationInSeconds ) }}Step 5: Following user location
Extend the MapCoordinator class to conform to the TomTomSDKLocationProvider.LocationUpdateObserver protocol to observe GPS updates:
extension MapCoordinator: TomTomSDKLocationProvider.LocationUpdateObserver { func didUpdateLocation(location: GeoLocation) { // Zoom and center the camera on the first location received. animateCamera( zoom: 9.0, position: location.location.coordinate, animationDurationInSeconds: 1.5, onceOnly: true ) }}Step 6: Starting location updates
Add an extension to MapCoordinator that conforms to TomTomSDKMapDisplay.MapDelegate :
extension MapCoordinator: TomTomSDKMapDisplay.MapDelegate { func map(_ map: TomTomMap, onInteraction interaction: MapInteraction) { // Handle map interactions. }
func map(_ map: TomTomMap, onCameraEvent event: CameraEvent) { // Handle camera events. }}In your MapCoordinator conform to the TomTomSDKMapDisplay.MapViewDelegate protocol, update the MapViewDelegate.mapView(_:onMapReady:) method to start observing location updates:
extension MapCoordinator: TomTomSDKMapDisplay.MapViewDelegate { func mapView(_ mapView: MapView, onMapReady map: TomTomMap) { // Store the map to be used later. self.map = map
// Observe map events. map.delegate = self
// Observe location updates. map.locationProvider.addObserver(self)
// Display a chevron. map.locationIndicatorType = .navigationChevron(scale: 1)
// Activate the GPS location engine in TomTomSDK. map.activateLocationProvider()
// Configure the camera to center on the current location. map.applyCamera(defaultCameraUpdate) }
func mapView( _ mapView: MapView, onStyleLoad result: Result<StyleContainer, Error> ) { print("Style loaded") }}Finally, find the TomTomMapView extension that conforms to UIViewRepresentable and set the MapView delegate:
extension TomTomMapView: UIViewRepresentable { func makeUIView(context: Context) -> TomTomSDKMapDisplay.MapView { mapView.delegate = context.coordinator return mapView }
func updateUIView(_: TomTomSDKMapDisplay.MapView, context: Context) {}
func makeCoordinator() -> MapCoordinator { MapCoordinator(mapView: mapView) }}Once you’ve completed these steps, build your app. The camera will now zoom in on the user’s current location upon startup, indicated by the blue chevron. Remember to simulate the location if you’re using the iOS Simulator for testing.

Planning a route
Now you can add the ability to plan a route to a location on the map once the long press event has been triggered.
Step 1: Setting up the route planner
Create a new class, NavigationController, with OnlineRoutePlanner to handle route planning:
final class NavigationController: ObservableObject { convenience init() { let routePlanner = TomTomSDKRoutePlannerOnline .OnlineRoutePlanner(apiKey: Keys.tomtomApiKey) let locationProvider = DefaultCLLocationProvider()
self.init( locationProvider: locationProvider, routePlanner: routePlanner, locationManager: CLLocationManager() ) }
init( locationProvider: LocationProvider, routePlanner: TomTomSDKRoutePlannerOnline.OnlineRoutePlanner, locationManager: CLLocationManager ) { self.locationProvider = locationProvider self.routePlanner = routePlanner self.locationManager = locationManager self.locationManager.requestWhenInUseAuthorization() self.locationProvider.enable() }
let locationProvider: LocationProvider let locationManager: CLLocationManager let routePlanner: TomTomSDKRoutePlannerOnline.OnlineRoutePlanner
var routePlanningOptions: TomTomSDKRoutingCommon.RoutePlanningOptions?
let displayedRouteSubject = PassthroughSubject< TomTomSDKRoute.Route?, Never >()}Update your TomTomMapView by adding a NavigationController:
struct TomTomMapView { var mapView = TomTomSDKMapDisplay.MapView() var navigationController: NavigationController}Update the TomTomMapView UIViewRepresentable implementation:
extension TomTomMapView: UIViewRepresentable { func makeUIView(context: Context) -> TomTomSDKMapDisplay.MapView { mapView.delegate = context.coordinator return mapView }
func updateUIView(_: TomTomSDKMapDisplay.MapView, context: Context) {}
func makeCoordinator() -> MapCoordinator { MapCoordinator( mapView: mapView, navigationController: navigationController ) }}Update the TomTomMapView initializer in your ContentView by adding a NavigationController:
struct ContentView: View { @ObservedObject var navigationController = NavigationController()
var body: some View { ZStack(alignment: .bottom) { TomTomMapView(navigationController: navigationController) } }}Step 2: Planning a route
Include the RoutePlanner.planRoute(options:onRouteReady:completion:) method, which calculates a route between specified origin and destination coordinates:
extension NavigationController { enum RoutePlanError: Error { case unableToPlanRoute }
private func planRoute( from origin: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D ) async throws -> TomTomSDKNavigationEngines.RoutePlan { routePlanningOptions = try createRoutePlanningOptions( from: origin, to: destination ) let route = try await planRoute( withRoutePlanner: routePlanner, routePlanningOptions: routePlanningOptions! ) return TomTomSDKNavigationEngines .RoutePlan(route: route, routePlanningOptions: routePlanningOptions!) }
private func createRoutePlanningOptions( from origin: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D ) throws -> TomTomSDKRoutingCommon.RoutePlanningOptions { let itinerary = Itinerary( origin: ItineraryPoint(coordinate: origin), destination: ItineraryPoint(coordinate: destination) ) let costModel = CostModel(routeType: .fast)
let guidanceOptions = try TomTomSDKRoutingCommon.GuidanceOptions()
let options = try TomTomSDKRoutingCommon.RoutePlanningOptions( itinerary: itinerary, costModel: costModel, guidanceOptions: guidanceOptions ) return options }
private func planRoute( withRoutePlanner routePlanner: OnlineRoutePlanner, routePlanningOptions: TomTomSDKRoutingCommon.RoutePlanningOptions ) async throws -> TomTomSDKRoute.Route { return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<TomTomSDKRoute.Route, Error>) in
routePlanner.planRoute( options: routePlanningOptions, onRouteReady: nil ) { result in switch result { case let .failure(error): continuation.resume(throwing: error) case let .success(response): guard let route = response.routes?.first else { continuation .resume(throwing: RoutePlanError.unableToPlanRoute) return } continuation.resume(returning: route) } } } }}Step 3: Preparing the route to be displayed
Implement the planAndDisplayRoute(destination: CLLocationCoordinate2D) method, which is responsible for planning a route and sending it to the map for display:
extension NavigationController { func planAndDisplayRoute(destination: CLLocationCoordinate2D) { Task { @MainActor in do { // Plan the route and add it to the map guard let start = locationProvider.lastKnownLocation?.location.coordinate else { return } let routePlan = try await planRoute( from: start, to: destination )
let route = routePlan.route self.displayedRouteSubject.send(route) } catch { print("Error when planning a route: \(error)") } } }}Step 4: Displaying the route on the map
Update `MapCoordinator`s extension to incorporate route planning functionality, allowing it to calculate a route from the current location to the selected destination when a long press event occurs:
extension MapCoordinator: TomTomSDKMapDisplay.MapDelegate { func map(_ map: TomTomMap, onInteraction interaction: MapInteraction) { switch interaction { case let .longPressed(coordinate): navigationController.planAndDisplayRoute(destination: coordinate) default: // Handle other gestures. break } }
func map(_ map: TomTomMap, onCameraEvent event: CameraEvent) { // Handle camera events. }}Extend the MapCoordinator with the MapCoordinator.observe(navigationController:) method that will display the route on the map after it is planned:
extension MapCoordinator { func observe(navigationController: NavigationController) { navigationController.displayedRouteSubject.sink { [weak self] route in guard let self = self else { return } if let route = route { self.addRouteToMap(route: route) } else { self.routeOnMap = nil self.map?.removeRoutes() } }.store(in: &cancellableBag) }}Execute the MapCoordinator.observe(navigationController:) method in the MapCoordinator initializer:
final class MapCoordinator { init( mapView: TomTomSDKMapDisplay.MapView, navigationController: NavigationController ) { self.mapView = mapView self.navigationController = navigationController observe(navigationController: navigationController) }
private let mapView: TomTomSDKMapDisplay.MapView private var map: TomTomSDKMapDisplay.TomTomMap? private var cameraUpdated = false private let navigationController: NavigationController private var routeOnMap: TomTomSDKMapDisplay.Route? private var cancellableBag = Set<AnyCancellable>()}Extend the functionality of MapCoordinator by adding methods for drawing routes on the map and configuring camera tracking modes:
extension MapCoordinator { private func createMapRouteOptions( coordinates: [CLLocationCoordinate2D] ) -> TomTomSDKMapDisplay.RouteOptions { var routeOptions = RouteOptions(coordinates: coordinates) routeOptions.outlineWidth = 1 routeOptions.routeWidth = 5 routeOptions.color = .activeRoute return routeOptions }
func addRouteToMap(route: TomTomSDKRoute.Route) { // Create the route options from the route geometry // and adds it to the map. let routeOptions = createMapRouteOptions(coordinates: route.geometry) if let routeOnMap = try? map?.addRoute(routeOptions) { self.routeOnMap = routeOnMap
// Zoom the map to make the route visible. map?.zoomToRoutes(padding: 32) } }
func setCamera(trackingMode: TomTomSDKMapDisplay.CameraTrackingMode) { map?.cameraTrackingMode = trackingMode
// Update the chevron position on the screen // so it is not hidden behind the navigation panel. if trackingMode == .followRouteDirection() || trackingMode == .followNorthUp() { let cameraUpdate = CameraUpdate(positionMarkerVerticalOffset: 0.4) map?.moveCamera(cameraUpdate) } }}After completing these steps, you can build the application. Just perform a long press on the map, and the application plans a route from your current location to the selected point on the map, displaying it on the screen:

Setting up turn-by-turn navigation
In this section, we show you how to navigate on a planned route.
Step 1: Enable Background App Refresh
Create an Info.plist file with the UIBackgroundModes key, this will allow for map updates in the background:
<plist version="1.0"><dict> <key>UIBackgroundModes</key> <array> <string>fetch</string> </array></dict></plist>Step 2: Setting up navigation
Add a TomTomNavigation object to the NavigationController to process location updates and convert them into navigation events:
final class NavigationController: ObservableObject { convenience init() { let textToSpeech = try! SystemTextToSpeechEngine() let routePlanner = TomTomSDKRoutePlannerOnline .OnlineRoutePlanner(apiKey: Keys.tomtomApiKey) let locationProvider = DefaultCLLocationProvider() let delay = Measurement.tt.seconds(1) let simulatedLocationProvider = SimulatedLocationProvider(delay: delay) let navigationConfiguration = OnlineTomTomNavigationFactory .Configuration( navigationTileStore: try! NavigationTileStore(config: NavigationTileStoreConfiguration( apiKey: Keys .tomtomApiKey )), locationProvider: simulatedLocationProvider, routePlanner: routePlanner, betterProposalAcceptanceMode: .automatic ) let navigation = try! OnlineTomTomNavigationFactory .create(configuration: navigationConfiguration) let navigationModel = TomTomSDKNavigationUI.NavigationView .ViewModel(navigation, tts: textToSpeech)
self.init( locationProvider: locationProvider, locationManager: CLLocationManager(), simulatedLocationProvider: simulatedLocationProvider, routePlanner: routePlanner, navigation: navigation, navigationModel: navigationModel ) }
init( locationProvider: LocationProvider, locationManager: CLLocationManager, simulatedLocationProvider: SimulatedLocationProvider, routePlanner: TomTomSDKRoutePlannerOnline.OnlineRoutePlanner, navigation: TomTomSDKNavigation.TomTomNavigation, navigationModel: TomTomSDKNavigationUI.NavigationView.ViewModel ) { self.locationProvider = locationProvider self.locationManager = locationManager self.simulatedLocationProvider = simulatedLocationProvider self.routePlanner = routePlanner self.navigation = navigation self.navigationViewModel = navigationModel
self.navigation.addProgressObserver(self) self.navigation.addRouteAddObserver(self) self.navigation.addRouteRemoveObserver(self) self.navigation.addRouteUpdateObserver(self) self.navigation.addActiveRouteChangeObserver(self) locationManager.requestWhenInUseAuthorization() locationProvider.enable() }
let locationProvider: LocationProvider let locationManager: CLLocationManager let simulatedLocationProvider: SimulatedLocationProvider let routePlanner: TomTomSDKRoutePlannerOnline.OnlineRoutePlanner let navigation: TomTomSDKNavigation.TomTomNavigation let navigationViewModel: TomTomSDKNavigationUI.NavigationView.ViewModel
var routePlanningOptions: TomTomSDKRoutingCommon.RoutePlanningOptions? var displayedRoutes: [UUID: TomTomSDKRoute.Route] = [:]
let displayedRouteSubject = PassthroughSubject< TomTomSDKRoute.Route?, Never >() let navigateRouteSubject = PassthroughSubject< TomTomSDKRoute.Route?, Never >() let progressOnRouteSubject = PassthroughSubject< Measurement<UnitLength>, Never >() let mapMatchedLocationProvider = PassthroughSubject< LocationProvider, Never >()
@Published var showNavigationView: Bool = false}Extend the NavigationController with a navigateOn(route: TomTomSDKRoute.Route) method. It will start navigation on the selected route:
extension NavigationController { func navigateOn(route: TomTomSDKRoute.Route) { Task { @MainActor in guard let routePlanningOptions else { return }
self.displayedRouteSubject.send(nil) // clear previous route after replanning
self.displayedRouteSubject.send(route)
let routePlan = RoutePlan(route: route, routePlanningOptions: routePlanningOptions)
let navigationOptions = NavigationOptions( activeRoutePlan: routePlan ) self.navigationViewModel.start(navigationOptions) // Use simulated location updates. self.simulatedLocationProvider .updateCoordinates(route.geometry, interpolate: true) self.simulatedLocationProvider.enable() self.mapMatchedLocationProvider .send(navigation.mapMatchedLocationProvider)
self.navigateRouteSubject.send(route) self.showNavigationView = true } }
func stopNavigating() { displayedRouteSubject.send(nil) navigationViewModel.stop() simulatedLocationProvider.disable() showNavigationView = false }}Step 3: Starting navigation on the selected route
Extend the NavigationController with RouteDelegate . This lets you know when a user presses on the route:
extension NavigationController: RouteDelegate { func mapRoute( _ route: TomTomSDKMapDisplay.Route, didTapOnCoordinate coordinate: CLLocationCoordinate2D, waypointIndex: Int ) { guard let route = displayedRoutes[route.routeID] else { return }
navigateOn(route: route) }}Update the MapCoordinator.addRouteToMap(route:) method to add the RouteDelegate .
func addRouteToMap(route: TomTomSDKRoute.Route, zoom: Bool = false) { // Create the route options from the route geometry // and add it to the map. let routeOptions = createMapRouteOptions(coordinates: route.geometry) if let routeOnMap = try? map?.addRoute(routeOptions) { self.routeOnMap = routeOnMap navigationController.displayedRoutes[routeOnMap.routeID] = route routeOnMap.delegate = navigationController
// Zoom the map to make the route visible. if !navigationController.showNavigationView { map?.zoomToRoutes(padding: 32) // zoom to the route if no navigation in progress } }}Step 4: Observing navigation events
Extend NavigationController to conform to NavigationProgressObserver to enable progress updates during navigation:
extension NavigationController: TomTomSDKNavigation.NavigationProgressObserver { func didUpdateProgress(progress: RouteProgress) { progressOnRouteSubject.send(progress.distanceAlongRoute) }}Extend the NavigationController class to handle navigation events and ensure the use of TomTomSDKNavigationUI.NavigationView.ViewModel for displaying both visual and voice instructions:
extension NavigationController { func onNavigationViewAction( _ action: TomTomSDKNavigationUI.NavigationView.Action ) { switch action { case let .arrival(action): onArrivalAction(action) case let .instruction(action): onInstructionAction(action) case let .confirmation(action): onConfirmationAction(action) case let .error(action): onErrorAction(action) @unknown default: /* YOUR CODE GOES HERE */ break } }
fileprivate func onArrivalAction( _ action: TomTomSDKNavigationUI.NavigationView.ArrivalAction ) { switch action { case .close: stopNavigating() @unknown default: /* YOUR CODE GOES HERE */ break } }
fileprivate func onInstructionAction( _ action: TomTomSDKNavigationUI.NavigationView.InstructionAction ) { switch action { case let .tapSound(muted): navigationViewModel.muteTextToSpeech(mute: !muted) case .tapLanes: navigationViewModel.hideLanes() case .tapThen: navigationViewModel.hideCombinedInstruction() @unknown default: /* YOUR CODE GOES HERE */ break } }
fileprivate func onConfirmationAction( _ action: TomTomSDKNavigationUI.NavigationView.ConfirmationAction ) { switch action { case .yes: stopNavigating() case .no: /* YOUR CODE GOES HERE */ break @unknown default: /* YOUR CODE GOES HERE */ break } }
fileprivate func onErrorAction( _ action: TomTomSDKNavigationUI.NavigationView.ErrorAction ) { /* YOUR CODE GOES HERE */ }}Extend the NavigationController class to handle the following route related changes:
- adding of a new route, e.g. if navigation proposes a better route
- removal of an existing route, e.g. if a better route proposal is rejected
- updating of an existing route, e.g. to obtain the latest traffic information
- changing of the active route, e.g. if a better route proposal is accepted
extension NavigationController: TomTomSDKNavigation.NavigationRouteAddObserver, TomTomSDKNavigation.NavigationRouteRemoveObserver, TomTomSDKNavigation.NavigationActiveRouteChangeObserver, TomTomSDKNavigation.NavigationRouteUpdateObserver { func didAddRoute( route: TomTomSDKRoute.Route, options: TomTomSDKRoutingCommon.RoutePlanningOptions, reason: TomTomSDKNavigation.RouteAddedReason ) { let triggeringReasons: [RouteAddedReason] = [.avoidBlockage, .deviated, .manuallyUpdated, .withinRange] if triggeringReasons.contains(reason) { displayedRouteSubject.send(nil) // clear previous route after replanning displayedRouteSubject.send(route) } }
func didRemoveRoute(route: TomTomSDKRoute.Route, reason: TomTomSDKNavigation.RouteRemovedReason) {}
func didChangeActiveRoute(route: TomTomSDKRoute.Route) {}
func didUpdateRoute(route: TomTomSDKRoute.Route, reason: TomTomSDKNavigation.RouteUpdatedReason) {}}Update the MapCoordinator.observe(navigationController:) method to process progress and LocationProvider updates:
extension MapCoordinator { func observe(navigationController: NavigationController) { navigationController.displayedRouteSubject.sink { [weak self] route in guard let self = self else { return } if let route = route { self.addRouteToMap(route: route) } else { self.navigationController.displayedRoutes = [:] self.routeOnMap = nil self.map?.removeRoutes() self.setCamera(trackingMode: .none) } }.store(in: &cancellableBag)
navigationController.navigateRouteSubject .sink { [weak self] route in guard let self = self else { return } if route != nil { self.setCamera(trackingMode: .followRouteDirection()) } else { self.setCamera(trackingMode: .followNorthUp()) } }.store(in: &cancellableBag)
navigationController.progressOnRouteSubject .sink { [weak self] progress in self?.routeOnMap?.progressOnRoute = progress }.store(in: &cancellableBag)
navigationController.mapMatchedLocationProvider .sink { [weak self] locationProvider in self?.map?.locationProvider = locationProvider }.store(in: &cancellableBag) }}Step 5: Displaying navigation UI
Include a NavigationView in your ContentView to enable the display of navigation-related information:
struct ContentView: View { @ObservedObject var navigationController = NavigationController()
var body: some View { ZStack(alignment: .bottom) { TomTomMapView(navigationController: navigationController) if navigationController.showNavigationView { NavigationView( navigationController.navigationViewModel, action: navigationController.onNavigationViewAction ) } } }}To enable voice guidance on the iOS 16+ simulator, access the “Settings” app on the simulator’s home screen. Within the Settings, navigate to “Accessibility” > “Spoken Content” and enable the “Speak Selection” toggle. Proceed to the “Voices” submenu, select and download a voice. Finally, press the play button on the downloaded voice to test it.
After completing these steps, you can build the app. Simply, perform a long press on the map and the app will plan a route from your current location to the selected point on the map and start the navigation simulation, including voice guidance:

Next steps
The TomTom Navigation SDK lets you customize the appearance of the map and its overlays, use your own Navigation UI components, or provide a custom implementation of certain navigation behaviors. See the following guides for more information: