Build a navigation app

VERSION 0.2.1855
PUBLIC PREVIEW

This tutorial shows how to build a simple navigation application using the TomTom Navigation SDK for iOS. The app uses the built-in UI components. You can also build custom components and integrate them with the SDK.

The application displays a map that shows the current location. After the user selects a destination with a long press, the app plans a route and draws it on the map. Navigation is started automatically using the GPS location or a route simulation.

Project set up

The Navigation SDK for iOS is only available upon request. Contact us to get started.

  1. Install Xcode if you don’t already have it.

  2. Create a new project or open an existing one. The application deployment target has to be set to at least 13.0.

  3. Install Cocoapods on your computer.

  4. Install cocoapods-art tool on your computer.

  5. Because the repository for Navigation SDK is private, you will need to contact us to get access. Once you have obtained access, go to repositories.tomtom.com and log in with your account. Expand the user menu in the top-right corner, and select "Edit profile" → "Generate an Identity Token". Copy your token and put it, together with your login, in ~/.netrc. If the file doesn’t exist, create one and add the following entry:

    1machine repositories.tomtom.com
    2login <YOUR_LOGIN>
    3password <YOUR_TOKEN>
  6. Add a reference to the cocoapods-art repository:

    pod repo-art add tomtom-sdk-cocoapods "https://repositories.tomtom.com/artifactory/api/pods/cocoapods"
  7. Then create a Podfile in the project folder. The pod init command in the project folder can generate a basic podfile.

  8. At the top of the Podfile add the source of the SDK Cocoapods.

    1plugin 'cocoapods-art', :sources => [
    2 'tomtom-sdk-cocoapods'
    3]
  9. Add the modules that your project requires. This tutorial uses the TomTomSDKMapsDisplay, TomTomSDKOnlineRouting, TomTomSDKRouting, TomTomSDKNavigationEngine, TomTomSDKUIComponents, TomTomSDKSystemTextToSpeechEngine, TomTomSDKTextToSpeechEngine and TomTomSDKTextToSpeech modules.

    1TOMTOM_SDK_VERSION = '{version}'
    2
    3target 'YourAppTarget' do
    4 use_frameworks!
    5 pod 'TomTomSDKMapsDisplay', TOMTOM_SDK_VERSION
    6 pod 'TomTomSDKNavigationEngine', TOMTOM_SDK_VERSION
    7 pod 'TomTomSDKRouting', TOMTOM_SDK_VERSION
    8 pod 'TomTomSDKOnlineRouting', TOMTOM_SDK_VERSION
    9 pod 'TomTomSDKUIComponents', TOMTOM_SDK_VERSION
    10 pod 'TomTomSDKSystemTextToSpeechEngine', TOMTOM_SDK_VERSION
    11 pod 'TomTomSDKTextToSpeechEngine', TOMTOM_SDK_VERSION
    12 pod 'TomTomSDKTextToSpeech', TOMTOM_SDK_VERSION
    13end

Make sure you have the appropriate TomTom API key. If you don’t have an API Key, go to How to get a TomTom API Key to learn how to create one.

Getting started

Create an empty App project with Xcode 13.4 or higher, using a SwiftUI interface that targets iOS 15 and above. Copy the snippets below into the *App.swift file that Xcode generated for the project.

  1. Create the basic Swift application structure as follows:

    1// System modules
    2import CoreLocation
    3import Foundation
    4import SwiftUI
    5
    6// GO SDK modules
    7import TomTomSDKCommon
    8import TomTomSDKLocation
    9import TomTomSDKMapsDisplay
    10import TomTomSDKNavigationEngine
    11import TomTomSDKOnlineRouting
    12import TomTomSDKRoute
    13import TomTomSDKRouting
    14import TomTomSDKSystemTextToSpeechEngine
    15import TomTomSDKTextToSpeech
    16import TomTomSDKTextToSpeechEngine
    17import TomTomSDKUIComponents
    18
    19// MARK: - TomTomServices
    20
    21enum TomTomServices {
    22 static func register() {
    23 TomTomSDKMapsDisplay.MapsDisplayService.mapsKey = Keys.ttServicesAPIKey
    24 TomTomSDKOnlineRouting.RoutingOnlineService.routingKey = Keys.ttServicesAPIKey
    25 }
    26}
    27
    28// MARK: - AppDelegate
    29
    30class AppDelegate: NSObject, UIApplicationDelegate {
    31 func application(
    32 _ application: UIApplication,
    33 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    34 )
    35 -> Bool {
    36 TomTomServices.register()
    37 return true
    38 }
    39}
    40
    41// MARK: - TomTomSDKNavigationUseCaseApp
    42
    43@main
    44struct TomTomSDKNavigationUseCaseApp: App {
    45 @UIApplicationDelegateAdaptor(AppDelegate.self)
    46 var appDelegate
    47
    48 var body: some Scene {
    49 WindowGroup {
    50 MainView()
    51 }
    52 }
    53}
    54
    55// MARK: - MainView
    56
    57struct MainView: View {
    58 // MARK: Internal
    59
    60 var body: some View {
    61 MapView(contentInsets: $contentInsets)
    62 }
    63
    64 // MARK: Private
    65
    66 @State
    67 private var contentInsets: EdgeInsets = .init()
    68}
    69
    70// MARK: - MapView
  2. Add a struct to hold your API key:

    1struct Keys {
    2 static let ttServicesAPIKey = "YOUR_API_KEY"
    3}

Key storage in the sourcecode is for demo purposes only. Remember to securely store your API keys when creating your app.

These code snippets setup the App struct and AppDelegate which registers the TomTom API key with the map display and online routing services. The MapView is then created in the next section.

Displaying a map

This section explains how to display a map and interact with it. Copy the snippets below into your Xcode project to complete this part of the tutorial.

  1. Create the MapView struct:

    1struct MapView {
    2 var mapView = TomTomMapView()
    3 var contentInsets: Binding<EdgeInsets>
    4
    5 func updateEdgeInsets(context: Context) {
    6 context.coordinator.setContentInsets(edgeInsets: contentInsets.wrappedValue)
    7 }
    8}
    9
    10// MARK: UIViewRepresentable
  2. Exend the MapView to conform to the UIViewRepresentable protocol. This allows us to use the TomTomMapView in SwiftUI:

    1extension MapView: UIViewRepresentable {
    2 typealias UIViewType = TomTomSDKMapsDisplay.TomTomMapView
    3
    4 func makeUIView(context: Context) -> TomTomMapView {
    5 mapView.delegate = context.coordinator
    6 mapView.currentLocationButtonVisibilityPolicy = .hiddenWhenCentered
    7 mapView.compassButtonVisibilityPolicy = .visibleWhenNeeded
    8 return mapView
    9 }
    10
    11 func updateUIView(_: TomTomMapView, context: Context) {
    12 updateEdgeInsets(context: context)
    13 }
    14
    15 func makeCoordinator() -> MapCoordinator {
    16 MapCoordinator(mapView)
    17 }
    18}
    19
    20// MARK: - MapCoordinator
  3. Create the MapCoordinator, which facilitates communication from the UIView to the SwiftUI environments.

    1class MapCoordinator: NSObject {
    2 // MARK: Lifecycle
    3
    4 init(_ mapView: TomTomMapView) {
    5 self.mapView = mapView
    6 }
    7
    8 // MARK: Internal
    9
    10 func setContentInsets(edgeInsets: EdgeInsets) {
    11 mapView.contentInsets = NSDirectionalEdgeInsets(
    12 top: edgeInsets.top,
    13 leading: edgeInsets.leading,
    14 bottom: edgeInsets.bottom,
    15 trailing: edgeInsets.trailing
    16 )
    17 }
    18
    19 // MARK: Private
    20
    21 private var mapView: TomTomSDKMapsDisplay.TomTomMapView
    22 private var map: TomTomSDKMapsDisplay.TomTomMap! // Set in onMapReady callback
    23 private var cameraUpdated = false
    24 private let routingService = TomTomSDKOnlineRouting.TomTomRoutingService()
    25 private var navigation: TomTomSDKNavigationEngine.Navigation?
    26 private let tts = TTS()
    27}
    28
    29// MARK: TomTomSDKMapsDisplay.TomTomMapViewDelegate
  4. Extend the MapCoordinator to conform to the TomTomMapViewDelegate protocol by implementing the onMapReady callback. The onMapReady callback notifies the MapCoordinator that the TomTomMap is ready to display. The TomTomMap instance can be configured in this callback function:

    1extension MapCoordinator: TomTomSDKMapsDisplay.TomTomMapViewDelegate {
    2 func tomTomMapView(_ mapView: TomTomMapView, onMapReady map: TomTomMap) {
    3 // Store the map to be used later
    4 self.map = map
    5
    6 // Observe TomTom map actions
    7 map.delegate = self
    8 // Observe location engine updates
    9 map.locationEngine.addObserver(self)
    10 // Hide the traffic on the map
    11 map.hideTraffic()
    12 // Don't display any GPS accuracy information
    13 map.accuracyIndicatorType = .none
    14 // Display a chevron at the current location
    15 map.locationIndicatorType = .navigationChevron(scale: 1)
    16 // Activate the GPS location engine in TomTomSDK.
    17 map.activateLocationEngine()
    18 // Configure the camera to centre on the current location
    19 map.cameraOptions = defaultCameraOptions
    20 }
    21}
  5. Next create the camera utility functions. The virtual map is observed through a camera that can be zoomed, panned, rotated, and tilted to provide a compelling 3D navigation experience.

    1extension MapCoordinator {
    2 private var defaultCameraOptions: CameraOptions {
    3 let defaultLocation = CLLocation(latitude: 0.0, longitude: 0.0)
    4 return CameraOptions(
    5 position: defaultLocation.coordinate,
    6 zoom: 1.0,
    7 tilt: 0.0,
    8 rotation: 0.0,
    9 positionMarkerVerticalOffset: 0.0
    10 )
    11 }
    12
    13 func animateCamera(zoom: Double, position: CLLocationCoordinate2D, animationDurationInSeconds: TimeInterval, onceOnly: Bool) {
    14 if onceOnly, cameraUpdated {
    15 return
    16 }
    17 cameraUpdated = true
    18 DispatchQueue.main.async {
    19 var cameraOptions = self.defaultCameraOptions
    20 cameraOptions.zoom = zoom
    21 cameraOptions.position = position
    22 self.map.animateCamera(cameraOptions, animationDuration: animationDurationInSeconds)
    23 }
    24 }
    25
    26 func setCamera(trackingMode: TomTomSDKMapsDisplay.CameraTrackingMode) {
    27 map?.cameraTrackingMode = .followWithHeading
    28 }
    29}
    30
    31// MARK: TomTomSDKLocation.LocationEngineObserver

    The remaining steps in this section ensure the project builds without compiler errors.

  6. Extend MapCoordinator to conform to TomTomMapDelegate and stub the function to handle a long press on the map:

    1extension MapCoordinator: TomTomSDKMapsDisplay.TomTomMapDelegate {
    2 func tomTomMap(_: TomTomSDKMapsDisplay.TomTomMap, didLongPressOn coordinate: CLLocationCoordinate2D) {
    3 // Handle long press
    4 }
    5}
  7. Extend MapCoordinator to conform to LocationEngineObserver by stubbing the following functions:

    1extension MapCoordinator: TomTomSDKLocation.LocationEngineObserver {
    2 func onLocationUpdated(location: GeoLocation) {
    3 // Handle location updates
    4 }
    5
    6 func onHeadingUpdate(newHeading: CLHeading, lastLocation: GeoLocation) {
    7 // Handle heading updates
    8 }
    9
    10 func onAuthorizationStatusChanged(isGranted: Bool) {
    11 // Handle authorization changes
    12 }
    13}
  8. Add an empty declaration for the TTS class.

    1class TTS {
    2 // Empty
    3}

You can now build and run the app on a device or simulator to display a zoomed-out map. You can interact with the map by dragging, pinching or using a two-fingered rotation gesture.

Showing the current location

This section explains how to update the camera position to show the current location, and mark the current location on the map.

  1. Replace the MapCoordinator extension that conformed to the LocationEngineObserver protocol with the following:

    1extension MapCoordinator: TomTomSDKLocation.LocationEngineObserver {
    2 func onLocationUpdated(location: GeoLocation) {
    3 // Zoom and center the camera on the first location received.
    4 animateCamera(zoom: 9.0, position: location.location.coordinate, animationDurationInSeconds: 1.5, onceOnly: true)
    5 }
    6
    7 func onHeadingUpdate(newHeading: CLHeading, lastLocation: GeoLocation) {
    8 // Handle heading updates
    9 }
    10
    11 func onAuthorizationStatusChanged(isGranted: Bool) {
    12 // Handle authorization changes
    13 }
    14}
    15
    16// MARK: TomTomMapDelegate

    This extension enables the MapCoordinator to observe GPS updates and authorization changes. This means that when the application starts, the camera position and zoom level are updated in the onLocationUpdated callback function. The user then sees the current location.

  2. Update the project’s info.plist keys to trigger the app to ask the user for location permission on startup. To do this in Xcode, select the projectthe targetInfo and add strings for the following keys:

    1NSLocationWhenInUseUsageDescription
    2NSLocationAlwaysUsageDescription
    3NSLocationAlwaysAndWhenInUseUsageDescription
  3. You can experiment with different ways of showing the current location on the map by changing the locationIndicatorType in the onMapReady callback. Activate the location engine to see the current GPS position on the map:

    1extension MapCoordinator: TomTomSDKMapsDisplay.TomTomMapViewDelegate {
    2 func tomTomMapView(_ mapView: TomTomMapView, onMapReady map: TomTomMap) {
    3 ...
    4
    5 // Display a chevron at the current location
    6 map.locationIndicatorType = .navigationChevron(scale: 1)
    7 // Activate the GPS location engine in TomTomSDK.
    8 map.activateLocationEngine()
    9 }
    10}

After completing these steps, build the app. The camera will zoom in on your current GPS location on startup. The location itself is indicated by the blue chevron. The map will display in portrait and landscape modes as the phone is rotated.

center

Planning a route

Now we will add the ability to plan a route to a location on the map by responding to the long press gesture.

  1. Update the MapCoordinator extension of TomTomMapDelegate to plan a route and add it to the map after a long press:

    1extension MapCoordinator: TomTomMapDelegate {
    2 func tomTomMap(_: TomTomSDKMapsDisplay.TomTomMap, didLongPressOn coordinate: CLLocationCoordinate2D) {
    3 Task { @MainActor in
    4 let routePlan = try await self.planRoute(to: coordinate)
    5 // First remove any old routes
    6 self.map?.removeRoutes()
    7 self.addRouteToMap(route: routePlan.route)
    8 }
    9 }
    10}

    The call to the async planRoute function is wrapped in a Task so that it can be called from the didLongPressOn delegate callback, even though that callback is not async.

  2. Now create a MapCoordinator exension for the route planning functions:

    1// MARK: Route planning
    2
    3extension MapCoordinator {
    4 enum RoutePlanError: Error {
    5 case unknownStartingLocation
    6 case unableToPlanRoute(_ description: String = "")
    7 }
    8
    9 private func createMapRouteOptions(coordinates: [CLLocationCoordinate2D]) -> TomTomSDKMapsDisplay.RouteOptions {
    10 var routeOptions = RouteOptions(coordinates: coordinates)
    11 routeOptions.outlineWidth = 1
    12 routeOptions.routeWidth = 5
    13 routeOptions.color = .activeRoute
    14 return routeOptions
    15 }
    16
    17 func createRoutingOptions(
    18 from origin: CLLocationCoordinate2D,
    19 to destination: CLLocationCoordinate2D
    20 )
    21 -> TomTomSDKRouting.RoutingOptions {
    22 let routingOptionsBuilder = RoutingOptionsBuilder(origin: origin, destination: destination)
    23 .with(routeType: .fast)
    24 // For voice announcements:
    25 .with(instructionsType: .tagged)
    26 .with(instructionAnnouncementPoints: .all)
    27 .with(instructionPhonetics: .IPA)
    28 .includeRoadShieldsInformation()
    29 .with(instructionRoadShieldReferences: .all)
    30 .includeSpeedLimits()
    31
    32 return routingOptionsBuilder.build()
    33 }
    34
    35 func planRoute(to destination: CLLocationCoordinate2D) async throws -> TomTomSDKNavigationEngine.RoutePlan {
    36 guard let currentLocation = map?.locationEngine.location?.location.coordinate else {
    37 throw RoutePlanError.unknownStartingLocation
    38 }
    39 return try await planRoute(from: currentLocation, to: destination)
    40 }
    41
    42 func planRoute(
    43 from origin: CLLocationCoordinate2D,
    44 to destination: CLLocationCoordinate2D
    45 ) async throws
    46 -> TomTomSDKNavigationEngine.RoutePlan {
    47 let routingOptions = createRoutingOptions(from: origin, to: destination)
    48 let route = try await planRoute(withRoutingService: routingService, routingOptions: routingOptions)
    49 return TomTomSDKNavigationEngine.RoutePlan(route: route, routingOptions: routingOptions)
    50 }
    51
    52 func planRoute(
    53 withRoutingService routingService: TomTomRoutingService,
    54 routingOptions: RoutingOptions
    55 ) async throws
    56 -> TomTomSDKRoute.Route {
    57 return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<TomTomSDKRoute.Route, Error>) in
    58 routingService.planRoute(options: routingOptions) { result in
    59 switch result {
    60 case let .failure(error):
    61 continuation.resume(throwing: error)
    62 case let .success(response):
    63 guard let routes = response.routes else {
    64 continuation.resume(throwing: RoutePlanError.unableToPlanRoute())
    65 return
    66 }
    67 guard let route = routes.first else {
    68 continuation.resume(throwing: RoutePlanError.unableToPlanRoute())
    69 return
    70 }
    71 continuation.resume(returning: route)
    72 }
    73 }
    74 }
    75 }
    76}
  3. Create a MapCoordinator extension to add the planned route to the map:

    1extension MapCoordinator {
    2 func addRouteToMap(route: TomTomSDKRoute.Route) {
    3 // Create the route options from the route geometry and add it to the map
    4 let routeOptions = createMapRouteOptions(coordinates: route.geometry)
    5 _ = try? map?.addRoute(routeOptions)
    6
    7 // Zoom the map to make the route visible
    8 map?.zoomToRoutes(padding: 32)
    9 }
    10}

You can build the app after completing these steps. The app can then plan a route from your current location to a destination selected with a long press on the map.

Route instruction

Starting navigation

This section explains how to start navigating along the planned route and simulate the journey.

  1. Create a MapCoordinator extension that contains the functions to start and configure navigation:

    1// MARK: Navigation
    2
    3extension MapCoordinator {
    4 func createSimulatedLocationEngine(route: TomTomSDKRoute.Route) -> LocationEngine {
    5 let simulatedLocationEngine = SimulatedLocationEngine(delay: 0.2)
    6 simulatedLocationEngine.updateCoordinates(route.geometry, interpolate: true)
    7 return simulatedLocationEngine
    8 }
    9
    10 func createNavigation(locationEngine: LocationEngine) -> TomTomSDKNavigationEngine.Navigation {
    11 let navigationConfiguration = NavigationConfigurationBuilder(
    12 navigationKey: Keys.ttServicesAPIKey,
    13 locationEngine: locationEngine,
    14 routingService: routingService
    15 )
    16 .with(routeReplanningMode: .automatic)
    17 .build()
    18 let navigation = Navigation(configuration: navigationConfiguration)
    19 // Set the TomTomSDKNavigationEngine.TomTomNavigationDelegate to receive navigation callbacks about the journey such as route instructions
    20 navigation.delegate = self
    21 return navigation
    22 }
    23
    24 func startNavigation(
    25 navigation: TomTomSDKNavigationEngine.Navigation,
    26 locationEngine: TomTomSDKLocation.LocationEngine,
    27 routePlan: TomTomSDKNavigationEngine.RoutePlan
    28 ) {
    29 // Set the navigation location engine
    30 navigation.stop()
    31 navigation.locationEngine = locationEngine
    32
    33 navigation.start(routePlan: routePlan) { error in
    34 print("Error starting navigation: \(error)")
    35 }
    36
    37 // The map matched location engine snaps to the route
    38 let locationEngine = navigation.mapMatchedLocationEngine
    39 map?.setCustomLocationEngine(locationEngine)
    40
    41 // Set the camera tracking mode to follow the current position
    42 setCamera(trackingMode: .followWithHeading)
    43 }
    44}
    45
    46// MARK: TomTomSDKNavigationEngine.TomTomNavigationDelegate
  2. Next update the didLongPressOn callback function from TomTomMapDelegate to plan the route and start navigating:

    1extension MapCoordinator: TomTomMapDelegate {
    2 func tomTomMap(_: TomTomSDKMapsDisplay.TomTomMap, didLongPressOn coordinate: CLLocationCoordinate2D) {
    3 Task { @MainActor in
    4 do {
    5 // Plan the route and add it to the map
    6 let routePlan = try await planRoute(to: coordinate)
    7 // First remove any old routes
    8 map?.removeRoutes()
    9 addRouteToMap(route: routePlan.route)
    10
    11 // Sart navigation after a short delay so that we can clearly see the transition to the driving view
    12 DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
    13 guard let self = self else {
    14 return
    15 }
    16
    17 // Use real location updates
    18 // let locationEngine = self.map.locationEngine
    19
    20 // Use simulated location updates
    21 let locationEngine = self.createSimulatedLocationEngine(route: routePlan.route)
    22
    23 let navigation = self.navigation ?? self.createNavigation(locationEngine: locationEngine)
    24 self.startNavigation(
    25 navigation: navigation,
    26 locationEngine: locationEngine,
    27 routePlan: routePlan
    28 )
    29 self.navigation = navigation
    30 // Start location updates
    31 locationEngine.start()
    32 }
    33 } catch {
    34 print("Error when planning a route: \(error)")
    35 }
    36 }
    37 }
    38
    39 func tomTomMap(_: TomTomSDKMapsDisplay.TomTomMap, gesture: TomTomSDKMapsDisplay.Gesture, didSendEvent _: GestureEvent) {
    40 // Handle gesture
    41 }
    42
    43 func tomTomMap(_: TomTomSDKMapsDisplay.TomTomMap, didChangeCameraTrackingMode mode: CameraTrackingMode) {
    44 // Handle camera tracking mode change
    45 }
    46
    47 func tomTomMapDidTapOnRecenterButton(_ map: TomTomSDKMapsDisplay.TomTomMap) {
    48 // Handle recenter map
    49 }
    50}

    The didLongPressOn callback function starts navigation by calling startNavigation with the desired location engine. Experiment by switching between real and simulated GPS location engines as shown in the code snippet above.

  3. Extend MapCoordinator to conform to the TomTomNavigationDelegate protocol. The delegate was set in the createNavigation function.

    1extension MapCoordinator: TomTomSDKNavigationEngine.TomTomNavigationDelegate {
    2 func tomTomNavigation(
    3 _ navigation: TomTomSDKNavigationEngine.Navigation,
    4 didReplanRoute replannedRoute: TomTomSDKRoute.Route,
    5 reason: TomTomSDKNavigationEngine.RouteReplanningReason
    6 ) {
    7 print("replanned route")
    8 }
    9
    10 func tomTomNavigation(
    11 _ navigation: TomTomSDKNavigationEngine.Navigation,
    12 didUpdateProgress: TomTomSDKNavigationEngine.RouteProgress
    13 ) {
    14 print("distance along route = \(didUpdateProgress.distanceAlongRouteInMeters)m")
    15 }
    16
    17 func tomTomNavigation(
    18 _ navigation: TomTomSDKNavigationEngine.Navigation,
    19 didGenerateAnnouncement announcement: TomTomSDKNavigationEngine.GuidanceAnnouncement
    20 ) {
    21 tts.play(announcement: announcement)
    22 }
    23}
    24
    25// MARK: - TTS
  4. Now stub the play function in the TTS class:

    1class TTS {
    2 func play(announcement: TomTomSDKNavigationEngine.GuidanceAnnouncement) {
    3 // Do nothing
    4 }
    5}

You can build the app after completing these steps. Do a long press on the map to plan a route to that location and start the navigation simulation.

Voice guidance

This section explains how to add voice instructions to the app.

  1. Implement the TTS class as follows:

    1class TTS {
    2 // MARK: Lifecycle
    3
    4 init(languageCode: String) {
    5 let ttsEngine = SystemTextToSpeechEngine(language: languageCode)
    6 self.ttsProvider = TomTomSDKTextToSpeech.makeTextToSpeech(ttsEngine: ttsEngine)
    7 }
    8
    9 convenience init() {
    10 let languageCode = Locale.preferredLanguages.first ?? Locale.current.languageCode ?? "en-GB"
    11 self.init(languageCode: languageCode)
    12 }
    13
    14 // MARK: Internal
    15
    16 func play(announcement: TomTomSDKNavigationEngine.GuidanceAnnouncement) {
    17 guard let message = announcement.verbalMessage else {
    18 return
    19 }
    20 var phonetics: [TomTomSDKTextToSpeechEngine.PhoneticTranscription] = if let streetPhonetics = parseStreetPhonetics(phonetics: announcement.verbalMessagePhonetics) {
    21 phonetics.append(streetPhonetics)
    22 }
    23 if let signpostPhonetics = parseSignpostPhonetics(phonetics: announcement.verbalMessagePhonetics) {
    24 phonetics.append(signpostPhonetics)
    25 }
    26 if let roadNumberPhonetics = parseRoadNumberPhonetics(phonetics: announcement.verbalMessagePhonetics) {
    27 phonetics.append(roadNumberPhonetics)
    28 }
    29 let ttsMessage = TTSMessage.tagged(message: message, phonetics: phonetics)
    30 let priority = TTSMessagePriority(timeout: messageTimeout)
    31 ttsProvider.play(message: ttsMessage, priority: priority)
    32 }
    33
    34 // MARK: Private
    35
    36 private let messageTimeout: TimeInterval = 10.0 // seconds
    37 private let ttsProvider: TomTomSDKTextToSpeech.TextToSpeechProtocol
    38
    39 private func parseStreetPhonetics(phonetics: TomTomSDKRoute.Phonetics?) -> TomTomSDKTextToSpeechEngine
    40 .PhoneticTranscription? {
    41 guard let street = phonetics?.street, let languageCode = phonetics?.streetLanguageCode else {
    42 return nil
    43 }
    44 return PhoneticTranscription(
    45 transcriptions: [street],
    46 languageCodes: [languageCode],
    47 tag: DefaultTags.streetName,
    48 alphabet: DefaultPhoneticAlphabets.ipa
    49 )
    50 }
    51
    52 private func parseSignpostPhonetics(phonetics: TomTomSDKRoute.Phonetics?) -> TomTomSDKTextToSpeechEngine
    53 .PhoneticTranscription? {
    54 guard let signpost = phonetics?.signpostText, let languageCode = phonetics?.signpostTextLanguageCode else {
    55 return nil
    56 }
    57 return PhoneticTranscription(
    58 transcriptions: [signpost],
    59 languageCodes: [languageCode],
    60 tag: DefaultTags.signpost,
    61 alphabet: DefaultPhoneticAlphabets.ipa
    62 )
    63 }
    64
    65 private func parseRoadNumberPhonetics(phonetics: TomTomSDKRoute.Phonetics?) -> TomTomSDKTextToSpeechEngine
    66 .PhoneticTranscription? {
    67 guard let roadNumbers = phonetics?.roadNumbers, let languageCodes = phonetics?.roadNumberLanguageCodes else {
    68 return nil
    69 }
    70 return PhoneticTranscription(
    71 transcriptions: roadNumbers,
    72 languageCodes: languageCodes,
    73 tag: DefaultTags.streetName,
    74 alphabet: DefaultPhoneticAlphabets.ipa
    75 )
    76 }
    77}

    The TTS class configures a text to speech provider using the SystemTextToSpeechEngine. Phonetic data is parsed from the GuidanceAnnouncement object and played back by the TTS provider. The TextToSpeechEngine protocol also declares functions for stopping playback, changing volume, and changing language.

You can build the app after completing these steps. You will be able to hear voice guidance during the navigation simulation.

Next steps

The TomTom Navigation SDK allows you to customize the appearance of the map and its overlays, use your own Navigation UI components, or provide a custom implementation of certain navigation behaviors.