Building a navigation app
The Navigation SDK for iOS is only available upon request. Contact us to get started.
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.
Project setup
- Create an empty SwiftUI application project with Xcode 14 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.
1// System modules.2import Combine3import CoreLocation4import SwiftUI56// TomTomSDK modules.7import TomTomSDKCommonUI8import TomTomSDKDefaultTextToSpeech9import TomTomSDKLocationProvider10import TomTomSDKMapDisplay11import TomTomSDKNavigation12import TomTomSDKNavigationEngines13import TomTomSDKNavigationOnline14import TomTomSDKNavigationTileStore15import TomTomSDKNavigationUI16import TomTomSDKRoute17import TomTomSDKRoutePlanner18import TomTomSDKRoutePlannerOnline19import TomTomSDKRoutingCommon
CocoaPods
- Open your project’s
Podfile
and add the required modules to the project’s target:1TOMTOM_SDK_VERSION = '0.67.0'2target 'YourAppTarget' do3 use_frameworks!4 pod 'TomTomSDKCommon', TOMTOM_SDK_VERSION5 pod 'TomTomSDKCommonUI', TOMTOM_SDK_VERSION6 pod 'TomTomSDKDefaultTextToSpeech', TOMTOM_SDK_VERSION7 pod 'TomTomSDKLocationProvider', TOMTOM_SDK_VERSION8 pod 'TomTomSDKMapDisplay', TOMTOM_SDK_VERSION9 pod 'TomTomSDKNavigation', TOMTOM_SDK_VERSION10 pod 'TomTomSDKNavigationEngines', TOMTOM_SDK_VERSION11 pod 'TomTomSDKNavigationOnline', TOMTOM_SDK_VERSION12 pod 'TomTomSDKNavigationUI', TOMTOM_SDK_VERSION13 pod 'TomTomSDKRoute', TOMTOM_SDK_VERSION14 pod 'TomTomSDKRoutePlanner', TOMTOM_SDK_VERSION15 pod 'TomTomSDKRoutePlannerOnline', TOMTOM_SDK_VERSION16 pod 'TomTomSDKRouteReplannerDefault', TOMTOM_SDK_VERSION17 pod 'TomTomSDKSearch', TOMTOM_SDK_VERSION18end - Install the dependencies by executing the following commands in the project directory:
pod repo-art update tomtom-sdk-cocoapodspod install --repo-update
- Import the following frameworks in your project’s
<ProjectName>App.swift
file:1// System modules.2import Combine3import CoreLocation4import SwiftUI56// TomTomSDK modules.7import TomTomSDKCommonUI8import TomTomSDKDefaultTextToSpeech9import TomTomSDKLocationProvider10import TomTomSDKMapDisplay11import TomTomSDKNavigation12import TomTomSDKNavigationEngines13import TomTomSDKNavigationOnline14import TomTomSDKNavigationTileStore15import TomTomSDKNavigationUI16import TomTomSDKRoute17import TomTomSDKRoutePlanner18import TomTomSDKRoutePlannerOnline19import TomTomSDKRoutingCommon
With the dependencies set up, you can proceed to the next step and start displaying the map.
Combine all the code snippets from this tutorial into a single Swift file, such as your project’s 'App.swift' file. The code only successfully compiles at the end of each section.
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:
1// Replace the content of the App struct in your Xcode project with the content of the provided snippet.2@main3struct MyNavigationApp: App {4 var body: some Scene {5 WindowGroup {6 ContentView()7 .ignoresSafeArea(edges: [.top, .bottom])8 }9 }1011 init() {12 TomTomSDKMapDisplay.MapsDisplayService.apiKey = Keys.tomtomApiKey13 }14}
Step 2: Map UI setup
Construct the MapCoordinator
class that is used to handle map events:
1final class MapCoordinator {2 init(mapView: TomTomSDKMapDisplay.MapView) {3 self.mapView = mapView4 }56 private let mapView: TomTomSDKMapDisplay.MapView7 private var map: TomTomSDKMapDisplay.TomTomMap?8}
Construct the TomTomMapView
structure, which contains the map view setup:
1struct TomTomMapView {2 var mapView = TomTomSDKMapDisplay.MapView()3}
Step 3: SwiftUI integration
Extend the TomTomMapView
struct to conform to the UIViewRepresentable
protocol. This allows you to use the TomTomMapView
in SwiftUI:
1extension TomTomMapView: UIViewRepresentable {2 func makeUIView(context: Context) -> TomTomSDKMapDisplay.MapView {3 return mapView4 }56 func updateUIView(_: TomTomSDKMapDisplay.MapView, context: Context) {}78 func makeCoordinator() -> MapCoordinator {9 MapCoordinator(mapView: mapView)10 }11}
Step 4: Displaying the map
Replace the ContentView structure declaration, enabling the presentation of the TomTomMapView on the application screen:
1struct ContentView: View {2 var body: some View {3 ZStack(alignment: .bottom) {4 TomTomMapView()5 }6 }7}
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:
NSLocationAlwaysUsageDescription
NSLocationAlwaysAndWhenInUseUsageDescription
NSLocationWhenInUseUsageDescription
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:
1final class MapCoordinator {2 init(mapView: TomTomSDKMapDisplay.MapView) {3 self.mapView = mapView4 }56 private let mapView: TomTomSDKMapDisplay.MapView7 private var map: TomTomSDKMapDisplay.TomTomMap?8 private var cameraUpdated = false9}
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
:
1extension MapCoordinator {2 private var defaultCameraUpdate: CameraUpdate {3 let defaultLocation = CLLocation(latitude: 0.0, longitude: 0.0)4 return CameraUpdate(5 position: defaultLocation.coordinate,6 zoom: 1.0,7 tilt: 0.0,8 rotation: 0.0,9 positionMarkerVerticalOffset: 0.010 )11 }1213 func animateCamera(14 zoom: Double,15 position: CLLocationCoordinate2D,16 animationDurationInSeconds: TimeInterval,17 onceOnly: Bool18 ) {19 if onceOnly, cameraUpdated {20 return21 }22 cameraUpdated = true23 var cameraUpdate = defaultCameraUpdate24 cameraUpdate.zoom = zoom25 cameraUpdate.position = position26 map?.applyCamera(27 cameraUpdate,28 animationDuration: animationDurationInSeconds29 )30 }31}
Step 5: Following user location
Extend the MapCoordinator
class to conform to the TomTomSDKLocationProvider.LocationUpdateObserver
protocol to observe GPS updates:
1extension MapCoordinator: TomTomSDKLocationProvider.LocationUpdateObserver {2 func didUpdateLocation(location: GeoLocation) {3 // Zoom and center the camera on the first location received.4 animateCamera(5 zoom: 9.0,6 position: location.location.coordinate,7 animationDurationInSeconds: 1.5,8 onceOnly: true9 )10 }11}
Step 6: Starting location updates
Add an extension to MapCoordinator
that conforms to TomTomSDKMapDisplay.MapDelegate
:
1extension MapCoordinator: TomTomSDKMapDisplay.MapDelegate {2 func map(_ map: TomTomMap, onInteraction interaction: MapInteraction) {3 // Handle map interactions.4 }56 func map(_ map: TomTomMap, onCameraEvent event: CameraEvent) {7 // Handle camera events.8 }9}
In your MapCoordinator
conform to the TomTomSDKMapDisplay.MapViewDelegate
protocol, update the MapViewDelegate.mapView(_:onMapReady:)
method to start observing location updates:
1extension MapCoordinator: TomTomSDKMapDisplay.MapViewDelegate {2 func mapView(_ mapView: MapView, onMapReady map: TomTomMap) {3 // Store the map to be used later.4 self.map = map56 // Observe map events.7 map.delegate = self89 // Observe location updates.10 map.locationProvider.addObserver(self)1112 // Display a chevron.13 map.locationIndicatorType = .navigationChevron(scale: 1)1415 // Activate the GPS location engine in TomTomSDK.16 map.activateLocationProvider()1718 // Configure the camera to center on the current location.19 map.applyCamera(defaultCameraUpdate)20 }2122 func mapView(23 _ mapView: MapView,24 onStyleLoad result: Result<StyleContainer, Error>25 ) {26 print("Style loaded")27 }28}
Finally, find the TomTomMapView
extension that conforms to UIViewRepresentable
and set the MapView
delegate:
1extension TomTomMapView: UIViewRepresentable {2 func makeUIView(context: Context) -> TomTomSDKMapDisplay.MapView {3 mapView.delegate = context.coordinator4 return mapView5 }67 func updateUIView(_: TomTomSDKMapDisplay.MapView, context: Context) {}89 func makeCoordinator() -> MapCoordinator {10 MapCoordinator(mapView: mapView)11 }12}
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:
1final class NavigationController: ObservableObject {2 convenience init() {3 let routePlanner = TomTomSDKRoutePlannerOnline4 .OnlineRoutePlanner(apiKey: Keys.tomtomApiKey)5 let locationProvider = DefaultCLLocationProvider()67 self.init(8 locationProvider: locationProvider,9 routePlanner: routePlanner10 )11 }1213 init(14 locationProvider: LocationProvider,15 routePlanner: TomTomSDKRoutePlannerOnline.OnlineRoutePlanner16 ) {17 self.locationProvider = locationProvider18 self.routePlanner = routePlanner19 self.locationProvider.enable()20 }2122 let locationProvider: LocationProvider23 let routePlanner: TomTomSDKRoutePlannerOnline.OnlineRoutePlanner2425 var routePlanningOptions: RoutePlanningOptions?2627 let displayedRouteSubject = PassthroughSubject<28 TomTomSDKRoute.Route?,29 Never30 >()31}
Update your TomTomMapView
by adding a NavigationController
:
1struct TomTomMapView {2 var mapView = TomTomSDKMapDisplay.MapView()3 var navigationController: NavigationController4}
Update the TomTomMapView
UIViewRepresentable
implementation:
1extension TomTomMapView: UIViewRepresentable {2 func makeUIView(context: Context) -> TomTomSDKMapDisplay.MapView {3 mapView.delegate = context.coordinator4 return mapView5 }67 func updateUIView(_: TomTomSDKMapDisplay.MapView, context: Context) {}89 func makeCoordinator() -> MapCoordinator {10 MapCoordinator(11 mapView: mapView,12 navigationController: navigationController13 )14 }15}
Update the TomTomMapView
initializer in your ContentView
by adding a NavigationController
:
1struct ContentView: View {2 @ObservedObject3 var navigationController = NavigationController()45 var body: some View {6 ZStack(alignment: .bottom) {7 TomTomMapView(navigationController: navigationController)8 }9 }10}
Step 2: Planning a route
Include the RoutePlanner.planRoute(options:onRouteReady:completion:)
method, which calculates a route between specified origin and destination coordinates:
1extension NavigationController {2 enum RoutePlanError: Error {3 case unableToPlanRoute4 }56 private func planRoute(7 from origin: CLLocationCoordinate2D,8 to destination: CLLocationCoordinate2D9 ) async throws10 -> TomTomSDKNavigationEngines.RoutePlan {11 routePlanningOptions = try createRoutePlanningOptions(12 from: origin,13 to: destination14 )15 let route = try await planRoute(16 withRoutePlanner: routePlanner,17 routePlanningOptions: routePlanningOptions!18 )19 return TomTomSDKNavigationEngines20 .RoutePlan(route: route, routePlanningOptions: routePlanningOptions!)21 }2223 private func createRoutePlanningOptions(24 from origin: CLLocationCoordinate2D,25 to destination: CLLocationCoordinate2D26 )27 throws -> TomTomSDKRoutingCommon.RoutePlanningOptions {28 let itinerary = Itinerary(29 origin: ItineraryPoint(coordinate: origin),30 destination: ItineraryPoint(coordinate: destination)31 )32 let costModel = CostModel(routeType: .fast)3334 let locale = Locale(identifier: "en-GB")35 let guidanceOptions = try GuidanceOptions(36 instructionType: .tagged,37 language: locale,38 roadShieldReferences: .all,39 announcementPoints: .all,40 phoneticsType: .IPA,41 progressPoints: .all42 )4344 let options = try RoutePlanningOptions(45 itinerary: itinerary,46 costModel: costModel,47 guidanceOptions: guidanceOptions48 )49 return options50 }5152 private func planRoute(53 withRoutePlanner routePlanner: OnlineRoutePlanner,54 routePlanningOptions: RoutePlanningOptions55 ) async throws56 -> TomTomSDKRoute.Route {57 return try await withCheckedThrowingContinuation {58 (continuation: CheckedContinuation<TomTomSDKRoute.Route, Error>) in5960 routePlanner.planRoute(61 options: routePlanningOptions,62 onRouteReady: nil63 ) { result in64 switch result {65 case let .failure(error):66 continuation.resume(throwing: error)67 case let .success(response):68 guard let route = response.routes?.first else {69 continuation70 .resume(throwing: RoutePlanError.unableToPlanRoute)71 return72 }73 continuation.resume(returning: route)74 }75 }76 }77 }78}
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:
1extension NavigationController {2 func planAndDisplayRoute(destination: CLLocationCoordinate2D) {3 Task { @MainActor in4 do {5 // Plan the route and add it to the map6 guard let start = locationProvider.lastKnownLocation?.location.coordinate7 else { return }8 let routePlan = try await planRoute(9 from: start,10 to: destination11 )1213 let route = routePlan.route14 self.displayedRouteSubject.send(route)15 } catch {16 print("Error when planning a route: \(error)")17 }18 }19 }20}
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:
1extension MapCoordinator: TomTomSDKMapDisplay.MapDelegate {2 func map(_ map: TomTomMap, onInteraction interaction: MapInteraction) {3 switch interaction {4 case let .longPressed(coordinate):5 navigationController.planAndDisplayRoute(destination: coordinate)6 default:7 // Handle other gestures.8 break9 }10 }1112 func map(_ map: TomTomMap, onCameraEvent event: CameraEvent) {13 // Handle camera events.14 }15}
Extend the MapCoordinator
with the MapCoordinator.observe(navigationController:)
method that will display the route on the map after it is planned:
1extension MapCoordinator {2 func observe(navigationController: NavigationController) {3 navigationController.displayedRouteSubject.sink { [weak self] route in4 guard let self = self else { return }5 if let route = route {6 self.addRouteToMap(route: route)7 } else {8 self.routeOnMap = nil9 self.map?.removeRoutes()10 }11 }.store(in: &cancellableBag)12 }13}
Execute the MapCoordinator.observe(navigationController:)
method in the MapCoordinator
initializer:
1final class MapCoordinator {2 init(3 mapView: TomTomSDKMapDisplay.MapView,4 navigationController: NavigationController5 ) {6 self.mapView = mapView7 self.navigationController = navigationController8 observe(navigationController: navigationController)9 }1011 private let mapView: TomTomSDKMapDisplay.MapView12 private var map: TomTomSDKMapDisplay.TomTomMap?13 private var cameraUpdated = false14 private let navigationController: NavigationController15 private var routeOnMap: TomTomSDKMapDisplay.Route?16 private var cancellableBag = Set<AnyCancellable>()17}
Extend the functionality of MapCoordinator
by adding methods for drawing routes on the map and configuring camera tracking modes:
1extension MapCoordinator {2 private func createMapRouteOptions(3 coordinates: [CLLocationCoordinate2D]4 )5 -> TomTomSDKMapDisplay.RouteOptions {6 var routeOptions = RouteOptions(coordinates: coordinates)7 routeOptions.outlineWidth = 18 routeOptions.routeWidth = 59 routeOptions.color = .activeRoute10 return routeOptions11 }1213 func addRouteToMap(route: TomTomSDKRoute.Route) {14 // Create the route options from the route geometry15 // and adds it to the map.16 let routeOptions = createMapRouteOptions(coordinates: route.geometry)17 if let routeOnMap = try? map?.addRoute(routeOptions) {18 self.routeOnMap = routeOnMap1920 // Zoom the map to make the route visible.21 map?.zoomToRoutes(padding: 32)22 }23 }2425 func setCamera(trackingMode: TomTomSDKMapDisplay.CameraTrackingMode) {26 map?.cameraTrackingMode = trackingMode2728 // Update the chevron position on the screen29 // so it is not hidden behind the navigation panel.30 if trackingMode == .followRouteDirection() || trackingMode == .followNorthUp() {31 let cameraUpdate = CameraUpdate(positionMarkerVerticalOffset: 0.4)32 map?.moveCamera(cameraUpdate)33 }34 }35}
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:
1<plist version="1.0">2<dict>3 <key>UIBackgroundModes</key>4 <array>5 <string>fetch</string>6 </array>7</dict>8</plist>
Step 2: Setting up navigation
Add a TomTomNavigation
object to the NavigationController
to process location updates and convert them into navigation events:
1final class NavigationController: ObservableObject {2 convenience init() {3 let textToSpeech = try! SystemTextToSpeechEngine()4 let routePlanner = TomTomSDKRoutePlannerOnline5 .OnlineRoutePlanner(apiKey: Keys.tomtomApiKey)6 let locationProvider = DefaultCLLocationProvider()7 let delay = Measurement.tt.seconds(1)8 let simulatedLocationProvider = SimulatedLocationProvider(delay: delay)9 let navigationConfiguration = OnlineTomTomNavigationFactory10 .Configuration(11 navigationTileStore: try! NavigationTileStore(config: NavigationTileStoreConfiguration(12 apiKey: Keys13 .tomtomApiKey14 )),15 locationProvider: simulatedLocationProvider,16 routePlanner: routePlanner,17 betterProposalAcceptanceMode: .automatic18 )19 let navigation = try! OnlineTomTomNavigationFactory20 .create(configuration: navigationConfiguration)21 let navigationModel = TomTomSDKNavigationUI.NavigationView22 .ViewModel(navigation, tts: textToSpeech)2324 self.init(25 locationProvider: locationProvider,26 simulatedLocationProvider: simulatedLocationProvider,27 routePlanner: routePlanner,28 navigation: navigation,29 navigationModel: navigationModel30 )31 }3233 init(34 locationProvider: LocationProvider,35 simulatedLocationProvider: SimulatedLocationProvider,36 routePlanner: TomTomSDKRoutePlannerOnline.OnlineRoutePlanner,37 navigation: TomTomSDKNavigation.TomTomNavigation,38 navigationModel: TomTomSDKNavigationUI.NavigationView.ViewModel39 ) {40 self.locationProvider = locationProvider41 self.simulatedLocationProvider = simulatedLocationProvider42 self.routePlanner = routePlanner43 self.navigation = navigation44 self.navigationViewModel = navigationModel4546 self.navigation.addProgressObserver(self)47 self.navigation.addRouteAddObserver(self)48 self.navigation.addRouteRemoveObserver(self)49 self.navigation.addRouteUpdateObserver(self)50 self.navigation.addActiveRouteChangeObserver(self)51 locationProvider.enable()52 }5354 let locationProvider: LocationProvider55 let simulatedLocationProvider: SimulatedLocationProvider56 let routePlanner: TomTomSDKRoutePlannerOnline.OnlineRoutePlanner57 let navigation: TomTomSDKNavigation.TomTomNavigation58 let navigationViewModel: TomTomSDKNavigationUI.NavigationView.ViewModel5960 var routePlanningOptions: RoutePlanningOptions?61 var displayedRoutes: [UUID: TomTomSDKRoute.Route] = [:]6263 let displayedRouteSubject = PassthroughSubject<64 TomTomSDKRoute.Route?,65 Never66 >()67 let navigateRouteSubject = PassthroughSubject<68 TomTomSDKRoute.Route?,69 Never70 >()71 let progressOnRouteSubject = PassthroughSubject<72 Measurement<UnitLength>,73 Never74 >()75 let mapMatchedLocationProvider = PassthroughSubject<76 LocationProvider,77 Never78 >()7980 @Published81 var showNavigationView: Bool = false82}
Extend the NavigationController
with a navigateOn(route: TomTomSDKRoute.Route)
method. It will start navigation on the selected route:
1extension NavigationController {2 func navigateOn(route: TomTomSDKRoute.Route) {3 Task { @MainActor in4 guard let routePlanningOptions else { return }56 self.displayedRouteSubject.send(nil) // clear previous route after replanning78 self.displayedRouteSubject.send(route)910 let routePlan = RoutePlan(route: route, routePlanningOptions: routePlanningOptions)1112 let navigationOptions = NavigationOptions(13 activeRoutePlan: routePlan14 )15 self.navigationViewModel.start(navigationOptions)16 // Use simulated location updates.17 self.simulatedLocationProvider18 .updateCoordinates(route.geometry, interpolate: true)19 self.simulatedLocationProvider.enable()20 self.mapMatchedLocationProvider21 .send(navigation.mapMatchedLocationProvider)2223 self.navigateRouteSubject.send(route)24 self.showNavigationView = true25 }26 }2728 func stopNavigating() {29 displayedRouteSubject.send(nil)30 navigationViewModel.stop()31 simulatedLocationProvider.disable()32 showNavigationView = false33 }34}
Step 3: Starting navigation on the selected route
Extend the NavigationController
with RouteDelegate
. This lets you know when a user presses on the route:
1extension NavigationController: RouteDelegate {2 func mapRoute(3 _ route: TomTomSDKMapDisplay.Route,4 didTapOnCoordinate coordinate: CLLocationCoordinate2D,5 waypointIndex: Int6 ) {7 guard let route = displayedRoutes[route.routeID]8 else { return }910 navigateOn(route: route)11 }12}
Update the MapCoordinator.addRouteToMap(route:)
method to add the RouteDelegate
.
1func addRouteToMap(route: TomTomSDKRoute.Route, zoom: Bool = false) {2 // Create the route options from the route geometry3 // and add it to the map.4 let routeOptions = createMapRouteOptions(coordinates: route.geometry)5 if let routeOnMap = try? map?.addRoute(routeOptions) {6 self.routeOnMap = routeOnMap7 navigationController.displayedRoutes[routeOnMap.routeID] = route8 routeOnMap.delegate = navigationController910 // Zoom the map to make the route visible.11 if !navigationController.showNavigationView {12 map?.zoomToRoutes(padding: 32) // zoom to the route if no navigation in progress13 }14 }15}
Step 4: Observing navigation events
Extend NavigationController
to conform to NavigationProgressObserver
to enable progress updates during navigation:
1extension NavigationController: TomTomSDKNavigation.NavigationProgressObserver {2 func didUpdateProgress(progress: RouteProgress) {3 progressOnRouteSubject.send(progress.distanceAlongRoute)4 }5}
Extend the NavigationController
class to handle navigation events and ensure the use of TomTomSDKNavigationUI.NavigationView.ViewModel
for displaying both visual and voice instructions:
1extension NavigationController {2 func onNavigationViewAction(3 _ action: TomTomSDKNavigationUI.NavigationView.Action4 ) {5 switch action {6 case let .arrival(action):7 onArrivalAction(action)8 case let .instruction(action):9 onInstructionAction(action)10 case let .confirmation(action):11 onConfirmationAction(action)12 case let .error(action):13 onErrorAction(action)14 @unknown default:15 /* YOUR CODE GOES HERE */16 break17 }18 }1920 fileprivate func onArrivalAction(21 _ action: TomTomSDKNavigationUI.NavigationView.ArrivalAction22 ) {23 switch action {24 case .close:25 stopNavigating()26 @unknown default:27 /* YOUR CODE GOES HERE */28 break29 }30 }3132 fileprivate func onInstructionAction(33 _ action: TomTomSDKNavigationUI.NavigationView.InstructionAction34 ) {35 switch action {36 case let .tapSound(muted):37 navigationViewModel.muteTextToSpeech(mute: !muted)38 case .tapLanes:39 navigationViewModel.hideLanes()40 case .tapThen:41 navigationViewModel.hideCombinedInstruction()42 @unknown default:43 /* YOUR CODE GOES HERE */44 break45 }46 }4748 fileprivate func onConfirmationAction(49 _ action: TomTomSDKNavigationUI.NavigationView.ConfirmationAction50 ) {51 switch action {52 case .yes:53 stopNavigating()54 case .no:55 /* YOUR CODE GOES HERE */56 break57 @unknown default:58 /* YOUR CODE GOES HERE */59 break60 }61 }6263 fileprivate func onErrorAction(64 _ action: TomTomSDKNavigationUI.NavigationView.ErrorAction65 ) {66 /* YOUR CODE GOES HERE */67 }68}
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
1extension NavigationController: TomTomSDKNavigation.NavigationRouteAddObserver,2 TomTomSDKNavigation.NavigationRouteRemoveObserver,3 TomTomSDKNavigation.NavigationActiveRouteChangeObserver,4 TomTomSDKNavigation.NavigationRouteUpdateObserver {5 func didAddRoute(6 route: TomTomSDKRoute.Route,7 options: TomTomSDKRoutingCommon.RoutePlanningOptions,8 reason: TomTomSDKNavigation.RouteAddedReason9 ) {10 let triggeringReasons: [RouteAddedReason] = [.avoidBlockage, .deviated, .manuallyUpdated, .withinRange]11 if triggeringReasons.contains(reason) {12 displayedRouteSubject.send(nil) // clear previous route after replanning13 displayedRouteSubject.send(route)14 }15 }1617 func didRemoveRoute(route: TomTomSDKRoute.Route, reason: TomTomSDKNavigation.RouteRemovedReason) {}1819 func didChangeActiveRoute(route: TomTomSDKRoute.Route) {}2021 func didUpdateRoute(route: TomTomSDKRoute.Route, reason: TomTomSDKNavigation.RouteUpdatedReason) {}22}
Update the MapCoordinator.observe(navigationController:)
method to process progress and LocationProvider
updates:
1extension MapCoordinator {2 func observe(navigationController: NavigationController) {3 navigationController.displayedRouteSubject.sink { [weak self] route in4 guard let self = self else { return }5 if let route = route {6 self.addRouteToMap(route: route)7 } else {8 self.navigationController.displayedRoutes = [:]9 self.routeOnMap = nil10 self.map?.removeRoutes()11 }12 }.store(in: &cancellableBag)1314 navigationController.navigateRouteSubject15 .sink { [weak self] route in16 guard let self = self else { return }17 if let route = route {18 self.setCamera(trackingMode: .followRouteDirection())19 } else {20 self.setCamera(trackingMode: .followNorthUp())21 }22 }.store(in: &cancellableBag)2324 navigationController.progressOnRouteSubject25 .sink { [weak self] progress in26 self?.routeOnMap?.progressOnRoute = progress27 }.store(in: &cancellableBag)2829 navigationController.mapMatchedLocationProvider30 .sink { [weak self] locationProvider in31 self?.map?.locationProvider = locationProvider32 }.store(in: &cancellableBag)33 }34}
Step 5: Displaying navigation UI
Include a NavigationView
in your ContentView
to enable the display of navigation-related information:
1struct ContentView: View {2 @ObservedObject3 var navigationController = NavigationController()45 var body: some View {6 ZStack(alignment: .bottom) {7 TomTomMapView(navigationController: navigationController)8 if navigationController.showNavigationView {9 NavigationView(10 navigationController.navigationViewModel,11 action: navigationController.onNavigationViewAction12 )13 }14 }15 }16}
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: