# Magic Lane - Maps SDK for iOS documentation ## Docs ### Get Started With Sample Apps Last updated: March 11, 2026 | 2 minutes read #### Prerequisites[​](#prerequisites "Direct link to Prerequisites") To run the iOS example applications you need a macOS system with a compatible version of Xcode installed, including the required iOS SDK. Follow the step by step guides provided by Apple to configure Xcode for development. On the first build an internet connection is required so Swift Package Manager can resolve and download our GEMKit package. Once the packages are resolved and signing is correctly configured, the application can be built and run normally on your device or simulator. #### Downloading the examples[​](#downloading-the-examples "Direct link to Downloading the examples") Example applications for the Maps SDK for iOS are available on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios). You can clone the repository or download project ZIP file by clicking the green `Code` button and selecting `Download ZIP`. ![](/docs/ios/assets/images/github_download-838344b14d9f2453dca72c836bee9cff.png) **Download Examples from GitHub** If you chose to download the ZIP file, make sure to **extract** it after the download is complete. #### Running the examples[​](#running-the-examples "Direct link to Running the examples") ##### Create and use an API key[​](#create-and-use-an-api-key "Direct link to Create and use an API key") Authentication is **recommended** to use the Maps SDK for iOS without the watermark and ensure full functionality. This [guide](https://developer.magiclane.com/docs/guides/get-started) provides detailed instructions on how to obtain and set up your API key. Some features do not work unless a valid token is set and the number of allowed requests is limited. Once you have your API key, you need to set it in the example project. Look for the `AppDelegate` class and locate the following line: ```swift let token = "YOUR_TOKEN" ``` And replace the `YOUR_TOKEN` with your actual API key: ```swift let token = "" ``` warning Make sure the token is not commited or shared publicly. If the token is exposed, please deactivate it and generate a new one. ##### Launching the example[​](#launching-the-example "Direct link to Launching the example") With the selected example project opened in Xcode you can just run the application on the preferred device or simulator. ![](/docs/ios/assets/images/run_example-4a268dd24327f411586dbcb4b9c364a5.png) **Run an example in Xcode** #### Disclaimer[​](#disclaimer "Direct link to Disclaimer") The example applications are primarily designed for standard mobile device screen sizes and may not be correctly aligned or responsive on all screen sizes. These examples are intentionally simplified to enhance clarity and ease of understanding. They do not necessarily reflect best practices for production environments. --- ### Maps & 3D Scene These articles provide extensive coverage of the map related functionalities and techniques for both UIKit and SwiftUI implementations. [![Hello Map image](/docs/ios/assets/images/1_hello_map-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/hello-map.md) ##### [Hello Map](/docs/ios/examples/maps-3dscene/hello-map.md) [Display an interactive map.](/docs/ios/examples/maps-3dscene/hello-map.md) [![Map Compass image](/docs/ios/assets/images/2_map_compass-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/map-compass.md) ##### [Map Compass](/docs/ios/examples/maps-3dscene/map-compass.md) [Show a compass and react to map orientation changes.](/docs/ios/examples/maps-3dscene/map-compass.md) [![Center Map image](/docs/ios/assets/images/16_center_map-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/center-map.md) ##### [Center Map](/docs/ios/examples/maps-3dscene/center-map.md) [Programmatically center the map on a specific coordinate with animation.](/docs/ios/examples/maps-3dscene/center-map.md) [![Map Style image](/docs/ios/assets/images/3_map_style-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/map-style.md) ##### [Map Style](/docs/ios/examples/maps-3dscene/map-style.md) [Change the map style.](/docs/ios/examples/maps-3dscene/map-style.md) [![Map Style Following Theme image](/docs/ios/assets/images/4_map_style_following_theme-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/map-style-following-theme.md) ##### [Map Style Following Theme](/docs/ios/examples/maps-3dscene/map-style-following-theme.md) [Match the map style to your app appearance mode.](/docs/ios/examples/maps-3dscene/map-style-following-theme.md) [![Position Tracker image](/docs/ios/assets/images/5_position-tracker-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/position-tracker.md) ##### [Position Tracker](/docs/ios/examples/maps-3dscene/position-tracker.md) [Track position.](/docs/ios/examples/maps-3dscene/position-tracker.md) [![Shapes image](/docs/ios/assets/images/6_shapes-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/shapes.md) ##### [Shapes](/docs/ios/examples/maps-3dscene/shapes.md) [Draw and style lines and polygons on the map.](/docs/ios/examples/maps-3dscene/shapes.md) [![Map Selection image](/docs/ios/assets/images/7_map_selection-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/map-selection.md) ##### [Map Selection](/docs/ios/examples/maps-3dscene/map-selection.md) [Handle user interactions with map elements.](/docs/ios/examples/maps-3dscene/map-selection.md) [![Map Render image](/docs/ios/assets/images/8_map_render-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/map-render.md) ##### [Map Render](/docs/ios/examples/maps-3dscene/map-render.md) [Render map content to a custom image buffer.](/docs/ios/examples/maps-3dscene/map-render.md) [![Following Position image](/docs/ios/assets/images/9_following_position-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/following-position.md) ##### [Following Position](/docs/ios/examples/maps-3dscene/following-position.md) [Keep the camera centered on the moving position.](/docs/ios/examples/maps-3dscene/following-position.md) [![Map Perspective image](/docs/ios/assets/images/10_map_perspective-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/map-perspective.md) ##### [Map Perspective](/docs/ios/examples/maps-3dscene/map-perspective.md) [Switch between 2D and 3D map perspectives.](/docs/ios/examples/maps-3dscene/map-perspective.md) [![Multi Map image](/docs/ios/assets/images/11_multi_map-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/multi-map.md) ##### [Multi Map](/docs/ios/examples/maps-3dscene/multi-map.md) [Display and control multiple maps in one screen.](/docs/ios/examples/maps-3dscene/multi-map.md) [![Projections image](/docs/ios/assets/images/12_projections-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/projections.md) ##### [Projections](/docs/ios/examples/maps-3dscene/projections.md) [Convert between screen points and map coordinates.](/docs/ios/examples/maps-3dscene/projections.md) [![Range image](/docs/ios/assets/images/13_range-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/range.md) ##### [Range](/docs/ios/examples/maps-3dscene/range.md) [Visualize and work with map view range values.](/docs/ios/examples/maps-3dscene/range.md) [![Map Download image](/docs/ios/assets/images/14_map_download-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/map-download.md) ##### [Map Download](/docs/ios/examples/maps-3dscene/map-download.md) [Download map data for offline usage.](/docs/ios/examples/maps-3dscene/map-download.md) [![Map Update image](/docs/ios/assets/images/15_map_update-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/maps-3dscene/map_update.md) ##### [Map Update](/docs/ios/examples/maps-3dscene/map_update.md) [Check and apply map data updates.](/docs/ios/examples/maps-3dscene/map_update.md) --- ### Center Map Last updated: April 24, 2026 | 2 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to programmatically center the map on a specific coordinate. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/CenterMap). ![](/docs/ios/assets/images/example_ios_center_map1-12fe7f6e349f9ceb24a08040ea2c86bd.png) **Initial Map View** ![](/docs/ios/assets/images/example_ios_center_map2-c16d60921ead4ea21c1095d56af99250.png) **Map View after centering** ##### Map View Creation[​](#map-view-creation "Direct link to Map View Creation") `MapViewController` is embedded as a child view controller and pinned to all edges of the parent view: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/CenterMap/CenterMap/ViewController.swift) ```swift func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), ]) } ``` ##### Centering the Map[​](#centering-the-map "Direct link to Centering the Map") Tapping the navigation bar button calls `center(onCoordinates:zoomLevel:animationDuration:)` to animate the camera to Paris: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/CenterMap/CenterMap/ViewController.swift) ```swift func addCenterMapButton() { let image = UIImage.init(systemName: "target") let barButton = UIBarButtonItem.init(image: image, style: .done, target: self, action: #selector(centerMapAction(_:))) self.navigationItem.rightBarButtonItem = barButton } @objc func centerMapAction(_ barButton: UIBarButtonItem) { let location = CoordinatesObject.coordinates(withLatitude: 48.840827, longitude: 2.381899) self.mapViewController!.center(onCoordinates: location, zoomLevel: 50, animationDuration: 1000) } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to programmatically center the map on a specific coordinate. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/CenterMap). ![](/docs/ios/assets/images/example_ios_center_map1-12fe7f6e349f9ceb24a08040ea2c86bd.png) **Initial Map View** ![](/docs/ios/assets/images/example_ios_center_map2-c16d60921ead4ea21c1095d56af99250.png) **Map View after centering** ##### Centering the Map[​](#centering-the-map-1 "Direct link to Centering the Map") `MapReader` exposes a `proxy` that provides access to the map's camera control. Tapping the toolbar button calls `proxy.centerOn(coordinates:zoomLevel:duration:)` to animate the camera to Paris: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/CenterMap/CenterMap/ContentView.swift) ```swift struct ContentView: View { let location = CoordinatesObject.coordinates( withLatitude: 48.840827, longitude: 2.381899) var body: some View { MapReader { proxy in MapBase() .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("", systemImage: "target") { proxy.centerOn(coordinates: location, zoomLevel: 50, duration: 1000) } .buttonStyle(.borderedProminent) } } } .ignoresSafeArea() } } ``` --- ### Following Position Last updated: April 24, 2026 | 2 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to follow the position on the map after requesting location permission. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/FollowingPosition). ![](/docs/ios/assets/images/example_ios_following_position1-ad8c99879b539b7562bf4215e7164e3a.png) **Location Authorization** ![](/docs/ios/assets/images/example_ios_following_position2-ded1d9e65b72ecf721dde4fe57c55686.png) **Following Position** ##### Map Display and Following Position[​](#map-display-and-following-position "Direct link to Map Display and Following Position") The following code outlines the main view, which displays the map and the action button to follow the user's position after requesting location permission: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/FollowingPosition/FollowingPosition/ViewController.swift) ```swift class ViewController: UIViewController, CLLocationManagerDelegate { var mapViewController: MapViewController? var locationManager: CLLocationManager? var positionContext: PositionContext? var dataSource: DataSourceContext? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } let configuration = DataSourceConfigurationObject.init() configuration.setPositionActivity(.automotive) configuration.setPositionAccuracy(.whenMoving) configuration.setPositionDistanceFilter(0) self.dataSource = DataSourceContext.init() self.dataSource!.setConfiguration(configuration, for: .position) self.positionContext = PositionContext.init(context: self.dataSource!) self.createMapView() self.mapViewController!.startRender() self.addLocationButton() } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), ]) } // MARK: - Location func addLocationButton() { if self.locationManager == nil { self.locationManager = CLLocationManager.init() self.locationManager!.delegate = self } if self.isLocationAvailable() { if let context = self.dataSource { if context.isStopped() == false { context.start() } } } var image = UIImage.init(systemName: "location") if self.isLocationAvailable() == false { image = UIImage.init(systemName: "location.slash") } let barButton = UIBarButtonItem.init(image: image, style: .done, target: self, action: #selector(startFollowLocation)) self.navigationItem.rightBarButtonItem = barButton } @objc func startFollowLocation() { if self.isLocationAvailable() == false { self.requestLocationPermission() } else { self.mapViewController!.startFollowingPosition(withAnimationDuration: 1000, zoomLevel: -1, viewAngle: 0) { success in } } } func isLocationAvailable() -> Bool { return (self.locationManager!.authorizationStatus == .authorizedWhenInUse) } func requestLocationPermission() { if self.locationManager!.authorizationStatus == .notDetermined { self.locationManager!.requestWhenInUseAuthorization() } } func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { self.addLocationButton() } } ``` --- ### Hello Map Last updated: February 24, 2026 | 2 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to display a fully interactive map view. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/HelloMap). ![](/docs/ios/assets/images/example_ios_hello_map-22531e8777fdd5a540fe1f1a528798c3.png) **MapViewController View** ##### Map Display[​](#map-display "Direct link to Map Display") The following code outlines the main view, which displays the map: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/HelloMap/HelloMap/ViewController.swift) ```swift class ViewController: UIViewController { var mapViewController: MapViewController? deinit { if let controller = mapViewController { controller.destroy() } } override func viewDidLoad() { super.viewDidLoad() if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.createMapView() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.mapViewController!.startRender() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.mapViewController!.stopRender() } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController!.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -0), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -0) ]) } } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to display a fully interactive map view. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/HelloMap). ![](/docs/ios/assets/images/example_ios_hello_map-22531e8777fdd5a540fe1f1a528798c3.png) **MapBase View** ##### Map Display[​](#map-display-1 "Direct link to Map Display") The following code outlines the main view, which displays the map: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/HelloMap/HelloMap/ContentView.swift) ```swift struct ContentView: View { var body: some View { MapReader { proxy in MapBase() } .ignoresSafeArea() } } ``` --- ### Map Update Last updated: April 24, 2026 | 9 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to update the maps. There are two scenarios explained here, the manual update that is mandatory when having offline maps downloaded, and the default automatic update when no offline maps are used. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapUpdate). #### How it Works[​](#how-it-works "Direct link to How it Works") * Checks the map update status and displays the `Prepare Test` button only when the map is up to date to ensure correct resource handling (especially on a fresh install). * Integrates the `Prepare Test` action to simulate the scenario for an update by adding an old regional map and replacing the default world map resource with an older version. * Applies the necessary logic to update the maps to the latest version. * Manages and integrates map update testing functionality seamlessly within the app's UI. info The unmodified example requires the update to be applied manually after checking the status. If you want to test the automatic update process, without the use of offline maps, you will need to make a tiny adjustment in the example code. This is explained in the code snippets below. info In order for the example to work correctly you need to be connected to the internet so the SDK can check for updates and also retrieve initial missing map resources, as explained above. If the device is not connected to the internet the update check will fail and the button won't be displayed in order to avoid resources incompatibility issues for this test scenario. danger The direct resource file manipulation done in this example is a "hack" to simulate the map update process and is only for demonstration purposes, you should NOT manipulate resource files in this way. ![](/docs/ios/assets/images/example_ios_map_update1-188d30c0556110bcba1cf713e1828ebd.png) **Initial Screen with Prepare Test and Maps buttons** ![](/docs/ios/assets/images/example_ios_map_update2-2c2be136f025cf4e87dbcea5858fdfd8.png) **Old Map with no Data after Resource Replacement** ![](/docs/ios/assets/images/example_ios_map_update3-5e855e8aa2a924202e2c46af3d431860.png) **Update Available for outdated Andorra Map** ![](/docs/ios/assets/images/example_ios_map_update4-4cc6be9527c204dc8c7d0de4b0809064.png) **Up to date Andorra map** ##### Map Display and Preparing Testing Scenario[​](#map-display-and-preparing-testing-scenario "Direct link to Map Display and Preparing Testing Scenario") The following code outlines the main view, which displays the map, adds the button that will lead to the maps view, and adds the `Prepare Test` button after checking the map update status: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapUpdate/MapUpdate/ViewController.swift) ```swift class ViewController: UIViewController, GEMSdkDelegate { var mapViewController: MapViewController? var mapsContext: MapsContext? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.mapsContext = MapsContext.init() self.createMapView() self.mapViewController!.startRender() self.addMapsButton() GEMSdk.shared().delegate = self } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) } // MARK: - Map Style func addMapsButton() { let image = UIImage.init(systemName: "map") let barButton = UIBarButtonItem.init(image: image, style: .done, target: self, action: #selector(openMaps)) self.navigationItem.rightBarButtonItem = barButton } @objc func openMaps() { let viewController = MapsViewController.init(context: self.mapsContext!) self.navigationController?.pushViewController(viewController, animated: true) } func addPrepareTestButton() { let barButton = UIBarButtonItem.init(title: "Prepare Test", style: .done, target: self, action: #selector(prepareTestingScenario)) self.navigationItem.leftBarButtonItem = barButton } // MARK: - GEMSdkDelegate // Control whether the automatic world map update should be applied based on current application status. func shouldUpdateWorldwideRoadMap(for status: ContentStoreOnlineSupportStatus) -> Bool { let value = (status == .expiredData || status == .oldData) print("shouldUpdateWorldwideRoadMap:%@", value ? "YES" : "NO") if value == false { self.addPrepareTestButton() } return value } func updateWorldwideRoadMapFinished(_ success: Bool) { print("updateWorldwideRoadMapFinished, success:%@", success ? "YES" : "NO") self.addPrepareTestButton() } func onWorldwideRoadMapVersionUpdated() { print("onWorldwideRoadMapVersionUpdated") self.addPrepareTestButton() } // Show the test preparation button only when map is up to date to ensure correct resource handling. func onConnectionStatusUpdated(_ connected: Bool) { print("onConnectionStatusUpdated:%@", connected ? "Connected" : "No connection") if connected { self.mapsContext!.checkForUpdate { status in if status == .upToDate { self.addPrepareTestButton() } } } } ``` ##### Preparing Testing Scenario[​](#preparing-testing-scenario "Direct link to Preparing Testing Scenario") The following code demonstrates the "hack" for this example, called after tapping the `Prepare Test` button, which involves replacing the current map resources with older versions, and then reinitializing the SDK and the map related objects in order to enable the map update flow: info TESTING AUTOMATIC UPDATES If you wish to test the automatic update without the use of offline maps, you should edit the `prepareTestingScenario` method to remove or comment out the code under the `// Offline Map` section, leaving only the `// World Map` snippet. Make sure to delete any existing offline maps manually or by quickly deleting and reinstalling the app. With this setup, after tapping the `Prepare Test` button, the update will be immediately applied after reinitializing the SDK with the old resource. To control when and if your application can apply the update you must implement and modify the `shouldUpdateWorldwideRoadMap(for status: ContentStoreOnlineSupportStatus)` method from the `GEMSdkDelegate`. This method can be seen in the code above, under the `GEMSdkDelegate` section. ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapUpdate/MapUpdate/ViewController.swift) ```swift // MARK: - Utils // Hack for this example to simulate the need of a map update. After this has been called // the screen should flash for a moment and a map with old data will be loaded. @objc func prepareTestingScenario() { guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } let resourceURL = documentsURL.appendingPathComponent("Data/Res") let mapsURL = documentsURL.appendingPathComponent("Data/Maps") let oldOfflineMapURL = Bundle.main.url(forResource: "AndorraOSM_2021Q1", withExtension: "cmap")! let oldWorldMapURL = Bundle.main.url(forResource: "WM_7_406", withExtension: "map")! do { // Offline Map let mapsFiles = try FileManager.default.contentsOfDirectory(atPath: mapsURL.path()) if let offlineMapFile = mapsFiles.first(where: { $0.hasPrefix("AndorraOSM") }) { try FileManager.default.removeItem(at: mapsURL.appendingPathComponent(offlineMapFile)) } if let data = try? Data(contentsOf: oldOfflineMapURL) { try data.write(to: mapsURL.appending(component: oldOfflineMapURL.lastPathComponent)) } // Comment out the above code under // Offline Map if you want to test the automatic update process without the use of offline maps, // but make sure to delete the existing offline maps manually or by reinstalling the app. // World Map let resourceFiles = try FileManager.default.contentsOfDirectory(atPath: resourceURL.path()) if let resourceFile = resourceFiles.first(where: { let pref = $0.hasPrefix("WM_") return pref }) { try FileManager.default.removeItem(at: resourceURL.appendingPathComponent(resourceFile)) } if let data = try? Data(contentsOf: oldWorldMapURL) { try data.write(to: resourceURL.appending(component: oldWorldMapURL.lastPathComponent)) } self.reinitSDKAndCreateMap() } catch { print(error.localizedDescription) } } // Clean the map and reinitialize SDK to make sure the map update process is triggered. ONLY FOR DEMONSTRATION PURPOSES. func reinitSDKAndCreateMap() { self.navigationItem.leftBarButtonItem = nil self.mapViewController!.stopRender() self.mapViewController!.view.removeFromSuperview() self.mapViewController!.destroy() self.mapViewController = nil GEMSdk.shared().cleanDestroy() GEMSdk.shared().initSdk(getProjectApiToken()) GEMSdk.shared().delegate = self self.createMapView() self.mapViewController!.startRender() self.mapsContext = MapsContext.init() } } ``` ##### Checking for Updates and Updating Maps[​](#checking-for-updates-and-updating-maps "Direct link to Checking for Updates and Updating Maps") The following code implements the logic for checking for map updates inside the Maps view, prompting the user to update if one is available, as well as showing the update download status and progress by making use of `ContentUpdateDelegate`: MapsViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapUpdate/MapUpdate/MapsViewController.swift) ```swift func addCheckUpdate() { var buttons: [UIBarButtonItem] = [] buttons.append(UIBarButtonItem.init(title: "Check Update", style: .plain, target: self, action: #selector(checkForUpdate(button:)))) self.navigationItem.rightBarButtonItems = buttons } @objc func checkForUpdate(button: UIBarButtonItem) { guard let context = self.mapsContext else { return } context.checkForUpdate { (status: ContentStoreOnlineSupportStatus) in if status == .upToDate { let action = UIAlertAction.init(title: "Ok", style: .default) { action in } let alert = UIAlertController.init(title: "Info", message: "World Map is up to date.", preferredStyle: .alert) alert.addAction(action) self.present(alert, animated: true, completion: nil) } else if status == .oldData || status == .expiredData { let action1 = UIAlertAction.init(title: "Update", style: .default) { [weak self] action in guard let strongSelf = self else { return } let activity = UIActivityIndicatorView.init(style: .medium) let button = UIBarButtonItem.init(customView: activity) strongSelf.navigationItem.rightBarButtonItem = button activity.startAnimating() strongSelf.updateMaps() } let action2 = UIAlertAction.init(title: "Later", style: .default) { action in } let alert = UIAlertController.init(title: "Update Available", message: "", preferredStyle: .alert) alert.addAction(action2) alert.addAction(action1) let message1 = "New World Map available" var message2 = ", \nSize: " + context.getUpdateSizeFormatted() if context.getUpdateSize() == 0 { message2 = "" } let message3 = ". Do you want to update?" let attributes1 = [ NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14), NSAttributedString.Key.foregroundColor: UIColor.label ] let attributes2 = [ NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 14), NSAttributedString.Key.foregroundColor: UIColor.label ] let attributeString = NSMutableAttributedString.init(string: message1, attributes: attributes1) attributeString.append(NSMutableAttributedString.init(string: message2, attributes: attributes2)) attributeString.append(NSMutableAttributedString.init(string: message3, attributes: attributes1)) alert.setValue(attributeString, forKey: "attributedMessage") self.present(alert, animated: true, completion: nil) self.tableView.reloadData() } } } func updateMaps() { guard let context = self.mapsContext else { return } context.delegateUpdate = self context.update(withAllowCellularNetwork: true) { [weak self] success in guard let strongSelf = self else { return } strongSelf.updateFinished(success: success) } } func updateFinished(success: Bool) { guard let context = self.mapsContext else { return } let title = success ? "Update Completed" : "Update Error" let message = success ? "New Map version: " + context.getWorldMapVersion() : "" let action = UIAlertAction.init(title: "Ok", style: .default) { action in } let alert = UIAlertController.init(title: title, message: message, preferredStyle: .alert) alert.addAction(action) self.present(alert, animated: true, completion: nil) let time: DispatchTime = .now() + 1.0 DispatchQueue.main.asyncAfter(deadline: time) { self.refreshWithLocalMaps() self.refreshWithOnlineMaps() self.tableView.reloadData() self.title = "Map ver. " + context.getWorldMapVersion() } self.navigationItem.titleView = nil self.navigationItem.rightBarButtonItems = [] } func addCancelUpdate() { var buttons: [UIBarButtonItem] = [] buttons.append(UIBarButtonItem.init(title: "Cancel Update", style: .plain, target: self, action: #selector(cancelUpdate(button:)))) self.navigationItem.rightBarButtonItems = buttons } @objc func cancelUpdate(button: UIBarButtonItem) { guard let context = self.mapsContext else { return } context.cancelUpdate() self.navigationItem.rightBarButtonItems = [] } // MARK: - ContentUpdateDelegate func contextUpdate(_ context: NSObject, notifyStart hasProgress: Bool) { self.prepareUpdateBar() self.addCancelUpdate() } func contextUpdate(_ context: NSObject, notifyProgress progress: Int32) { if self.navigationItem.titleView == nil { self.addCancelUpdate() self.prepareUpdateBar() } let value: Float = Float(progress) / 100.0 if let masterView = self.navigationItem.titleView { if let label = masterView.viewWithTag(15) as? UILabel { label.text = String(format: "%d", progress) + "%" } if let progressBar = masterView.viewWithTag(16) as? UIProgressView { progressBar.progress = value } } } func contextUpdate(_ context: NSObject, notifyComplete success: Bool) { self.updateFinished(success: success) } func contextUpdate(_ context: NSObject, notifyStatusChanged status: ContentUpdateStatus) { } // MARK: - Utils func prepareUpdateBar() { self.navigationItem.titleView = nil let masterView = UIView.init() let label = UILabel.init() label.tag = 15 label.textAlignment = .center label.font = UIFont.boldSystemFont(ofSize: 14) let progressBar = UIProgressView.init(progressViewStyle: .bar) progressBar.tag = 16 progressBar.trackTintColor = UIColor.lightGray.withAlphaComponent(0.5) progressBar.progress = 0 progressBar.clipsToBounds = true progressBar.layer.cornerRadius = 4.0 masterView.addSubview(label) masterView.addSubview(progressBar) label.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ label.bottomAnchor.constraint(equalTo: progressBar.topAnchor), label.widthAnchor.constraint(equalTo: masterView.widthAnchor) ]) progressBar.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ progressBar.widthAnchor.constraint(equalTo: masterView.widthAnchor), progressBar.heightAnchor.constraint(equalToConstant: 8), progressBar.bottomAnchor.constraint(equalTo: masterView.bottomAnchor) ]) masterView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ masterView.widthAnchor.constraint(equalToConstant: 120), masterView.heightAnchor.constraint(equalToConstant: 32) ]) masterView.sizeToFit() self.navigationItem.titleView = masterView } ``` Rest of the UI code for the Maps view is not included here as it's not relevant for the map update process, check the full `MapsViewController.swift` file for the complete implementation. --- ### Map Compass Last updated: February 24, 2026 | 2 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to customize the compass. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapCompass). ![](/docs/ios/assets/images/example_ios_mapcompass-54406e1c54d5eca1b57ffd828f081e0a.png) **Map with Compass** ##### Map Display and Compass customization[​](#map-display-and-compass-customization "Direct link to Map Display and Compass customization") The following code outlines the main view, which displays the map, and implements the methods to customize the compass: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapCompass/MapCompass/ViewController.swift) ```swift class ViewController: UIViewController { var mapViewController: MapViewController? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.createMapView() self.mapViewController!.startRender() self.mapViewController!.showCompass() self.mapViewController!.setCompassFollowUserInterfaceStyle(true) self.setCompassTapHandler() self.changeCompassSize() self.changeCompassInsets() // Different approach to change compass layout // // self.changeCompassConstraints() } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), ]) } func setCompassTapHandler() { self.mapViewController!.setTapCompassCompletionHandler({ mode in print("tap compass") }) } func changeCompassSize() { self.mapViewController!.setCompassSize(60.0) } func changeCompassInsets() { self.mapViewController!.setCompassInsets(UIEdgeInsets(top: 40, left: 0, bottom: 0, right: 40)) } // Change constraints directly after map loads func changeCompassConstraints() { let compassImageView = self.mapViewController!.getCompassImageView() NSLayoutConstraint.deactivate(self.mapViewController!.getCompassLayoutConstraints()) NSLayoutConstraint.activate([ compassImageView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 50), compassImageView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant: 20) ]) } } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to customize the compass. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapCompass). ![](/docs/ios/assets/images/example_ios_mapcompass-54406e1c54d5eca1b57ffd828f081e0a.png) **Map with Compass** ##### Map Display and Compass customization[​](#map-display-and-compass-customization-1 "Direct link to Map Display and Compass customization") The following code outlines the main view, which displays the map, and implements the methods and modifiers to customize the compass: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapCompass/MapCompass/ContentView.swift) ```swift struct ContentView: View { var body: some View { MapReader { proxy in MapBase() .mapCompass(true) .mapCompassSize(60) .mapCompassInsets(getInsets()) .mapCompassFollowInterfaceStyle(true) .didTapCompass { mode in print("tap compass") } .ignoresSafeArea() } } func getInsets() -> UIEdgeInsets { return UIEdgeInsets.init(top: 40, left: 0, bottom: 0, right: 40) } } ``` --- ### Map Download Last updated: April 24, 2026 | 5 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to display multiple maps simultaneously. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapDownload). ![](/docs/ios/assets/images/example_ios_map_download1-a9630660eaba9d40fb4c2d3f14081571.png) **Initial Screen with Map Download button** ![](/docs/ios/assets/images/example_ios_map_download2-f64778dface2f69a85b9668b7dc2c2a5.png) **Downloading a Map with Progress** ##### Map Display and Map Download Button[​](#map-display-and-map-download-button "Direct link to Map Display and Map Download Button") The following code outlines the main view, which displays the map and prepares the button that will lead to the map download view: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapDownload/MapDownload/ViewController.swift) ```swift class ViewController: UIViewController { var mapViewController: MapViewController? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.createMapView() self.mapViewController!.startRender() self.addMapsButton() } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), ]) } // MARK: - Map Style func addMapsButton() { let image = UIImage.init(systemName: "map") let barButton = UIBarButtonItem.init(image: image, style: .done, target: self, action: #selector(openMaps)) self.navigationItem.rightBarButtonItem = barButton } @objc func openMaps() { let viewController = DownloadMapsViewController.init() self.navigationController?.pushViewController(viewController, animated: true) } } ``` ##### Map Download View[​](#map-download-view "Direct link to Map Download View") The following code outlines the map download list view, which is presented when the user taps the map download button, initializes the maps context, and retrieves the list of available maps: DownloadMapsViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapDownload/MapDownload/DownloadMapsViewController.swift) ```swift class DownloadMapsViewController: UITableViewController, ContentStoreObjectDelegate { var kProgressViewTag = 100 var mapsContext: MapsContext? var mapsList: [ContentStoreObject] = [] deinit { NSLog("DownloadMapsViewController: deinit") } // MARK: - Life Cycle override func viewDidLoad() { super.viewDidLoad() self.title = "Maps" self.navigationItem.largeTitleDisplayMode = .never self.view.backgroundColor = UIColor.systemBackground self.mapsContext = MapsContext.init() self.refreshWithLocalMaps() self.refreshWithOnlineMaps() } // MARK: - Refresh func refreshWithOnlineMaps() { self.mapsContext! .getOnlineList(completionHandler: { [weak self] array in guard let weakSelf = self else { return } if !array.isEmpty { weakSelf.mapsList = array weakSelf.tableView.reloadData() } }) } func refreshWithLocalMaps() { self.mapsList = self.mapsContext!.getLocalList() } ``` ##### Map Download Table View with Progress Bar[​](#map-download-table-view-with-progress-bar "Direct link to Map Download Table View with Progress Bar") The following code demonstrates the implementation of the table view, which displays the list of maps and their download status, and allows the user to start, pause, or delete map downloads: DownloadMapsViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapDownload/MapDownload/DownloadMapsViewController.swift) ```swift override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let rows = self.mapsList.count return rows } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let identifier = "defaultCellId" var cell = tableView.dequeueReusableCell(withIdentifier: identifier) if cell == nil { cell = UITableViewCell.init(style: .subtitle, reuseIdentifier: identifier) cell!.textLabel!.numberOfLines = 0 cell!.detailTextLabel!.numberOfLines = 0 } cell!.selectionStyle = .default let object = self.mapsList[indexPath.row] if object.getStatus() == .completed { cell!.selectionStyle = .none } self.setupText(tableView: tableView, cell: cell!, indexPath: indexPath) self.setupImage(tableView: tableView, cell: cell!, indexPath: indexPath) self.setupAccessoryView(tableView: tableView, cell: cell!, indexPath: indexPath) return cell! } func setupText(tableView: UITableView, cell: UITableViewCell, indexPath: IndexPath) { let object = self.mapsList[indexPath.row] let text = object.getName() cell.textLabel?.text = text let description = object.getTotalSizeFormatted() cell.detailTextLabel?.text = description } func setupImage(tableView: UITableView, cell: UITableViewCell, indexPath: IndexPath) { let scale = UIScreen.main.scale let size = CGSize.init(width: 60.0 * scale, height: 60.0 * scale) let object = self.mapsList[indexPath.row] if let code = object.getCountryCodes().first { if let image = self.mapsContext!.getCountryFlag(withIsoCode: code, size: size) { cell.imageView?.image = image cell.imageView?.layer.shadowOpacity = 0.8 cell.imageView?.layer.shadowColor = UIColor.lightGray.cgColor } else { cell.imageView?.image = nil cell.imageView?.layer.shadowOpacity = 0 cell.imageView?.layer.shadowColor = nil } } } func setupAccessoryView(tableView: UITableView, cell: UITableViewCell, indexPath: IndexPath) { let object = self.mapsList[indexPath.row] let status = object.getStatus() var value: Float = 0 var color = UIColor.systemBlue if status == .downloadRunning { value = Float(object.getDownloadProgress()) / 100.0 } else if status == .completed { value = 1 color = UIColor.systemGreen } if let progressBar = cell.contentView.viewWithTag(kProgressViewTag) as? UIProgressView { progressBar.progress = value progressBar.tintColor = color return } let progressBar = UIProgressView.init(progressViewStyle: .bar) progressBar.tag = kProgressViewTag progressBar.progress = value progressBar.tintColor = color cell.contentView.addSubview(progressBar) progressBar.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ progressBar.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: 0), progressBar.leadingAnchor.constraint(equalTo: cell.textLabel!.leadingAnchor, constant: 0), progressBar.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: 0), progressBar.heightAnchor.constraint(equalToConstant: 2.0) ]) } // MARK: - UITableViewDelegate override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { return .delete } override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let action = UIContextualAction.init( style: .destructive, title: "Delete", handler: { (action, view, completion) in let object = self.mapsList[indexPath.row] if object.canDeleteContent() { object.deleteContent() self.tableView.reloadRows(at: [indexPath], with: .fade) } completion(true) }) let actions = UISwipeActionsConfiguration.init(actions: [action]) return actions } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let object = self.mapsList[indexPath.row] object.delegate = self let status = object.getStatus() if status == .downloadRunning { object.pauseDownload() } else if status == .unavailable || status == .paused { object.download(withAllowCellularNetwork: true) { (success: Bool) in } } } ``` ##### Content Store Object Delegate[​](#content-store-object-delegate "Direct link to Content Store Object Delegate") The following code implements the `ContentStoreObjectDelegate` methods to receive status updates and update the progress bar in the table view cells as the map download progresses: DownloadMapsViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapDownload/MapDownload/DownloadMapsViewController.swift) ```swift // MARK: - ContentStoreObjectDelegate func contentStoreObject(_ object: ContentStoreObject, notifyStart hasProgress: Bool) { } func contentStoreObject(_ object: ContentStoreObject, notifyProgress progress: Int32) { if let row = self.mapsList.firstIndex(of: object) { let indexPath = IndexPath.init(row: row, section: 0) if let cell = self.tableView.cellForRow(at: indexPath) { if let view = cell.contentView.viewWithTag(kProgressViewTag) as? UIProgressView { let value: Float = Float(progress) / 100.0 view.progress = value } } } } func contentStoreObject(_ object: ContentStoreObject, notifyComplete success: Bool) { } func contentStoreObject(_ object: ContentStoreObject, notifyStatusChanged status: ContentStoreObjectStatus) { if status == .completed { if let row = self.mapsList.firstIndex(of: object) { let indexPath = IndexPath.init(row: row, section: 0) if let cell = self.tableView.cellForRow(at: indexPath) { if let view = cell.contentView.viewWithTag(kProgressViewTag) as? UIProgressView { view.tintColor = UIColor.systemGreen } } } } } } ``` --- ### Map Perspective Last updated: April 24, 2026 | 2 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to follow the position on the map after requesting location permission. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapPerspective). ![](/docs/ios/assets/images/example_ios_map_perspective1-e1f15f0b2a0a8f27d23b7b1515c5173e.png) **Initial 2D Perspective** ![](/docs/ios/assets/images/example_ios_map_perspective2-997b4f25565451b1629490ca3079f95d.png) **3D Perspective** ##### Map Display and Perspective Control[​](#map-display-and-perspective-control "Direct link to Map Display and Perspective Control") The following code outlines the main view, which displays the map and the action buttons to change the map perspective, align it north up, and toggle perspective gestures: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapPerspective/MapPerspective/ViewController.swift) ```swift class ViewController: UIViewController { var mapViewController: MapViewController? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.createMapView() self.mapViewController!.startRender() self.addMapPerspective() } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), ]) } // MARK: - Perspective func addMapPerspective() { let barButton = UIBarButtonItem.init( image: UIImage.init(systemName: "view.3d"), style: .done, target: self, action: #selector(changeMapPerspective)) let barButton2 = UIBarButtonItem.init( image: UIImage.init(systemName: "location.north.line"), style: .done, target: self, action: #selector(mapAlighNorthUp)) let barButton3 = UIBarButtonItem.init( image: UIImage.init(systemName: "perspective"), style: .done, target: self, action: #selector(togglePerspectiveGesture)) self.navigationItem.rightBarButtonItems = [barButton, barButton2] self.navigationItem.leftBarButtonItems = [barButton3] } @objc func changeMapPerspective(item: UIBarButtonItem) { guard let mapViewController = self.mapViewController else { return } if mapViewController.getPerspective() == .view2D { item.image = UIImage.init(systemName: "view.2d") mapViewController.setPerspective(.view3D, animationDuration: 1000) { success in } } else { item.image = UIImage.init(systemName: "view.3d") self.mapViewController!.setPerspective(.view2D, animationDuration: 1000) { success in } } } @objc func mapAlighNorthUp() { self.mapViewController!.alignNorthUp(withAnimationDuration: 1000) { success in } } @objc func togglePerspectiveGesture() { guard let preferences = self.mapViewController?.getPreferences() else { return } let state = preferences.isTouchGestureEnabled(.onShove) // Single gesture preferences.enableTouchGesture(.onShove, enable: !state) // Multiple gestures // let gestures = (MapViewTouchGestures.onShove.rawValue | MapViewTouchGestures.onRotate.rawValue | MapViewTouchGestures.onPinch.rawValue | MapViewTouchGestures.onPinchSwipe.rawValue) // preferences.enableTouchGestures(gestures, enable: !state) } } ``` --- ### Map Render Last updated: April 24, 2026 | 2 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to handle the map rendering based on the view controller's lifecycle. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapRender). ![](/docs/ios/assets/images/example_ios_map_render-be99fea90fb42a6d005ea05dc4a01eaf.png) **MapViewController View** ##### Map View Creation[​](#map-view-creation "Direct link to Map View Creation") `MapViewController` is embedded as a child and pinned to all edges. The compass is hidden immediately after creation: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapRender/MapRender/ViewController.swift) ```swift func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController!.hideCompass() self.mapViewController!.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) } ``` ##### Render Lifecycle[​](#render-lifecycle "Direct link to Render Lifecycle") `startRender()` and `stopRender()` are called in the view controller's appearance callbacks to tie rendering to the screen visibility. The `deinit` calls `destroy()` to cleanly release the map resources: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapRender/MapRender/ViewController.swift) ```swift deinit { if let controller = mapViewController { controller.destroy() } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.mapViewController!.startRender() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.mapViewController!.stopRender() } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to handle the map rendering based on the app's lifecycle. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapRender). ![](/docs/ios/assets/images/example_ios_map_render-be99fea90fb42a6d005ea05dc4a01eaf.png) **MapBase View** ##### Map Display and Rendering[​](#map-display-and-rendering "Direct link to Map Display and Rendering") The following code outlines the main view, which displays the map and manages the rendering state based on the current scene phase: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapRender/MapRender/ContentView.swift) ```swift struct ContentView: View { @Environment(\.scenePhase) private var scenePhase @State private var isAppActive = true var body: some View { MapReader { proxy in MapBase(initialPosition: .amsterdam, initialZoomLevel: 54) .mapCompass(false) .mapRender(isAppActive) } .ignoresSafeArea() .onChange(of: scenePhase) { newPhase in switch newPhase { case .active: isAppActive = true case .background: isAppActive = false default: break } } } } ``` info This is the most basic example of controlling map rendering. Rendering can be toggled on and off at any time based on your app's specific needs, such as pausing rendering when the map is not fully visible or when UI smoothness is a priority, like with animations. --- ### Map Selection Last updated: April 24, 2026 | 5 minutes read

* UIKit * SwiftUI This example demonstrates how to handle user interactions with map elements (e.g. streets, landmarks, overlays, and traffic events) and apply custom highlight styles using `HighlightRenderSettings` in a UIKit application. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapSelection). ![](/docs/ios/assets/images/example_ios_map_selection-7fd4f2b64bd6dbd3731b390aac1f7b0c.png) **Selected Charging Station POI** ##### UI and Map selection events[​](#ui-and-map-selection-events "Direct link to UI and Map selection events") The following code outlines the main view controller, which displays the map, centers on a position and implements the selection methods from the `MapViewControllerDelegate` protocol: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapSelection/MapSelection/ViewController.swift) ```swift class ViewController: UIViewController, MapViewControllerDelegate { var mapViewController: MapViewController? var label = UILabel.init() var imageView = UIImageView() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.title = "Map Selection" self.navigationItem.hidesSearchBarWhenScrolling = false self.navigationItem.largeTitleDisplayMode = .never self.createMapView() self.mapViewController!.startRender() self.addLabelText() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // Paris let location = CoordinatesObject.coordinates(withLatitude: 48.840827, longitude: 2.381899) self.mapViewController!.center(onCoordinates: location, zoomLevel: 70, animationDuration: 1200) } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.delegate = self self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), ]) } // MARK: - Label func addLabelText() { self.imageView.contentMode = .scaleAspectFit self.imageView.isHidden = true self.imageView.layer.shadowColor = UIColor.lightGray.cgColor self.imageView.layer.shadowOpacity = 0.8 self.label.font = UIFont.boldSystemFont(ofSize: 14) self.label.numberOfLines = 0 self.label.backgroundColor = UIColor.systemBackground self.label.isHidden = true self.label.layer.shadowColor = UIColor.lightGray.cgColor self.label.layer.shadowOpacity = 0.8 self.view.addSubview(self.label) self.view.addSubview(self.imageView) self.label.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.label.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 10), self.label.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -10), self.label.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -10), self.label.heightAnchor.constraint(equalToConstant: 70) ]) self.imageView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.imageView.leadingAnchor.constraint(equalTo: self.label.leadingAnchor), self.imageView.topAnchor.constraint(equalTo: self.label.topAnchor, constant: -30), self.imageView.widthAnchor.constraint(equalToConstant: 40), self.imageView.heightAnchor.constraint(equalToConstant: 40) ]) } // MARK: - MapViewControllerDelegate func mapViewController(_ mapViewController: MapViewController, didSelectLandmarks landmarks: [LandmarkObject], onTouch point: CGPoint) { guard let landmark = landmarks.first else { return } let text = " " + landmark.getLandmarkName() + "\n" + " " + landmark.getLandmarkDescription() self.label.text = text self.label.isHidden = false let scale = UIScreen.main.scale self.imageView.image = landmark.getLandmarkImage(CGSize.init(width: 40 * scale, height: 40 * scale)) self.imageView.isHidden = false self.highlight(landmark: landmark) } func mapViewController( _ mapViewController: MapViewController, didSelectLandmarks landmarks: [LandmarkObject], onLongTouch point: CGPoint ) { guard let landmark = landmarks.first else { return } let text = " " + landmark.getLandmarkName() + "\n" + " " + landmark.getLandmarkDescription() self.label.text = text self.label.isHidden = false let scale = UIScreen.main.scale self.imageView.image = landmark.getLandmarkImage(CGSize.init(width: 40 * scale, height: 40 * scale)) self.imageView.isHidden = false self.highlight(landmark: landmark) } func mapViewController(_ mapViewController: MapViewController, didSelectStreets streets: [LandmarkObject], onTouch point: CGPoint) { guard let landmark = streets.first else { return } let text = " " + landmark.getLandmarkName() + "\n" + " " + landmark.getLandmarkDescription() self.label.text = text self.label.isHidden = false let scale = UIScreen.main.scale self.imageView.image = landmark.getLandmarkImage(CGSize.init(width: 40 * scale, height: 40 * scale)) self.imageView.isHidden = false self.highlight(landmark: landmark) } func mapViewController(_ mapViewController: MapViewController, didSelectStreets streets: [LandmarkObject], onLongTouch point: CGPoint) { guard let landmark = streets.first else { return } let text = " " + landmark.getLandmarkName() + "\n" + " " + landmark.getLandmarkDescription() self.label.text = text self.label.isHidden = false let scale = UIScreen.main.scale self.imageView.image = landmark.getLandmarkImage(CGSize.init(width: 40 * scale, height: 40 * scale)) self.imageView.isHidden = false self.highlight(landmark: landmark) } func mapViewController(_ mapViewController: MapViewController, didSelectOverlays overlays: [OverlayItemObject], onTouch point: CGPoint) { print("didSelectOverlays") } func mapViewController(_ mapViewController: MapViewController, didSelectOverlays overlays: [OverlayItemObject], onLongTouch point: CGPoint) { print("didSelectOverlays") } func mapViewController(_ mapViewController: MapViewController, didSelectTrafficEvents events: [TrafficEventObject]) { print("didSelectTrafficEvents") } func highlight(landmark: LandmarkObject) { let settings = HighlightRenderSettings.init() settings.showPin = true settings.imageSize = 7 if landmark.isContourGeograficAreaEmpty() == false { settings.options = Int32( HighlightOption.showLandmark.rawValue | HighlightOption.overlap.rawValue | HighlightOption.showContour.rawValue) settings.contourInnerColor = UIColor.white settings.contourOuterColor = UIColor.systemBlue } self.mapViewController!.presentHighlights([landmark], settings: settings, highlightId: 0) // Center animation // self.mapViewController!.center(onCoordinates: landmark.getCoordinates(), zoomLevel: -1, animationDuration: 900) } } ``` This example demonstrates how to handle user interactions with map elements (e.g. streets, landmarks, overlays, and traffic events) and apply custom highlight styles using `HighlightRenderSettings` in a SwiftUI application. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapSelection). ![](/docs/ios/assets/images/example_ios_map_selection-7fd4f2b64bd6dbd3731b390aac1f7b0c.png) **Selected Charging Station POI** ##### UI and Map selection events[​](#ui-and-map-selection-events-1 "Direct link to UI and Map selection events") The following code outlines the main view, which displays the map, centers on a position and implements the `MapBase` view modifiers for different selection events: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapSelection/MapSelection/ContentView.swift) ```swift struct ContentView: View { @Environment(\.displayScale) private var displayScale @State private var selectedLandmark: LandmarkObject? @State private var zoom = 74 var body: some View { MapReader { proxy in ZStack(alignment: .bottom) { MapBase() .didSelectStreets { streets, touchPoint, isLongTouch in proxy.present(highlights: streets, settings: getRenderSettings()) selectedLandmark = streets.first } .didSelectLandmarks { landmarks, touchPoint, isLongTouch in proxy.present(highlights: landmarks, settings: getRenderSettings()) selectedLandmark = landmarks.first } .didSelectOverlays { overlays, touchPoint, isLongTouch in print("didSelectOverlays") } .didSelectTrafficEvents { events, touchPoint, isLongTouch in print("didSelectTrafficEvents") } .onAppear { goToPosition(proxy) } .ignoresSafeArea() if let selectedLandmark = selectedLandmark { HStack { Image(uiImage: getLandmarkImage(landmark: selectedLandmark)) .frame(width: 40, height: 40) Text(selectedLandmark.getLandmarkName() + "\n" + selectedLandmark.getLandmarkDescription()) Spacer() } .padding(10) .background(Rectangle().fill(.background)) .padding() } } } } func getRenderSettings() -> HighlightRenderSettings { let settings = HighlightRenderSettings.init() settings.showPin = true settings.imageSize = 7 return settings } func getLandmarkImage(landmark: LandmarkObject) -> UIImage { let image = landmark.getLandmarkImage(CGSize(width: 40 * displayScale, height: 40 * displayScale)) ?? UIImage() return image } func goToPosition(_ proxy: MapProxy) { proxy.centerOn(coordinates: .amsterdam, zoomLevel: zoom) } } extension CoordinatesObject { static let amsterdam = CoordinatesObject.coordinates(withLatitude: 52.368447, longitude: 4.888229) } ``` --- ### Map Custom Style Last updated: April 24, 2026 | 2 minutes read

* UIKit * SwiftUI This example demonstrates how to apply a custom map style in a UIKit application. A custom map style can be created in our [Map Studio](https://developer.magiclane.com/docs/guides/map-studio/). Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapViewCustomStyle). ![](/docs/ios/assets/images/example_ios_map_custom_style_uikit-89ba891acfa5be1f0b1f30745f934887.png) **Map view with custom style** ##### Map Display and applying the Style[​](#map-display-and-applying-the-style "Direct link to Map Display and applying the Style") The following code outlines the main view, which displays the map and applies a custom style from the app's resources: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapViewCustomStyle/MapViewCustomStyle/ViewController.swift) ```swift class ViewController: UIViewController { var mapViewController: MapViewController? override func viewDidLoad() { super.viewDidLoad() if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.createMapView() self.mapViewController!.hideCompass() self.setCustomStyle() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.mapViewController!.startRender() } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -0), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -0) ]) } func setCustomStyle() { if let url = Bundle.main.url(forResource: "Basic1Oldtime", withExtension: "style") { if let data = NSData.init(contentsOf: url) as Data? { self.mapViewController!.applyStyle(withStyleBuffer: data, smoothTransition: false) } } } } ``` This example demonstrates how to apply a custom map style in a SwiftUI application. A custom map style can be created in our [Map Studio](https://developer.magiclane.com/docs/guides/map-studio/). Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapCustomStyle). ![](/docs/ios/assets/images/example_ios_map_custom_style_swiftui-5d009d96b5187a2da88bba45ffbc7e2d.png) **Map view with custom style** ##### Map Display and applying the Style[​](#map-display-and-applying-the-style-1 "Direct link to Map Display and applying the Style") The following code outlines the main view, which displays the map and applies a custom style from the app's resources: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapCustomStyle/MapCustomStyle/ContentView.swift) ```swift struct ContentView: View { var body: some View { MapReader { proxy in MapBase() .onAppear { applyCustomMapStyle(proxy) } .ignoresSafeArea() } } func applyCustomMapStyle(_ proxy: MapProxy) { guard let url = Bundle.main.url(forResource: "CustomMapStyle", withExtension: "style") else { return } if let data = NSData.init(contentsOf: url) as Data? { proxy.setMapStyle(data: data, smoothTransition: true) } } } ``` --- ### Map Style Following Theme Last updated: April 24, 2026 | 2 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to apply a map style that follows the device theme (light/dark mode). Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapStyleFollowingTheme). ![](/docs/ios/assets/images/example_ios_mapstyle_follow_os1-ac28708590009c9c98bd028a0e715371.png) **Map Light Theme** ![](/docs/ios/assets/images/example_ios_mapstyle_follow_os2-cdbb77671d21b10d6ce94511f4e56ca9.png) **Map Dark Theme** ##### Applying the Style on Load and Theme Change[​](#applying-the-style-on-load-and-theme-change "Direct link to Applying the Style on Load and Theme Change") `applyStyleForCurrentTheme()` reads `traitCollection.userInterfaceStyle` to choose between `MapStyleIdentifiers.day` and `.night`, then applies it via `applyStyle(withStyleIdentifier:smoothTransition:)`. It is called once in `viewDidLoad` and again in `traitCollectionDidChange(_:)` whenever the system appearance changes: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MapStyleFollowingTheme/MapStyleFollowingTheme/ViewController.swift) ```swift override func viewDidLoad() { super.viewDidLoad() // ... self.applyStyleForCurrentTheme() } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) if previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle { self.applyStyleForCurrentTheme() } } // MARK: - Style func applyStyleForCurrentTheme() { let styleIdentifier: Int if traitCollection.userInterfaceStyle == .dark { styleIdentifier = MapStyleIdentifiers.night.rawValue } else { styleIdentifier = MapStyleIdentifiers.day.rawValue } self.mapViewController?.applyStyle(withStyleIdentifier: styleIdentifier, smoothTransition: false) } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to apply a map style that follows the device theme (light/dark mode). Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapStyleFollowingTheme). ![](/docs/ios/assets/images/example_ios_mapstyle_follow_os1-ac28708590009c9c98bd028a0e715371.png) **Map Light Theme** ![](/docs/ios/assets/images/example_ios_mapstyle_follow_os2-cdbb77671d21b10d6ce94511f4e56ca9.png) **Map Dark Theme** ##### Map Display and Style Following OS Theme[​](#map-display-and-style-following-os-theme "Direct link to Map Display and Style Following OS Theme") `@Environment(\.colorScheme)` observes the current system appearance. The `.mapStyle()` modifier is called with the result of `getStyleFollowingOS()`, which returns `MapStyleIdentifiers.night` in dark mode and `.day` in light mode. SwiftUI automatically re-evaluates the view whenever `colorScheme` changes, so the map style updates in real time: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapStyleFollowingTheme/MapStyleFollowingTheme/ContentView.swift) ```swift struct ContentView: View { @Environment(\.colorScheme) var colorScheme var body: some View { MapReader { proxy in MapBase() .mapStyle(getStyleFollowingOS()) .ignoresSafeArea() } } func getStyleFollowingOS() -> Int { return colorScheme == .dark ? MapStyleIdentifiers.night.rawValue : MapStyleIdentifiers.day.rawValue } } ``` --- ### Multi-Map Last updated: April 24, 2026 | 2 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to display multiple maps simultaneously. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MultiMapView). ![](/docs/ios/assets/images/example_ios_multimap-5a616855916d90d2e6b3077764b5b0d3.png) **Multiple Maps Displayed** ##### Multiple Map Display[​](#multiple-map-display "Direct link to Multiple Map Display") The following code outlines the main view, which displays multiple maps and the action buttons to add a map and remove the last map: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/MultiMapView/MultiMapView/ViewController.swift) ```swift class ViewController: UIViewController { var index: CGFloat = 0 var offsetTop: CGFloat = 0 var offsetLeft: CGFloat = 0 var size: CGFloat = 172 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } var image = UIImage.init(systemName: "plus") let barButton1 = UIBarButtonItem.init(image: image, style: .done, target: self, action: #selector(buttonPlusPressed)) image = UIImage.init(systemName: "minus") let barButton2 = UIBarButtonItem.init(image: image, style: .done, target: self, action: #selector(buttonMinusPressed)) self.navigationItem.rightBarButtonItems = [barButton1] self.navigationItem.leftBarButtonItems = [barButton2] } // MARK: - Map View func createMapView() { self.offsetTop = 10 + self.index * 60.0 + 90 self.offsetLeft = 10 + self.index * 40.0 self.index += 1 let mapViewController = MapViewController.init() mapViewController.view.backgroundColor = UIColor.systemBackground mapViewController.setCompassSize(20) mapViewController.view.layer.borderWidth = 1 mapViewController.view.layer.borderColor = UIColor.darkGray.cgColor mapViewController.view.layer.cornerRadius = 8 mapViewController.view.layer.shadowColor = UIColor.lightGray.cgColor mapViewController.view.layer.shadowOpacity = 0.8 self.addChild(mapViewController) self.view.addSubview(mapViewController.view) mapViewController.didMove(toParent: self) mapViewController.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ mapViewController.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: self.offsetTop), mapViewController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: self.offsetLeft), mapViewController.view.widthAnchor.constraint(equalToConstant: self.size), mapViewController.view.heightAnchor.constraint(equalToConstant: self.size) ]) mapViewController.startRender() } func deleteMapView() { if let mapViewController = self.children.last as? MapViewController { mapViewController.stopRender() mapViewController.willMove(toParent: nil) mapViewController.view.removeFromSuperview() mapViewController.removeFromParent() self.index -= 1 } } // MARK: - Button Action @objc func buttonPlusPressed(barButton: UIBarButtonItem) { self.createMapView() } @objc func buttonMinusPressed(barButton: UIBarButtonItem) { self.deleteMapView() } // MARK: - Render func startRender() { for viewController in self.children { if let mapView = viewController as? MapViewController { mapView.startRender() } } } func stopRender() { for viewController in self.children { if let mapView = viewController as? MapViewController { mapView.stopRender() } } } } ``` --- ### Position Tracker Last updated: April 24, 2026 | 5 minutes read

* UIKit * SwiftUI This example demonstrates how to change the map's Position Tracker in a UIKit application. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/PositionTracker). ![](/docs/ios/assets/images/example_ios_position_tracker1-e4a71096c53e125c0bf17ab404f8ad51.png) **Initial screen** ![](/docs/ios/assets/images/example_ios_position_tracker2-038545549084302c6c7e42f0fab41ca4.png) **Custom Position Tracker** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The following code outlines the main view, which displays the map and the main action buttons: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/PositionTracker/PositionTracker/ViewController.swift) ```swift class ViewController: UIViewController, CLLocationManagerDelegate { var mapViewController: MapViewController? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance let array = [ UIBarButtonItem.init( image: UIImage.init(systemName: "location.fill"), style: .done, target: self, action: #selector(defaultPositionTracker)), UIBarButtonItem.init( image: UIImage.init(systemName: "car.fill"), style: .done, target: self, action: #selector(carPositionTracker)), UIBarButtonItem.init( image: UIImage.init(systemName: "airplane"), style: .done, target: self, action: #selector(planePositionTracker)), UIBarButtonItem.init( image: UIImage.init(systemName: "circle.fill"), style: .done, target: self, action: #selector(imagePositionTracker)) ] self.navigationItem.leftBarButtonItems = array self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(image: UIImage.init(systemName: "location"), style: .done, target: self, action: #selector(startFollowLocation)) } AppManager.shared.startLiveSensors() self.createMapView() self.mapViewController!.startRender() } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) } ``` ##### Position Tracker customization[​](#position-tracker-customization "Direct link to Position Tracker customization") The following code implements the different methods for the location following and the position tracker customization actions: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/PositionTracker/PositionTracker/ViewController.swift) ```swift @objc func startFollowLocation() { AppManager.shared.requestLocationPermission() self.mapViewController!.startFollowingPosition(withAnimationDuration: 0, zoomLevel: 70, viewAngle: 0) { success in } } @objc func defaultPositionTracker() { self.mapViewController!.setPositionTrackerScaleFactor(1) self.mapViewController!.setDefaultPositionTracker() } @objc func carPositionTracker() { let fileName = "car" if let urlMtl = Bundle.main.url(forResource: fileName, withExtension: "mtl") { if let material = NSData.init(contentsOf: urlMtl) as Data? { if let urlObj = Bundle.main.url(forResource: fileName, withExtension: "obj") { if let object = NSData.init(contentsOf: urlObj) as Data? { self.mapViewController!.setPositionTrackerScaleFactor(1) self.mapViewController!.customizePositionTracker(object, material: material) } } } } } @objc func planePositionTracker() { let fileName = "plane" if let urlMtl = Bundle.main.url(forResource: fileName, withExtension: "mtl") { if let material = NSData.init(contentsOf: urlMtl) as Data? { if let urlObj = Bundle.main.url(forResource: fileName, withExtension: "obj") { if let object = NSData.init(contentsOf: urlObj) as Data? { self.mapViewController!.setPositionTrackerScaleFactor(1) self.mapViewController!.customizePositionTracker(object, material: material) } } } } } @objc func imagePositionTracker() { guard let image = UIImage.init(named: "DotRay") else { return } if let data = image.pngData() { self.mapViewController!.setPositionTrackerScaleFactor(2) self.mapViewController!.customizePositionTracker(data) } } ``` ##### Location Services and Location Manager[​](#location-services-and-location-manager "Direct link to Location Services and Location Manager") The referenced `AppManager` and `LiveDataSourceController` classes prepare the `PositionContext` and `DataSourceContext` objects from the `GEMKit` package to make full use of the Location data. Check the full implementation for the corresponding files on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/PositionTracker/PositionTracker). This example demonstrates how to change the map's Position Tracker in a SwiftUI application. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapPositionTracker). ![](/docs/ios/assets/images/example_ios_position_tracker1-e4a71096c53e125c0bf17ab404f8ad51.png) **Initial screen** ![](/docs/ios/assets/images/example_ios_position_tracker2-038545549084302c6c7e42f0fab41ca4.png) **Custom Position Tracker** ##### UI and Map Integration[​](#ui-and-map-integration-1 "Direct link to UI and Map Integration") The following code outlines the main view, which displays the map and the main action buttons: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapPositionTracker/MapPositionTracker/ContentView.swift) ```swift struct ContentView: View { var body: some View { MapReader { proxy in MapBase() .mapCompass(false) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("", systemImage: "location") { goToUserPosition(proxy) } .buttonStyle(.borderedProminent) } ToolbarItem(placement: .topBarLeading) { Button("", systemImage: "location.fill") { defaultPositionTracker(proxy) } .buttonStyle(.borderedProminent) } ToolbarItem(placement: .topBarLeading) { Button("", systemImage: "car.fill") { carPositionTracker(proxy) } .buttonStyle(.borderedProminent) } ToolbarItem(placement: .topBarLeading) { Button("", systemImage: "airplane") { planePositionTracker(proxy) } .buttonStyle(.borderedProminent) } ToolbarItem(placement: .topBarLeading) { Button("", systemImage: "circle.fill") { imagePositionTracker(proxy) } .buttonStyle(.borderedProminent) } } .ignoresSafeArea() } } func goToUserPosition(_ proxy: MapProxy) { AppManager.shared.requestLocationPermission() proxy.startFollowingPosition(duration: 0, zoomLevel: 70, viewAngle: 0) } ``` ##### Position Tracker customization[​](#position-tracker-customization-1 "Direct link to Position Tracker customization") The following code implements the different methods for the position tracker customization actions: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapPositionTracker/MapPositionTracker/ContentView.swift) ```swift func defaultPositionTracker(_ proxy: MapProxy) { proxy.mapViewController?.setPositionTrackerScaleFactor(1) proxy.mapViewController?.setDefaultPositionTracker() } func carPositionTracker(_ proxy: MapProxy) { let fileName = "car" if let urlMtl = Bundle.main.url(forResource: fileName, withExtension: "mtl") { if let material = NSData.init(contentsOf: urlMtl) as Data? { if let urlObj = Bundle.main.url(forResource: fileName, withExtension: "obj") { if let object = NSData.init(contentsOf: urlObj) as Data? { proxy.mapViewController?.setPositionTrackerScaleFactor(1) proxy.mapViewController?.customizePositionTracker(object, material: material) } } } } } func planePositionTracker(_ proxy: MapProxy) { let fileName = "plane" if let urlMtl = Bundle.main.url(forResource: fileName, withExtension: "mtl") { if let material = NSData.init(contentsOf: urlMtl) as Data? { if let urlObj = Bundle.main.url(forResource: fileName, withExtension: "obj") { if let object = NSData.init(contentsOf: urlObj) as Data? { proxy.mapViewController?.setPositionTrackerScaleFactor(1) proxy.mapViewController?.customizePositionTracker(object, material: material) } } } } } func imagePositionTracker(_ proxy: MapProxy) { guard let image = UIImage.init(named: "DotRay") else { return } if let data = image.pngData() { proxy.mapViewController?.setPositionTrackerScaleFactor(2) proxy.mapViewController?.customizePositionTracker(data) } } ``` ##### Location Services and Location Manager[​](#location-services-and-location-manager-1 "Direct link to Location Services and Location Manager") The referenced `AppManager` and `LiveDataSourceController` classes prepare the `PositionContext` and `DataSourceContext` objects from the `GEMKit` package to make full use of the Location data. Check the full implementation for the corresponding files on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/MapPositionTracker/MapPositionTracker). info The officially supported formats for customizing the Position Tracker are .obj, .gltf and .png. --- ### Projections Last updated: April 24, 2026 | 7 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to convert the coordinates of a selected landmark to different projections and display the results in a table view. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/Projections). ![](/docs/ios/assets/images/example_ios_projections-4c15df8d857c5b7e2c316a122e370f23.png) **Projections Table** ##### Map Display and Projections Table View[​](#map-display-and-projections-table-view "Direct link to Map Display and Projections Table View") The following code outlines the main view, which displays the map and prepares the table view to show the different projections for a selected landmark: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/Projections/Projections/ViewController.swift) ```swift class ItemProjection: NSObject { var title: NSAttributedString = NSAttributedString.init(string: "") var details: NSAttributedString = NSAttributedString.init(string: "") var object: ProjectionObject? } class ViewController: UIViewController, MapViewControllerDelegate, UITableViewDataSource, UITableViewDelegate { var mapViewController: MapViewController? var projectionContext: ProjectionContext? var tableView: UITableView? var buttonExit: UIButton? var modelData: [ItemProjection] = [] var selectedLandmark: LandmarkObject? deinit { if let controller = mapViewController { controller.destroy() } } override func viewDidLoad() { super.viewDidLoad() if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.title = "Projections" self.projectionContext = ProjectionContext.init() self.createMapView() self.addTableView() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.mapViewController!.startRender() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let location = CoordinatesObject.coordinates(withLatitude: 53.592590, longitude: 9.924337) // Hamburg self.mapViewController!.center(onCoordinates: location, zoomLevel: 70, animationDuration: 0) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.mapViewController!.stopRender() } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.delegate = self self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController!.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -0), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -0) ]) } // MARK: - TableView func addTableView() { self.tableView = UITableView.init(frame: self.view.frame, style: .insetGrouped) self.tableView!.isHidden = true self.tableView!.dataSource = self self.tableView!.delegate = self self.view.addSubview(self.tableView!) self.tableView!.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.tableView!.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.tableView!.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), self.tableView!.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.tableView!.heightAnchor.constraint(equalToConstant: 360) ]) let size: CGFloat = 50 let buttonExit = UIButton.init(type: .system) buttonExit.isHidden = true buttonExit.setImage( UIImage.init(systemName: "xmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium)), for: .normal) buttonExit.addTarget(self, action: #selector(closeTableView), for: .touchUpInside) buttonExit.layer.shadowColor = UIColor.darkGray.cgColor buttonExit.layer.shadowOpacity = 0.8 buttonExit.backgroundColor = UIColor.systemBackground buttonExit.layer.cornerRadius = size / 2 self.buttonExit = buttonExit self.view.addSubview(buttonExit) buttonExit.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ buttonExit.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -5), buttonExit.bottomAnchor.constraint(equalTo: self.tableView!.topAnchor, constant: size + 5), buttonExit.widthAnchor.constraint(equalToConstant: size), buttonExit.heightAnchor.constraint(equalToConstant: size) ]) } // MARK: - MapViewControllerDelegate func mapViewController(_ mapViewController: MapViewController, didSelectLandmarks landmarks: [LandmarkObject], onTouch point: CGPoint) { guard let landmark = landmarks.first else { return } self.handleSelection(landmark: landmark) } func mapViewController( _ mapViewController: MapViewController, didSelectLandmarks landmarks: [LandmarkObject], onLongTouch point: CGPoint ) { guard let landmark = landmarks.first else { return } self.handleSelection(landmark: landmark) } func mapViewController(_ mapViewController: MapViewController, didSelectStreets streets: [LandmarkObject], onTouch point: CGPoint) { guard let landmark = streets.first else { return } self.handleSelection(landmark: landmark) } func mapViewController(_ mapViewController: MapViewController, didSelectStreets streets: [LandmarkObject], onLongTouch point: CGPoint) { guard let landmark = streets.first else { return } self.handleSelection(landmark: landmark) } // MARK: - UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let count = self.modelData.count return count } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if let lmk = self.selectedLandmark { return lmk.getLandmarkName() } return "" } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let identifier = "defaultCellId" var cell = tableView.dequeueReusableCell(withIdentifier: identifier) if cell == nil { cell = UITableViewCell.init(style: .subtitle, reuseIdentifier: identifier) cell!.textLabel!.numberOfLines = 0 cell!.detailTextLabel!.numberOfLines = 0 } let item = self.modelData[indexPath.row] cell!.textLabel?.attributedText = item.title cell!.detailTextLabel?.attributedText = item.details return cell! } // MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return UITableView.automaticDimension } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) } ``` ##### Converting Coordinates to Different Projections[​](#converting-coordinates-to-different-projections "Direct link to Converting Coordinates to Different Projections") The following code outlines the main functions to handle the landmark selection, highlight it on the map, and convert its coordinates to different projections: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/Projections/Projections/ViewController.swift#L150) ```swift // MARK: - Utils func handleSelection(landmark: LandmarkObject) { self.modelData.removeAll() let attributesMessage = self.getStringAttributes() let coordinates = landmark.getCoordinates() if coordinates.isValid() { let details = NSAttributedString.init( string: "Latitude: \(coordinates.latitude)\nLongitude: \(coordinates.longitude)", attributes: attributesMessage) let item = ItemProjection.init() item.title = self.getString(title: "World Geodetic System:") item.details = details self.modelData.append(item) } self.convertFrom(coordinates: coordinates, toType: .mgrs) { error, details in let item = ItemProjection.init() item.title = self.getString(title: "Military Grid Reference System:") item.details = self.getString(title: "", description: "Not supported.") if error == .kNoError { item.details = details } self.modelData.append(item) self.tableView!.reloadData() } self.convertFrom(coordinates: coordinates, toType: .bng) { error, details in let item = ItemProjection.init() item.title = self.getString(title: "British National Grid:") item.details = self.getString(title: "", description: "Not supported.") if error == .kNoError { item.details = details } self.modelData.append(item) self.tableView!.reloadData() } self.convertFrom(coordinates: coordinates, toType: .lam) { error, details in let item = ItemProjection.init() item.title = self.getString(title: "Lambert 93") item.details = self.getString(title: "", description: "Not supported.") if error == .kNoError { item.details = details } self.modelData.append(item) self.tableView!.reloadData() } self.convertFrom(coordinates: coordinates, toType: .utm) { error, details in let item = ItemProjection.init() item.title = self.getString(title: "Universal Transverse Mercator:") item.details = self.getString(title: "", description: "Not supported.") if error == .kNoError { item.details = details } self.modelData.append(item) self.tableView!.reloadData() } self.convertFrom(coordinates: coordinates, toType: .gk) { error, details in let item = ItemProjection.init() item.title = self.getString(title: "Gauss-Krueger") item.details = self.getString(title: "", description: "Not supported.") if error == .kNoError { item.details = details } self.modelData.append(item) self.tableView!.reloadData() } self.convertFrom(coordinates: coordinates, toType: .W3W) { error, details in let item = ItemProjection.init() item.title = self.getString(title: "What 3 Words") item.details = self.getString(title: "", description: "Not supported.") if error == .kNoError { item.details = details } self.modelData.append(item) self.tableView!.reloadData() } // let text = landmark.getLandmarkName() // let scale = UIScreen.main.scale // let image = landmark.getLandmarkImage(CGSize.init(width: 40*scale, height: 40*scale)) self.highlight(landmark: landmark) self.selectedLandmark = landmark self.tableView!.reloadData() self.tableView!.isHidden = false self.buttonExit!.isHidden = false } func highlight(landmark: LandmarkObject) { let settings = HighlightRenderSettings.init() settings.showPin = true settings.imageSize = 7 if landmark.isContourGeograficAreaEmpty() == false { settings.options = Int32( HighlightOption.showLandmark.rawValue | HighlightOption.overlap.rawValue | HighlightOption.showContour.rawValue) settings.contourInnerColor = UIColor.white settings.contourOuterColor = UIColor.systemBlue } self.mapViewController!.presentHighlights([landmark], settings: settings, highlightId: 0) if self.selectedLandmark == nil { self.mapViewController!.center(onCoordinates: landmark.getCoordinates(), zoomLevel: 70, animationDuration: 400) } } func convertFrom( coordinates: CoordinatesObject, toType: ProjectionType, completionHandler: (@escaping (_ error: SDKErrorCode, _ details: NSAttributedString) -> Void) ) { guard let projectionContext = self.projectionContext else { return } let fromProj = ProjectionWGS84Object.init(coordinates: coordinates) var toProj: ProjectionObject? switch toType { case .bng: toProj = ProjectionBNGObject.init() case .lam: toProj = ProjectionLAMObject.init() case .utm: toProj = ProjectionUTMObject.init() case .mgrs: toProj = ProjectionMGRSObject.init() case .gk: toProj = ProjectionGKObject.init() case .W3W: // // Token is available at: https://developer.what3words.com // /* toProj = ProjectionW3WObject(token: "") */ break default: completionHandler(SDKErrorCode.kNotSupported, NSMutableAttributedString.init()) } if let toProj = toProj { let attributesMessage = self.getStringAttributes() projectionContext.convert(fromProj, to: toProj) { error in let details = NSMutableAttributedString.init() if error == .kNoError { switch toProj.getType() { case .bng: if let toProj = toProj as? ProjectionBNGObject { let easting = toProj.getEasting() let northing = toProj.getNorthing() let gridRef = toProj.getGridReference() details.append(NSMutableAttributedString.init(string: "Easting: \(easting)", attributes: attributesMessage)) details.append(NSMutableAttributedString.init(string: "\nNorthing: \(northing)", attributes: attributesMessage)) details.append(NSMutableAttributedString.init(string: "\nGrid: \(gridRef)", attributes: attributesMessage)) } case .lam: if let toProj = toProj as? ProjectionLAMObject { let x = toProj.getX() let y = toProj.getY() details.append(NSMutableAttributedString.init(string: "X: \(x)", attributes: attributesMessage)) details.append(NSMutableAttributedString.init(string: "\nY: \(y)", attributes: attributesMessage)) } case .utm: if let toProj = toProj as? ProjectionUTMObject { let x = toProj.getX() let y = toProj.getY() let zone = toProj.getZone() let hemisphere = toProj.getHemisphere() == .north ? "North" : "South" details.append(NSMutableAttributedString.init(string: "X: \(x)", attributes: attributesMessage)) details.append(NSMutableAttributedString.init(string: "\nY: \(y)", attributes: attributesMessage)) details.append(NSMutableAttributedString.init(string: "\nZone: \(zone)", attributes: attributesMessage)) details.append( NSMutableAttributedString.init(string: "\nHemisphere: \(hemisphere)", attributes: attributesMessage)) } case .mgrs: if let toProj = toProj as? ProjectionMGRSObject { let easting = toProj.getEasting() let northing = toProj.getNorthing() let zone = toProj.getZone() let letters = toProj.getSq100kIdentifier() details.append(NSMutableAttributedString.init(string: "Easting: \(easting)", attributes: attributesMessage)) details.append(NSMutableAttributedString.init(string: "\nNorthing: \(northing)", attributes: attributesMessage)) details.append(NSMutableAttributedString.init(string: "\nZone: \(zone)", attributes: attributesMessage)) details.append( NSMutableAttributedString.init( string: "\n100,000 meter square id: \(letters)", attributes: attributesMessage)) } case .gk: if let toProj = toProj as? ProjectionGKObject { let easting = toProj.getEasting() let northing = toProj.getNorthing() let zone = toProj.getZone() details.append(NSMutableAttributedString.init(string: "Easting: \(easting)", attributes: attributesMessage)) details.append(NSMutableAttributedString.init(string: "\nNorthing: \(northing)", attributes: attributesMessage)) details.append(NSMutableAttributedString.init(string: "\nZone: \(zone)", attributes: attributesMessage)) } case .W3W: if let toProj = toProj as? ProjectionW3WObject { let words = toProj.getWords() details.append(NSMutableAttributedString.init(string: "Words: \(words)", attributes: attributesMessage)) } default: break } } completionHandler(error, details) } } } ``` --- ### Range Last updated: April 24, 2026 | 5 minutes read

* UIKit / SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to display multiple maps simultaneously. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/RangeView). ![](/docs/ios/assets/images/example_ios_range1-0bad04056082b998f3b921dc034f1828.png) **Range View with Settings** ![](/docs/ios/assets/images/example_ios_range2-f91b3cc36377d04c659dff29860c0fb0.png) **Calculated Ranges on Map** ##### Map Display and Range View Preparation[​](#map-display-and-range-view-preparation "Direct link to Map Display and Range View Preparation") The following code outlines the main view, which displays the map and prepares the range calculation view: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/RangeView/RangeView/ViewController.swift) ```swift class ViewController: UIViewController, MapViewControllerDelegate { var mapViewController: MapViewController? var rangeViewController: RangeViewController? var buttonExit: UIButton? deinit { if let controller = mapViewController { controller.destroy() } } override func viewDidLoad() { super.viewDidLoad() if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.title = "Range Demo" self.createMapView() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.mapViewController!.startRender() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let location = CoordinatesObject.coordinates(withLatitude: 52.517477, longitude: 13.397152) // Berlin self.mapViewController!.center(onCoordinates: location, zoomLevel: 70, animationDuration: 0) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.mapViewController!.stopRender() } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.delegate = self self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController!.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -0), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -0) ]) } // MARK: - TableView func addRangeView() { let rangeViewController = RangeViewController.init(mapViewController: self.mapViewController!) rangeViewController.view.layer.shadowColor = UIColor.darkGray.cgColor rangeViewController.view.layer.shadowOpacity = 0.8 self.view.addSubview(rangeViewController.view) rangeViewController.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ rangeViewController.view!.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), rangeViewController.view!.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), rangeViewController.view!.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), rangeViewController.view!.heightAnchor.constraint(equalToConstant: 360) ]) let size: CGFloat = 50 let buttonExit = UIButton.init(type: .system) buttonExit.setImage( UIImage.init(systemName: "xmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium)), for: .normal) buttonExit.addTarget(self, action: #selector(closeRangeView), for: .touchUpInside) buttonExit.layer.shadowColor = UIColor.darkGray.cgColor buttonExit.layer.shadowOpacity = 0.8 buttonExit.backgroundColor = UIColor.systemBackground buttonExit.layer.cornerRadius = size / 2 self.buttonExit = buttonExit self.view.addSubview(buttonExit) buttonExit.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ buttonExit.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -5), buttonExit.bottomAnchor.constraint(equalTo: rangeViewController.view!.topAnchor, constant: size - 30), buttonExit.widthAnchor.constraint(equalToConstant: size), buttonExit.heightAnchor.constraint(equalToConstant: size) ]) self.rangeViewController = rangeViewController } ``` ##### Range View Actions[​](#range-view-actions "Direct link to Range View Actions") The following code outlines the main functions to handle the range view actions, such as closing the view and handling the range operations, including adding, deleting, and selecting range routes: info For this example the Range UI that shows the different route settings and range actions is done in a SwiftUI View for the sake of simplicity. You can find the full implementation of the `RangeView` in the GitHub repository linked above. RangeViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/RangeView/RangeView/RangeViewController.swift) ```swift func prepareView() { guard let mapViewController = self.mapViewController else { return } self.rangeView = RangeView(model: self.model) self.rangeView!.didSelectOptionItem = { [weak self] item in guard let strongSelf = self else { return } guard let rangeView = strongSelf.rangeView else { return } let alert = strongSelf.presentOptionsMenu( title: item.title, options: item.options, selectedIndex: item.chosenOption ) { newIndex in rangeView.updateSettingsOnOption(item: item, newOption: newIndex) } if let alert = alert { strongSelf.present(alert, animated: true, completion: nil) } } self.rangeView!.didSelectTransportMode = { [weak self] in guard let strongSelf = self else { return } guard let rangeView = strongSelf.rangeView else { return } let alert = strongSelf.presentOptionsMenu( title: "Transport Mode", options: [ "Car", "Pedestrian", "Bicycle", "Truck" ], selectedIndex: strongSelf.model.mode.rawValue ) { newIndex in if strongSelf.model.mode.rawValue != newIndex { rangeView.updateTransportModeOnOption(newOption: newIndex) } } if let alert = alert { strongSelf.present(alert, animated: true, completion: nil) } } self.rangeView!.onRangeAdded = { [weak self] in guard let strongSelf = self else { return } if strongSelf.model.isRangeValueUsed(value: strongSelf.model.rangeValue, transportMode: strongSelf.model.mode) == false { strongSelf.calculateRangeRoutes() } } self.rangeView!.onRangeDeleted = { [weak self] item in guard let strongSelf = self else { return } mapViewController.removeRoutes(item.routes) strongSelf.colors[item.routeColor] = false strongSelf.centerOnRoutes(routes: strongSelf.model.getSelectedRangeRoutes()) } self.rangeView!.onRangeSelected = { [weak self] item in guard self != nil else { return } if item.isSelected { // let insets = strongSelf.calculateAreaInset(margin: 10) // mapViewController.setEdgeAreaInsets(insets) mapViewController.presentRoutes(item.routes, withTraffic: nil, showSummary: false, animationDuration: 200) // mapViewController.setDebugEdgeAreaVisible(true) if let route = item.routes.first { let renderSettings = MapViewRouteRenderSettings.init() renderSettings.options = Int32(MapViewRouteRenderOption.main.rawValue) renderSettings.fillColor = item.routeColor renderSettings.contourInnerSize = 0.3 renderSettings.contourOuterSize = 0.3 let preferences = mapViewController.getPreferences() preferences.setRenderSettings(renderSettings, route: route) } } else { mapViewController.removeRoutes(item.routes) } } let controller = UIHostingController.init(rootView: self.rangeView!) self.addChild(controller) self.view.addSubview(controller.view) controller.didMove(toParent: self) controller.view.translatesAutoresizingMaskIntoConstraints = false let array: [NSLayoutConstraint] = [ controller.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0), controller.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0), controller.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -0), controller.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -0) ] NSLayoutConstraint.activate(array) controller.view.backgroundColor = .systemBackground } ``` ##### Range Calculation[​](#range-calculation "Direct link to Range Calculation") The following code demonstrates how to calculate the range based on the selected settings in the range view and present the results on the map: RangeViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/RangeView/RangeView/RangeViewController.swift) ```swift // MARK: - Draw Routes func calculateRangeRoutes() { guard let mapViewController = self.mapViewController else { return } guard let rangeLandmark = landmark else { return } let value = NSNumber.init(value: model.rangeType == .time ? Int(model.rangeValue) * 60 : Int(model.rangeValue)) let routePreferences = self.getRoutePreferences(transportMode: model.mode) //Range value setting for the route calculation. routePreferences.setRouteRanges([value], quality: 100) self.navigationContext = NavigationContext.init(preferences: routePreferences) self.model.isCalculating = true self.navigationContext.calculateRoute( withWaypoints: [rangeLandmark], completionHandler: { [weak self] (results: [RouteObject]) in guard let strongSelf = self else { return } strongSelf.model.isCalculating = false if !results.isEmpty, let route = results.first { let rangeRouteItem = RangeValueRoutesItem( rangeType: strongSelf.model.rangeType, rangeValue: strongSelf.model.rangeValue, routes: [route], transportMode: strongSelf.model.mode) strongSelf.model.addPresentedRoutes(rangeRouteItem) let color: UIColor = strongSelf.model.presentedRoutes.count > 1 ? (strongSelf.colors.someKey(forValue: false) ?? strongSelf.initialColor) : strongSelf.initialColor // let insets = strongSelf.calculateAreaInset(margin: 10) // mapViewController.setEdgeAreaInsets(insets) mapViewController.presentRoutes(results, withTraffic: nil, showSummary: false, animationDuration: 800) // mapViewController.setDebugEdgeAreaVisible(true) rangeRouteItem.routeColor = color strongSelf.colors[color] = true let renderSettings = MapViewRouteRenderSettings.init() renderSettings.options = Int32(MapViewRouteRenderOption.main.rawValue) renderSettings.fillColor = color renderSettings.contourInnerSize = 0.3 renderSettings.contourOuterSize = 0.3 let preferences = mapViewController.getPreferences() preferences.setRenderSettings(renderSettings, route: route) } }) } ``` --- ### Shapes Last updated: April 24, 2026 | 5 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to draw Shapes on the map using `Markers`. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/Shapes). ![](/docs/ios/assets/images/example_ios_shapes_uikit-e22598718c5d4f3fe5d1726c8c3fe0d9.png) **Shapes drawn on Map** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The following code outlines the main view, which displays the map and creates the action buttons: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/Shapes/Shapes/ViewController.swift) ```swift class ViewController: UIViewController { var mapViewController: MapViewController? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.title = "Shapes" self.createMapView() self.mapViewController!.startRender() self.mapViewController!.setEdgeAreaInsets(self.areaEdge(margin: 30)) self.mapViewController!.hideCompass() self.addShapesButton() } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController!.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -0), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -0) ]) } // MARK: - Shapes func addShapesButton() { var image = UIImage.init(systemName: "circle") let barButton1 = UIBarButtonItem.init(image: image, style: .done, target: self, action: #selector(toggleCircle(_:))) barButton1.tag = 1 image = UIImage.init(systemName: "line.diagonal") let barButton2 = UIBarButtonItem.init(image: image, style: .done, target: self, action: #selector(togglePolylines(_:))) barButton2.tag = 2 image = UIImage.init(systemName: "triangle") let barButton3 = UIBarButtonItem.init(image: image, style: .done, target: self, action: #selector(togglePolygon(_:))) barButton3.tag = 3 image = UIImage.init(systemName: "clear") let barButton = UIBarButtonItem.init(image: image, style: .done, target: self, action: #selector(clearAll)) self.navigationItem.rightBarButtonItems = [barButton1, barButton2, barButton3] self.navigationItem.leftBarButtonItem = barButton } ``` ##### Drawing the Shapes and handling Marker Collections[​](#drawing-the-shapes-and-handling-marker-collections "Direct link to Drawing the Shapes and handling Marker Collections") The following code implements the methods to draw the shapes, by creating a `MarkerCollectionObject` for each one, and the methods for general Marker handling: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/3DScene/Shapes/Shapes/ViewController.swift) ```swift @objc func toggleCircle(_ barButton: UIBarButtonItem) { let name = "My Circle" if let marker = self.markerAvailable(name: name) { self.mapViewController!.removeMarker(marker) } else { let coordinates = CoordinatesObject.coordinates(withLatitude: 52.350934, longitude: 4.886882) let marker = MarkerObject.init(circleCenter: coordinates, radius: 3000) let markerCollection = MarkerCollectionObject.init(name: name, type: .polygon) markerCollection.addMarker(marker) markerCollection.setInnerSize(0.6) markerCollection.setInnerColor(UIColor.red) markerCollection.setOuterSize(1.2) markerCollection.setOuterColor(UIColor.black) markerCollection.setFill(UIColor.yellow.withAlphaComponent(0.25)) self.mapViewController!.addMarker(markerCollection, animationDuration: 100) } } @objc func togglePolylines(_ barButton: UIBarButtonItem) { let name = "My Polyline" if let marker = self.markerAvailable(name: name) { self.mapViewController!.removeMarker(marker) } else { let coordinates = self.generatePolylinesCoordinates() let marker = MarkerObject.init(coordinates: coordinates) let markerCollection = MarkerCollectionObject.init(name: name, type: .polyline) markerCollection.addMarker(marker) markerCollection.setInnerSize(1.0) markerCollection.setInnerColor(UIColor.red) markerCollection.setOuterSize(1.2) markerCollection.setOuterColor(UIColor.black) self.mapViewController!.addMarker(markerCollection, animationDuration: 100) } } @objc func togglePolygon(_ barButton: UIBarButtonItem) { let name = "My Polygon" if let marker = self.markerAvailable(name: name) { self.mapViewController!.removeMarker(marker) } else { let coordinates = self.generatePolygonCoordinates() let marker = MarkerObject.init(coordinates: coordinates) let markerCollection = MarkerCollectionObject.init(name: name, type: .polygon) markerCollection.addMarker(marker) markerCollection.setInnerSize(0.6) markerCollection.setInnerColor(UIColor.red) markerCollection.setOuterSize(1.2) markerCollection.setOuterColor(UIColor.black) markerCollection.setFill(UIColor.yellow.withAlphaComponent(0.25)) self.mapViewController!.addMarker(markerCollection, animationDuration: 100) } } @objc func clearAll() { let allMarkers = self.mapViewController!.getAvailableMarkers() for marker in allMarkers { self.mapViewController!.removeMarker(marker) } } // MARK: - Utils func generatePolylinesCoordinates() -> [CoordinatesObject] { var coordinates: [CoordinatesObject] = [] coordinates.append(CoordinatesObject.coordinates(withLatitude: 52.360495, longitude: 4.936882)) coordinates.append(CoordinatesObject.coordinates(withLatitude: 52.360495, longitude: 4.836882)) return coordinates } func generatePolygonCoordinates() -> [CoordinatesObject] { var coordinates: [CoordinatesObject] = [] coordinates.append(CoordinatesObject.coordinates(withLatitude: 52.340234, longitude: 4.886882)) coordinates.append(CoordinatesObject.coordinates(withLatitude: 52.300495, longitude: 4.936882)) coordinates.append(CoordinatesObject.coordinates(withLatitude: 52.300495, longitude: 4.836882)) return coordinates } func markerAvailable(name: String) -> MarkerCollectionObject? { let allMarkers = self.mapViewController!.getAvailableMarkers() for marker in allMarkers { let markerName = marker.getName() if markerName == name { return marker } } return nil } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to draw Shapes on the map using `Markers`. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/Shapes). ![](/docs/ios/assets/images/example_ios_shapes_swiftui-abc40b4da45ebbe42480a63b24ddd101.png) **Shapes drawn on Map** ##### UI and Map Integration with Markers Content[​](#ui-and-map-integration-with-markers-content "Direct link to UI and Map Integration with Markers Content") The following code outlines the main view, which displays the map, prepares the `MarkerCollectionObject` arrays and links those markers to the `MapBase` content building closure: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/Shapes/Shapes/ContentView.swift) ```swift enum ShapeType: Int { case triangle, circle, line } struct ContentView: View { @State var collType1: [MarkerCollectionObject] = [] @State var collType2: [MarkerCollectionObject] = [] @State var collType3: [MarkerCollectionObject] = [] var body: some View { MapReader { proxy in ZStack { MapBase(initialPosition: .amsterdam, initialZoomLevel: 56, content: { MapMarker(title: "A", collections: collType1) MapMarker(title: "B", collections: collType2) MapMarker(title: "C", collections: collType3) }) .mapCompass(false) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("", systemImage: "triangle") { refreshMarkerCollections(proxy, .triangle) } .buttonStyle(.borderedProminent) } ToolbarItem(placement: .topBarTrailing) { Button("", systemImage: "circle") { refreshMarkerCollections(proxy, .circle) } .buttonStyle(.borderedProminent) } ToolbarItem(placement: .topBarTrailing) { Button("", systemImage: "line.diagonal") { refreshMarkerCollections(proxy, .line) } .buttonStyle(.borderedProminent) } ToolbarItem(placement: .topBarLeading) { Button("", systemImage: "x.square") { removeAllMarkers(proxy) } .buttonStyle(.borderedProminent) } } .ignoresSafeArea() } } } ``` ##### Drawing the Shapes[​](#drawing-the-shapes "Direct link to Drawing the Shapes") The following code implements the methods to draw the shapes by adding markers to the previously created collections: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/3DScene/Shapes/Shapes/ContentView.swift) ```swift func refreshMarkerCollections(_ proxy: MapProxy, _ type: ShapeType) { if type == .triangle { if !collType1.isEmpty { collType1.removeAll() return } let markerCollection = MarkerCollectionObject.init(name: "My Triangle", type: .polygon) markerCollection.setInnerSize(0.8) markerCollection.setInnerColor(UIColor.yellow) markerCollection.setOuterSize(1.2) markerCollection.setOuterColor(UIColor.black) markerCollection.setFill(UIColor.yellow.withAlphaComponent(0.25)) markerCollection.addMarker( MarkerObject.init(coordinates: [ .coordinates(withLatitude: 52.390934, longitude: 4.896882), .coordinates(withLatitude: 52.379934, longitude: 4.875082), .coordinates(withLatitude: 52.379934, longitude: 4.896882) ]) ) collType1.append(markerCollection) } else if type == .circle { if !collType2.isEmpty { collType2.removeAll() return } let markerCollection = MarkerCollectionObject.init(name: "My Circle", type: .polygon) markerCollection.setInnerSize(0.8) markerCollection.setInnerColor(UIColor.yellow) markerCollection.setOuterSize(1.2) markerCollection.setOuterColor(UIColor.black) markerCollection.setFill(UIColor.yellow.withAlphaComponent(0.25)) markerCollection.addMarker( MarkerObject.init( circleCenter: .coordinates(withLatitude: 52.379934, longitude: 4.896882), radius: 800) ) collType2.append(markerCollection) } else if type == .line { if !collType3.isEmpty { collType3.removeAll() return } let markerCollection = MarkerCollectionObject.init(name: "My Line", type: .polyline) markerCollection.setInnerSize(0.8) markerCollection.setInnerColor(UIColor.yellow) markerCollection.setOuterSize(1.2) markerCollection.setOuterColor(UIColor.black) markerCollection.setFill(UIColor.yellow.withAlphaComponent(0.25)) markerCollection.addMarker( MarkerObject.init(coordinates: [ .coordinates(withLatitude: 52.370934, longitude: 4.907082), .coordinates(withLatitude: 52.370934, longitude: 4.875082) ]) ) collType3.append(markerCollection) } } func removeAllMarkers(_ proxy: MapProxy) { collType1.removeAll() collType2.removeAll() collType3.removeAll() } ``` --- ### Places & Search These articles cover places and search functionality for both UIKit and SwiftUI implementations. [![Search Text image](/docs/ios/assets/images/1_search_text-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/places-search/search-text.md) ##### [Search Text](/docs/ios/examples/places-search/search-text.md) [Search places using a free-text query.](/docs/ios/examples/places-search/search-text.md) [![Group Search Results image](/docs/ios/assets/images/2_group_search_results-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/places-search/group-search-results.md) ##### [Group Search Results](/docs/ios/examples/places-search/group-search-results.md) [Display grouped search results as highlights on the map.](/docs/ios/examples/places-search/group-search-results.md) [![Search Category image](/docs/ios/assets/images/3_search_category-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/places-search/search_category.md) ##### [Search Category](/docs/ios/examples/places-search/search_category.md) [Find landmarks by a selected category.](/docs/ios/examples/places-search/search_category.md) [![Search on Map image](/docs/ios/assets/images/4_search_on_map-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/places-search/search-on-map.md) ##### [Search on Map](/docs/ios/examples/places-search/search-on-map.md) [Search around the current map center or by query.](/docs/ios/examples/places-search/search-on-map.md) [![What's Nearby image](/docs/ios/assets/images/5_whats_nearby-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/places-search/whats-nearby.md) ##### [What's Nearby](/docs/ios/examples/places-search/whats-nearby.md) [Search for nearby landmarks around a selected location.](/docs/ios/examples/places-search/whats-nearby.md) [![Search Along Route image](/docs/ios/assets/images/6_search_along_route-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/places-search/search-along-route.md) ##### [Search Along Route](/docs/ios/examples/places-search/search-along-route.md) [Search for landmarks along a route corridor.](/docs/ios/examples/places-search/search-along-route.md) [![Address Search image](/docs/ios/assets/images/7_address_search-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/places-search/address-search.md) ##### [Address Search](/docs/ios/examples/places-search/address-search.md) [Resolve a full address step-by-step from country through state, city, street, and house number.](/docs/ios/examples/places-search/address-search.md) --- ### Address Search Last updated: April 24, 2026 | 3 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to resolve a full street address step-by-step, from country detection through state, city, street, and house number, using the hierarchical `addressSearch` API of `SearchContext`. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/AddressSearch). ![](/docs/ios/assets/images/example_ios_address_search1-cb22959f8a3fa0490fe95b522f0955fe.png) **Address Search Progress** ![](/docs/ios/assets/images/example_ios_address_search2-3c623aecbf2a86af38a40769c144ad47.png) **Address Search Result** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The view controller embeds a `MapViewController` and a `SearchContext`. A progress panel tracks each search stage with spinners and checkmarks as the search progresses: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/AddressSearch/AddressSearch/ViewController.swift) ```swift class ViewController: UIViewController, MapViewControllerDelegate { var mapViewController: MapViewController? var searchContext: SearchContext? override func viewDidLoad() { super.viewDidLoad() self.title = "Address Search" self.navigationItem.largeTitleDisplayMode = .never self.createMapView() self.createProgressPanel() self.mapViewController!.startRender() self.addSearchButton() } ``` ##### Step 1: Detecting the Country from Coordinates[​](#step-1-detecting-the-country-from-coordinates "Direct link to Step 1: Detecting the Country from Coordinates") The search starts by resolving a coordinate to a country using `addressSearchGetCountry(withCoordinates:)`. If the country has a state level, the state search is started next; otherwise the search jumps directly to city: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/AddressSearch/AddressSearch/ViewController.swift) ```swift @objc func searchAddress() { if self.searchContext == nil { self.searchContext = SearchContext.init() } showProgress() updateStage(.country, active: true) let location = CoordinatesObject.coordinates(withLatitude: 37.33141, longitude: -122.03042) let country = self.searchContext!.addressSearchGetCountry(withCoordinates: location) updateStage(.country, completed: true) self.searchContext!.setAddressSearchMaximumMatches(40) if self.searchContext!.hasAddressSearchState(withCountry: country) { updateStage(.state, active: true) self.searchState(inCountry: country) } else { updateStage(.state, skipped: true) updateStage(.city, active: true) self.searchCity(inCountry: country) } } ``` ##### Step 2: Searching State and City[​](#step-2-searching-state-and-city "Direct link to Step 2: Searching State and City") Each level is resolved by passing the result of the previous level as the parent landmark to `addressSearch(withLandmark:level:query:)`. The state search uses `.state` level, then hands off to the city search at `.city` level: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/AddressSearch/AddressSearch/ViewController.swift) ```swift func searchState(inCountry: LandmarkObject) { self.searchContext! .addressSearch(withLandmark: inCountry, level: .state, query: "California") { [weak self] (results: [LandmarkObject]) in guard let strongSelf = self else { return } if let state = results.first { strongSelf.updateStage(.state, completed: true) strongSelf.updateStage(.city, active: true) DispatchQueue.main.async { strongSelf.searchCity(inState: state) } } } } func searchCity(inState: LandmarkObject) { self.searchContext! .addressSearch(withLandmark: inState, level: .city, query: "Cuppertino") { [weak self] (results: [LandmarkObject]) in guard let strongSelf = self else { return } if let city = results.first { strongSelf.updateStage(.city, completed: true) strongSelf.updateStage(.street, active: true) DispatchQueue.main.async { strongSelf.searchStreet(inCity: city) } } } } ``` ##### Step 3: Searching Street and House Number[​](#step-3-searching-street-and-house-number "Direct link to Step 3: Searching Street and House Number") The chain continues down to `.street` and `.houseNumber` levels. Once the house number is resolved, the result is highlighted on the map and the camera animates to its coordinates: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/AddressSearch/AddressSearch/ViewController.swift) ```swift func searchStreet(inCity: LandmarkObject) { self.searchContext! .addressSearch(withLandmark: inCity, level: .street, query: "Infinite Loop") { [weak self] (results: [LandmarkObject]) in guard let strongSelf = self else { return } if let street = results.first { strongSelf.updateStage(.street, completed: true) strongSelf.updateStage(.houseNumber, active: true) DispatchQueue.main.async { strongSelf.searchHouseNumber(inStreet: street) } } } } func searchHouseNumber(inStreet: LandmarkObject) { self.searchContext! .addressSearch(withLandmark: inStreet, level: .houseNumber, query: "1") { [weak self] (results: [LandmarkObject]) in guard let strongSelf = self else { return } if let houseNumber = results.first { strongSelf.updateStage(.houseNumber, completed: true) strongSelf.hideProgress() strongSelf.mapViewController!.removeHighlights() let settings = HighlightRenderSettings.init() settings.showPin = true settings.imageSize = 7 if houseNumber.isContourGeograficAreaEmpty() == false { settings.options = Int32( HighlightOption.showLandmark.rawValue | HighlightOption.overlap.rawValue | HighlightOption.showContour.rawValue) settings.contourInnerColor = UIColor.orange settings.contourOuterColor = UIColor.orange } strongSelf.mapViewController!.presentHighlights([houseNumber], settings: settings) strongSelf.mapViewController!.center(onCoordinates: houseNumber.getCoordinates(), zoomLevel: -1, animationDuration: 800) } } } ``` --- ### Group Search Results Last updated: April 24, 2026 | 6 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to display and center the results of a text search on the map as `Highlights`, with grouping enabled and a custom pin image. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/GroupSearchResults). ![](/docs/ios/assets/images/example_ios_group_search_results-e7be6ff0d26e109a63cda4f351584809.png) **Grouped Search Results** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The layout stacks a search field (with an activity indicator), a map container, a results table, and a "Center Results" button. `MapViewController` is embedded inside the tagged `mapContainer` view and pinned to its edges: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/GroupSearchResults/GroupSearchResults/ViewController.swift) ```swift func createMapView() { guard let mapContainer = self.view.viewWithTag(100) else { return } self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = .systemBackground self.addChild(self.mapViewController!) mapContainer.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController!.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: mapContainer.topAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: mapContainer.leadingAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: mapContainer.bottomAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: mapContainer.trailingAnchor), ]) } ``` ##### Performing Search and Setting a Custom Pin Image[​](#performing-search-and-setting-a-custom-pin-image "Direct link to Performing Search and Setting a Custom Pin Image") `performSearch(text:)` clears previous results, sets a rectangular location hint around Amsterdam, then calls `searchContext.search(withQuery:location:)`. Each returned `LandmarkObject` receives a custom pin image via `setImage(_:)` before being added to the landmarks array. After the search completes, `presentHighlights(_:settings:highlightId:)` renders all results on the map: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/GroupSearchResults/GroupSearchResults/ViewController.swift) ```swift func performSearch(text: String) { guard let mapViewController = self.mapViewController else { return } mapViewController.removeHighlights() let amsterdam = CoordinatesObject.coordinates(withLatitude: 52.368447, longitude: 4.888229) mapViewController.center(onCoordinates: amsterdam, zoomLevel: 60, animationDuration: 0) landmarks.removeAll() results.removeAll() selectedIndex = nil tableView.reloadData() activityIndicator.startAnimating() searchContext.setLocationHint( RectangleGeographicAreaObject( location: amsterdam, horizontalRadius: 2000, verticalRadius: 2000)) searchContext.search(withQuery: text, location: amsterdam) { [weak self] (response: [LandmarkObject]) in guard let self = self else { return } self.activityIndicator.stopAnimating() for item in response { item.setImage(self.getImageObject()) self.landmarks.append(item) } self.results = response.map { item in let coords = item.getCoordinates() return Place( title: item.getLandmarkName(), details: "Lat:\(coords.latitude), Long:\(coords.longitude)", landmark: item) } let settings = self.getHighlightSettings() mapViewController.presentHighlights(self.landmarks, settings: settings, highlightId: self.defaultHighlightId) self.centerButton.isEnabled = !self.landmarks.isEmpty self.tableView.reloadData() } } func getImageObject() -> ImageObject { if let image = UIImage(named: "MapPinDefault"), let data = image.pngData() { return ImageObject(dataBuffer: data, format: .png) } return ImageObject() } ``` ##### Center on Highlight Area and Highlight Settings[​](#center-on-highlight-area-and-highlight-settings "Direct link to Center on Highlight Area and Highlight Settings") Tapping a row in the table centers the map on that result's coordinates. The "Center Results" button calls `getHighlightArea(_:)` to retrieve the bounding area of all highlights and then uses `center(onArea:zoomLevel:animationDuration:)` to fit them all in view. `getHighlightSettings()` configures grouping, landmark display, and text/image sizes: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/GroupSearchResults/GroupSearchResults/ViewController.swift) ```swift func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) guard let mapViewController = self.mapViewController else { return } selectedIndex = indexPath.row tableView.reloadData() let place = results[indexPath.row] mapViewController.center(onCoordinates: place.landmark.getCoordinates(), zoomLevel: -1, animationDuration: 1200) } @objc func centerResultsTapped() { guard let mapViewController = self.mapViewController else { return } selectedIndex = nil tableView.reloadData() let list = mapViewController.getHighlight(defaultHighlightId) guard !list.isEmpty else { return } guard let area = mapViewController.getHighlightArea(defaultHighlightId) else { return } mapViewController.center(onArea: area, zoomLevel: -1, animationDuration: 1200) } func getHighlightSettings() -> HighlightRenderSettings { let settings = HighlightRenderSettings.init() settings.options = Int32( HighlightOption.showLandmark.rawValue | HighlightOption.group.rawValue | HighlightOption.selectable.rawValue) settings.textColor = UIColor.darkGray settings.textSize = 2.2 settings.imageSize = 5.6 return settings } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to display and center the results of a text search on the map as `Highlights`, with grouping enabled and a custom pin image. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/GroupSearchResults). ![](/docs/ios/assets/images/example_ios_group_search_results-e7be6ff0d26e109a63cda4f351584809.png) **Grouped Search Results** ##### UI and Map Integration[​](#ui-and-map-integration-1 "Direct link to UI and Map Integration") The following code outlines the search result item structure and the map view with a simple search field: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/GroupSearchResults/GroupSearchResults/ContentView.swift) ```swift struct Place: Identifiable, Equatable { let id = UUID() let title: String let details: String let lmk: LandmarkObject } struct ContentView: View { let context = SearchContext.init() private let defaultHighlightId = 10 @State private var searchQuery = "" @State private var landmarks: [LandmarkObject] = [] @State private var results: [Place] = [] @State private var selectedItem: Place? @State private var isSearching: Bool = false @Environment(\.displayScale) var displayScale var body: some View { MapReader { proxy in VStack { HStack { TextField( "Search", text: $searchQuery, onCommit: { performSearch(proxy) } ) .textFieldStyle(RoundedBorderTextFieldStyle()) if isSearching { ProgressView() .transition(.opacity) .progressViewStyle(.circular) .controlSize(.regular) } } .frame(height: 50) MapBase(initialPosition: .amsterdam, initialZoomLevel: 64) { MapLandmark( landmarks: landmarks, renderSettings: getSettings(), highlightId: defaultHighlightId, animationDuration: 800) } .didSelectLandmarks({ landmarks, touchPoint, isLongTouch in guard let coordinates = landmarks.first?.getCoordinates() else { return } proxy.centerOn(coordinates: coordinates, duration: 1200) }) .mapCompass(false) .mapEdgeInsets(getInsets()) .frame(height: 300) List(results) { place in VStack(alignment: .leading) { Text(place.title) .font(.headline) Text(place.details) .font(.subheadline) .foregroundColor(.secondary) } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) .onTapGesture { proxy.centerOn(coordinates: place.lmk.getCoordinates(), duration: 1200) withAnimation(.easeInOut(duration: 0.2)) { selectedItem = place } } .listRowBackground( selectedItem == place ? Color.blue.opacity(0.2) : Color.clear ) } Button { selectedItem = nil centerOnHighlightArea(proxy) } label: { Text("Center Results") .font(.headline) .foregroundStyle(.blue) } .padding() .buttonStyle(PlainButtonStyle()) .disabled(landmarks.isEmpty) } } .padding(.horizontal, 15) } ``` ##### Performing Search and setting Custom Pin Image[​](#performing-search-and-setting-custom-pin-image "Direct link to Performing Search and setting Custom Pin Image") The following code demonstrates how to perform a search with a location hint and set a custom pin image for the search results: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/GroupSearchResults/GroupSearchResults/ContentView.swift) ```swift private func performSearch(_ proxy: MapProxy) { proxy.removeAllHighlights() proxy.centerOn(coordinates: .amsterdam, zoomLevel: 60) landmarks.removeAll() results.removeAll() isSearching = true // Location Hit support: narrow the search area to a specific radius context.setLocationHint( RectangleGeographicAreaObject( location: .amsterdam, horizontalRadius: 2000, verticalRadius: 2000)) context.search(withQuery: searchQuery, location: .amsterdam) { response in isSearching = false for item in response { item.setImage(getImageObject()) // set custom pin image landmarks.append(item) } results = response.map { item in let coordinates = item.getCoordinates() return Place( title: item.getLandmarkName(), details: "Lat:\(coordinates.latitude), Long:\(coordinates.longitude)", lmk: item) } } } private func getImageObject() -> ImageObject { if let image = UIImage.init(named: "MapPinDefault"), let data = image.pngData() { let object = ImageObject.init(dataBuffer: data, format: .png) return object } return ImageObject() } ``` ##### Center on Highlight Area and Helper Methods[​](#center-on-highlight-area-and-helper-methods "Direct link to Center on Highlight Area and Helper Methods") The following code demonstrates how to center the map on the area of the search results and includes helper methods for highlight settings and map insets: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/GroupSearchResults/GroupSearchResults/ContentView.swift) ```swift private func getSettings() -> HighlightRenderSettings { let settings = HighlightRenderSettings.init() settings.options = Int32( HighlightOption.showLandmark.rawValue | HighlightOption.group.rawValue | HighlightOption.selectable.rawValue) settings.textColor = UIColor.darkGray settings.textSize = 2.2 settings.imageSize = 5.6 return settings } func centerOnHighlightArea(_ proxy: MapProxy) { guard let mapViewController = proxy.mapViewController else { return } let list = mapViewController.getHighlight(Int32(defaultHighlightId)) guard !list.isEmpty else { return } guard let area = mapViewController.getHighlightArea(Int32(defaultHighlightId)) else { return } mapViewController.center(onArea: area, zoomLevel: -1, animationDuration: 1200) } func getInsets() -> UIEdgeInsets { let margin: CGFloat = 35 return UIEdgeInsets.init( top: displayScale * margin, left: displayScale * margin, bottom: displayScale * margin, right: displayScale * margin) } } ``` --- ### Search Category Last updated: March 13, 2026 | 4 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to search for `Landmarks` in a specific category. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchCategory). ![](/docs/ios/assets/images/example_ios_search_category1-cbf73bfdd5ad9b0bd51fc429c2d2f8e1.png) **Initial screen** ![](/docs/ios/assets/images/example_ios_search_category2-ba029636e814c3f264568dec932803c1.png) **Categories List** ![](/docs/ios/assets/images/example_ios_search_category3-a3406102b0389b78f9b89e4aebd99e4d.png) **Gas Station Category Search** ##### Map Display and Search Button[​](#map-display-and-search-button "Direct link to Map Display and Search Button") The following code outlines the map view with a search button in the navigation bar. Tapping the search button will navigate to a list of categories: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchCategory/SearchCategory/ViewController.swift) ```swift class ViewController: UIViewController { var mapViewController: MapViewController? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.createMapView() self.mapViewController!.startRender() self.addSearchButton() } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) } // MARK: - Search func addSearchButton() { let barButton = UIBarButtonItem.init( image: UIImage.init(systemName: "magnifyingglass"), style: .done, target: self, action: #selector(searchButton(item:))) self.navigationItem.rightBarButtonItems = [barButton] } @objc func searchButton(item: UIBarButtonItem) { let poiListViewController = PoiCategoriesViewController.init() self.navigationController?.pushViewController(poiListViewController, animated: true) } } ``` ##### Preparing Categories List and Performing Search[​](#preparing-categories-list-and-performing-search "Direct link to Preparing Categories List and Performing Search") The code for `PoiCategoriesViewController` prepares a list of categories and performs a search when a category is selected. The search results are then highlighted on the map: PoiCategoriesViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchCategory/SearchCategory/PoiCategoriesViewController.swift) ```swift class PoiCategoriesViewController: UITableViewController { let searchContext = SearchContext.init() let categoriesContext = GenericCategoriesContext.init() var categoriesList: [LandmarkCategoryObject] = [] deinit { NSLog("PoiCategoriesViewController: deinit") } // MARK: - Life Cycle override func viewDidLoad() { super.viewDidLoad() self.title = "Poi Categories" self.navigationItem.largeTitleDisplayMode = .never self.view.backgroundColor = UIColor.systemBackground self.searchContext.setMaxMatches(40) self.searchContext.setSearchMapPOIs(true) self.refreshList() } // MARK: - Refresh func refreshList() { self.categoriesList = self.categoriesContext.getCategories() self.tableView.reloadData() } // MARK: - UITableViewData override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let rows = self.categoriesList.count return rows } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let identifier = "defaultCellId" var cell = tableView.dequeueReusableCell(withIdentifier: identifier) if cell == nil { cell = UITableViewCell.init(style: .subtitle, reuseIdentifier: identifier) cell!.textLabel!.numberOfLines = 0 cell!.detailTextLabel!.numberOfLines = 0 } self.setupText(tableView: tableView, cell: cell!, indexPath: indexPath) self.setupImage(tableView: tableView, cell: cell!, indexPath: indexPath) return cell! } func setupText(tableView: UITableView, cell: UITableViewCell, indexPath: IndexPath) { let category = self.categoriesList[indexPath.row] let text = category.getName() cell.textLabel?.text = text let description = "id: " + String(category.getIdentifier()) cell.detailTextLabel?.text = description } func setupImage(tableView: UITableView, cell: UITableViewCell, indexPath: IndexPath) { let scale = UIScreen.main.scale let size = CGSize.init(width: 60.0 * scale, height: 60.0 * scale) let category = self.categoriesList[indexPath.row] if let image = category.getImage(size) { cell.imageView?.image = image cell.imageView?.layer.shadowOpacity = 0.8 cell.imageView?.layer.shadowColor = UIColor.lightGray.cgColor } else { cell.imageView?.image = nil cell.imageView?.layer.shadowOpacity = 0 cell.imageView?.layer.shadowColor = nil } } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return nil } override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return 80 } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 80 } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let category = self.categoriesList[indexPath.row] let success = self.searchContext.setCategory(category) guard success == true else { return } if let viewController = self.navigationController?.viewControllers.first as? ViewController { self.navigationController?.popViewController(animated: true) if let mapViewController = viewController.mapViewController { let location = CoordinatesObject.coordinates(withLatitude: 48.840827, longitude: 2.371899) mapViewController.removeHighlights() mapViewController.center(onCoordinates: location, zoomLevel: 60, animationDuration: 1200) self.searchContext.searchAround(withLocation: location) { (results: [LandmarkObject]) in let settings = HighlightRenderSettings.init() mapViewController.presentHighlights(results, settings: settings) } } } } } ``` --- ### Search Along Route Last updated: April 24, 2026 | 5 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to calculate a car route, search for landmarks along it using `SearchContext`, and display the results as grouped `Highlights` on the map. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchAlongRoute). info `SearchContext` settings and parameters showcased in the other examples, such as category and query, can also be applied in this scenario to further refine the search results. ![](/docs/ios/assets/images/example_ios_search_along_route1-9ceaf62e90b793b6aefdd0624cb092f1.png) **Route Calculated** ![](/docs/ios/assets/images/example_ios_search_along_route2-86b40bd6443517f1910acf128f7c1dbe.png) **Search Along Route Results** ##### Map View and Route Display[​](#map-view-and-route-display "Direct link to Map View and Route Display") `MapViewController` is embedded full-screen with edge insets to leave room for route summary bubbles. The delegate is set so route selection events can promote a route to main. Two navigation bar buttons trigger route calculation and clearing: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchAlongRoute/SearchAlongRoute/ViewController.swift) ```swift func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.delegate = self self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController!.setEdgeAreaInsets(UIEdgeInsets(top: 30, left: 40, bottom: 30, right: 40)) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) } // MARK: - MapViewControllerDelegate func mapViewController(_ mapViewController: MapViewController, didSelectRoute route: RouteObject) { self.mainRoute = route mapViewController.setMainRoute(route) } ``` ##### Calculating the Route[​](#calculating-the-route "Direct link to Calculating the Route") `routeButtonAction` lazily creates a `NavigationContext` with car preferences on first tap, then calls `calculateRoute(withWaypoints:completionHandler:)` with San Francisco and San Jose as waypoints. On completion, routes are presented on the map: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchAlongRoute/SearchAlongRoute/ViewController.swift) ```swift @objc func routeButtonAction(item: UIBarButtonItem) { if self.navigationContext == nil { let preferences = RoutePreferencesObject.init() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) preferences.setAvoidUnpavedRoads(true) self.navigationContext = NavigationContext.init(preferences: preferences) self.navigationContext?.delegate = self } let waypoints = [ LandmarkObject.landmark( withName: "San Francisco", location: CoordinatesObject.coordinates(withLatitude: 37.77903, longitude: -122.41991)), LandmarkObject.landmark( withName: "San Jose", location: CoordinatesObject.coordinates(withLatitude: 37.33619, longitude: -121.89058)) ] item.isEnabled = false self.navigationContext? .calculateRoute( withWaypoints: waypoints, completionHandler: { [weak self] (results: [RouteObject]) in guard let strongSelf = self else { return } strongSelf.routeResults = results if !results.isEmpty { strongSelf.mainRoute = results.first strongSelf.addSearch() strongSelf.mapViewController?.presentRoutes(results, withTraffic: nil, showSummary: true, animationDuration: 1000) } item.isEnabled = true }) } ``` ##### Searching Along the Route[​](#searching-along-the-route "Direct link to Searching Along the Route") `searchButton` lazily initialises a `SearchContext` with a maximum of 40 matches, then calls `searchAlong(withRoute:query:)` using the current main route. Results are rendered on the map as grouped highlights via `presentHighlights(_:settings:highlightId:)`: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchAlongRoute/SearchAlongRoute/ViewController.swift) ```swift @objc func searchButton() { guard let mainRoute = self.mainRoute else { return } if self.searchContext == nil { self.searchContext = SearchContext.init() self.searchContext?.setMaxMatches(40) self.searchContext?.setSearchMapPOIs(true) self.searchContext?.setSearchAddresses(true) } self.searchContext! .searchAlong(withRoute: mainRoute, query: "Gas station") { [weak self] (results: [LandmarkObject]) in guard let strongSelf = self else { return } let settings = HighlightRenderSettings() settings.imageSize = 7 settings.options = Int32(HighlightOption.group.rawValue) strongSelf.mapViewController?.presentHighlights(results, settings: settings, highlightId: strongSelf.defaultHighlightId) } } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to calculate a car route, search for landmarks along it using `SearchContext`, and display the results as grouped `Highlights` on the map. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/SearchAlongRoute). info `SearchContext` settings and parameters showcased in the other examples, such as category and query, can also be applied in this scenario to further refine the search results. ![](/docs/ios/assets/images/example_ios_search_along_route1-9ceaf62e90b793b6aefdd0624cb092f1.png) **Route Calculated** ![](/docs/ios/assets/images/example_ios_search_along_route2-86b40bd6443517f1910acf128f7c1dbe.png) **Search Along Route Results** ##### Map View and Route Display[​](#map-view-and-route-display-1 "Direct link to Map View and Route Display") `MapBase` hosts a `MapRoute` for rendering calculated routes and a `MapLandmark` for search result highlights. The `.didSelectRoutes` modifier promotes a selected route to main with `proxy.setMain(route:)`. Three toolbar buttons trigger route calculation, search, and clearing: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/SearchAlongRoute/SearchAlongRoute/ContentView.swift) ```swift var body: some View { MapReader { proxy in MapBase { if !routeResults.isEmpty { MapRoute( routes: routeResults, bubbleSummary: true, animationDuration: 800 ) } MapLandmark( landmarks: searchResultLandmarks, renderSettings: getHighlightSettings(), highlightId: defaultHighlightId, animationDuration: -1 ) } .didSelectRoutes { routes, point in guard let route = routes.first else { return } mainRoute = route proxy.setMain(route: route) } .mapEdgeInsets(UIEdgeInsets(top: 30, left: 40, bottom: 30, right: 40)) .ignoresSafeArea() .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("", systemImage: "point.topleft.down.curvedto.point.bottomright.up") { calculateRoute(proxy) } .disabled(isCalculating) .buttonStyle(.borderedProminent) } ToolbarItem(placement: .topBarTrailing) { Button("", systemImage: "clear") { clearAll(proxy) } .buttonStyle(.borderedProminent) } ToolbarItem(placement: .topBarLeading) { Button("", systemImage: "magnifyingglass") { searchAlongRoute(proxy) } .disabled(mainRoute == nil) .buttonStyle(.borderedProminent) } } } } ``` ##### Calculating the Route[​](#calculating-the-route-1 "Direct link to Calculating the Route") `calculateRoute(_:)` builds a `NavigationContext` with car preferences on first call, then calls `calculateRoute(withWaypoints:)` with San Francisco and San Jose as waypoints. The first result is set as the `mainRoute`: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/SearchAlongRoute/SearchAlongRoute/ContentView.swift) ```swift private func calculateRoute(_ proxy: MapProxy) { if navigationContext == nil { let preferences = RoutePreferencesObject() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) preferences.setAvoidUnpavedRoads(true) navigationContext = NavigationContext(preferences: preferences) } let waypoints = [ LandmarkObject.landmark( withName: "San Francisco", location: CoordinatesObject.coordinates(withLatitude: 37.77903, longitude: -122.41991)), LandmarkObject.landmark( withName: "San Jose", location: CoordinatesObject.coordinates(withLatitude: 37.33619, longitude: -121.89058)) ] isCalculating = true navigationContext?.calculateRoute(withWaypoints: waypoints) { results in routeResults = results if !results.isEmpty { mainRoute = results.first routeResults = results } isCalculating = false } } ``` ##### Searching Along the Route[​](#searching-along-the-route-1 "Direct link to Searching Along the Route") `searchAlongRoute(_:)` lazily initialises a `SearchContext` with a maximum of 40 matches, then calls `searchAlong(withRoute:query:)` using the selected main route. Results are stored in `searchResultLandmarks` and rendered automatically by the `MapLandmark` view: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/SearchAlongRoute/SearchAlongRoute/ContentView.swift) ```swift private func searchAlongRoute(_ proxy: MapProxy) { guard let route = mainRoute else { return } if searchContext == nil { searchContext = SearchContext() searchContext?.setMaxMatches(40) searchContext?.setSearchMapPOIs(true) searchContext?.setSearchAddresses(true) } searchContext?.searchAlong(withRoute: route, query: "Gas station") { results in searchResultLandmarks = results } } ``` --- ### Search on Map Last updated: March 13, 2026 | 3 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to perform searches on the map, including searching for nearby landmarks and landmarks matching a specific query. Searching on the map chooses the center point as the location reference. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchOnMap). ![](/docs/ios/assets/images/example_ios_search_onmap1-473b4dd2a154ec231ed9c83616b4d342.png) **Search with Query** ![](/docs/ios/assets/images/example_ios_search_onmap2-bc0d716e79ba42569c4570f7ad4e6948.png) **Search Around** ##### Map Display and Search Buttons[​](#map-display-and-search-buttons "Direct link to Map Display and Search Buttons") The following code outlines the map view with search buttons in the navigation bar: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchOnMap/SearchOnMap/ViewController.swift) ```swift class ViewController: UIViewController { var mapViewController: MapViewController? let defaultHighlightId: Int32 = 10 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.createMapView() self.mapViewController!.startRender() self.addSearch() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let location = CoordinatesObject.coordinates(withLatitude: 52.368447, longitude: 4.888229) self.mapViewController!.center(onCoordinates: location, zoomLevel: 70, animationDuration: 0) } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) } // MARK: - Search func addSearch() { let image1 = UIImage.init(systemName: "clear") let image2 = UIImage.init(systemName: "mappin.and.ellipse") let image3 = UIImage.init(systemName: "magnifyingglass") let barButton1 = UIBarButtonItem.init(image: image1, style: .done, target: self, action: #selector(cleanMap)) let barButton2 = UIBarButtonItem.init(image: image2, style: .done, target: self, action: #selector(searchNearbyButton)) let barButton3 = UIBarButtonItem.init(image: image3, style: .done, target: self, action: #selector(searchQueryButton)) self.navigationItem.leftBarButtonItems = [barButton1] self.navigationItem.rightBarButtonItems = [barButton2, barButton3] } ``` ##### Performing Search on Map[​](#performing-search-on-map "Direct link to Performing Search on Map") The code for performing two types of searches on the map, one for nearby landmarks and another for landmarks matching a specific query, as well as helper methods: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchOnMap/SearchOnMap/ViewController.swift) ```swift @objc func searchNearbyButton() { self.mapViewController! .searchAround { [weak self] (results: [LandmarkObject]) in guard let strongSelf = self else { return } strongSelf.mapViewController!.removeHighlights() let settings = HighlightRenderSettings.init() settings.options = Int32(HighlightOption.group.rawValue) settings.showPin = true settings.imageSize = 7 strongSelf.mapViewController!.presentHighlights(results, settings: settings, highlightId: strongSelf.defaultHighlightId) strongSelf.centerOnHighlightArea() } } @objc func searchQueryButton() { self.mapViewController! .search(withQuery: "restaurant") { [weak self] (results: [LandmarkObject]) in guard let strongSelf = self else { return } guard let landmark = results.first else { return } strongSelf.mapViewController!.removeHighlights() let settings = HighlightRenderSettings.init() settings.options = Int32(HighlightOption.group.rawValue) settings.showPin = true settings.imageSize = 7 strongSelf.mapViewController!.presentHighlights([landmark], settings: settings, highlightId: strongSelf.defaultHighlightId) strongSelf.centerOnHighlightArea() } } @objc func cleanMap(item: UIBarButtonItem) { self.mapViewController!.removeHighlights() } func centerOnHighlightArea() { let list = self.mapViewController!.getHighlight(self.defaultHighlightId) guard !list.isEmpty else { return } guard let area = self.mapViewController!.getHighlightArea(self.defaultHighlightId) else { return } self.mapViewController!.center(onArea: area, zoomLevel: 75, animationDuration: 1200) } } ``` --- ### Search Text Last updated: March 13, 2026 | 6 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to search for `Landmarks` using a text query. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchText). ![](/docs/ios/assets/images/example_ios_searchtext1-69190bf91acffcccc22b86e1f3395e2a.png) **Initial screen** ![](/docs/ios/assets/images/example_ios_searchtext2-7656c07ba2a4ff3a1f96f89ad0570c8e.png) **Text Query Search** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The following code outlines the map view with a simple search field: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchText/SearchText/ViewController.swift) ```swift class ViewController: UIViewController, UISearchBarDelegate, ResultsViewControllerDelegate { var mapViewController: MapViewController? var searchContext: SearchContext? let resultsViewController = ResultsViewController.init() var searchController: UISearchController? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.title = "Search Text" self.navigationItem.hidesSearchBarWhenScrolling = false self.navigationItem.largeTitleDisplayMode = .never self.resultsViewController.delegate = self self.searchController = UISearchController.init(searchResultsController: self.resultsViewController) self.searchController!.view.backgroundColor = UIColor.systemBackground self.searchController!.searchBar.delegate = self self.searchController!.searchBar.placeholder = "Search" self.searchController!.obscuresBackgroundDuringPresentation = false self.searchController!.hidesNavigationBarDuringPresentation = false self.navigationItem.searchController = self.searchController self.createMapView() self.mapViewController!.startRender() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let location = CoordinatesObject.coordinates(withLatitude: 52.368447, longitude: 4.888229) self.mapViewController!.center(onCoordinates: location, zoomLevel: 50, animationDuration: 0) } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) } // MARK: - UISearchBarDelegate func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { self.performSearch(text: searchText) } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { guard let mapViewController = self.mapViewController else { return } if let searchContext = self.searchContext { searchContext.cancelSearch() } mapViewController.removeHighlights() self.resultsViewController.dataModel = [] self.resultsViewController.tableView.reloadData() } ``` ##### Perform Search with query and Helper methods[​](#perform-search-with-query-and-helper-methods "Direct link to Perform Search with query and Helper methods") The code for performing the search alongside the implementation of helper methods for the example, one of them showing how to get the Location for the Map center using screen coordinates: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchText/SearchText/ViewController.swift) ```swift func performSearch(text: String) { if self.searchContext == nil { self.searchContext = SearchContext.init() // Preferences self.searchContext?.setMaxMatches(40) self.searchContext?.setSearchMapPOIs(true) self.searchContext?.setSearchAddresses(true) } // Location Hint support: narrow the search area to a specific radius self.searchContext?.setLocationHint( RectangleGeographicAreaObject( location: self.getMapCenterLocation(), horizontalRadius: 2000, verticalRadius: 2000)) let location = self.getMapCenterLocation() self.searchContext? .search(withQuery: text, location: location) { [weak self] (results: [LandmarkObject]) in guard let strongSelf = self else { return } strongSelf.resultsViewController.dataModel = results strongSelf.resultsViewController.referencePoint = location strongSelf.resultsViewController.tableView.reloadData() } } // MARK: - ResultsViewControllerDelegate func didSelectLandmark(landmark: LandmarkObject) { guard let searchController = self.searchController else { return } guard let mapViewController = self.mapViewController else { return } searchController.dismiss(animated: true) {} let text = landmark.getLandmarkName() searchController.searchBar.text = text mapViewController.removeHighlights() let settings = HighlightRenderSettings.init() settings.showPin = true settings.imageSize = 7 settings.options = Int32( HighlightOption.showLandmark.rawValue | HighlightOption.overlap.rawValue | HighlightOption.showContour.rawValue) settings.contourInnerColor = UIColor.orange settings.contourOuterColor = UIColor.orange mapViewController.presentHighlights([landmark], settings: settings) self.centerLandmark(landmark: landmark) } func centerLandmark(landmark: LandmarkObject) { guard let mapViewController = self.mapViewController else { return } if let contour = landmark.getContourGeograficArea(), !contour.isEmpty() { // default mapViewController.center( onArea: contour, zoomLevel: -1, animationDuration: 1200) } else { // 2d mapViewController.center( onCoordinates: landmark.getCoordinates(), zoomLevel: -1, mapAngle: Double.greatestFiniteMagnitude, viewAngle: 0, animationDuration: 1200) } } // MARK: - Utils func getMapCenterLocation() -> CoordinatesObject { guard let mapViewController = self.mapViewController else { return CoordinatesObject.init() } let scale = UIScreen.main.scale let center = mapViewController.view.center let point = CGPoint.init(x: center.x * scale, y: center.y * scale) let location = mapViewController.transformScreen(toWgs: point) return location } } ``` ##### Results List[​](#results-list "Direct link to Results List") ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/SearchText/SearchText/ViewController.swift) ```swift protocol ResultsViewControllerDelegate: NSObject { func didSelectLandmark(landmark: LandmarkObject) } class ResultsViewController: UITableViewController { weak var delegate: ResultsViewControllerDelegate? var dataModel: [LandmarkObject] = [] var referencePoint: CoordinatesObject? override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.dataModel.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell.init(style: .subtitle, reuseIdentifier: "testCell") let scale = UIScreen.main.scale let size = CGSize.init(width: 40 * scale, height: 40 * scale) let landmark = self.dataModel[indexPath.row] let text = landmark.getLandmarkName() let desc = landmark.getLandmarkDescription() let img = landmark.getLandmarkImage(size) cell.textLabel?.text = text cell.detailTextLabel?.text = desc cell.imageView?.image = img self.setupAccessoryView(tableView: tableView, cell: cell, indexPath: indexPath) return cell } func setupAccessoryView(tableView: UITableView, cell: UITableViewCell, indexPath: IndexPath) { let size = CGSize.init(width: 70, height: 40) let accessoryView = UIView.init(frame: CGRect.init(origin: CGPoint.zero, size: size)) if let point = self.referencePoint { let statusText = self.dataModel[indexPath.row].getLandmarkDistanceFormatted(withLocation: point) let statusDesc = self.dataModel[indexPath.row].getLandmarkDistanceUnitFormatted(withLocation: point) let frameL1 = CGRect.init(origin: CGPoint.init(x: 0, y: 0), size: CGSize.init(width: 70, height: 25)) let labelText = UILabel.init(frame: frameL1) labelText.text = statusText + statusDesc labelText.textAlignment = .right accessoryView.addSubview(labelText) } cell.accessoryView = accessoryView } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if self.delegate != nil { self.delegate!.didSelectLandmark(landmark: self.dataModel[indexPath.row]) } } } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to search for `Landmarks` using a text query. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/SearchText). ![](/docs/ios/assets/images/example_ios_searchtext1-69190bf91acffcccc22b86e1f3395e2a.png) **Initial screen** ![](/docs/ios/assets/images/example_ios_searchtext2-7656c07ba2a4ff3a1f96f89ad0570c8e.png) **Text Query Search** ##### UI and Map Integration[​](#ui-and-map-integration-1 "Direct link to UI and Map Integration") The following code outlines the search result item structure and the map view with a simple search field: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/SearchText/SearchText/ContentView.swift) ```swift struct Place: Identifiable { let id = UUID() let image: UIImage let title: String let details: String let distance: String let lmk: LandmarkObject } struct ContentView: View { let context = SearchContext.init() private let defaultHighlightId = 10 @State private var searchQuery = "" @State private var results: [Place] = [] @State private var selectedItem: Place? @State private var isSearching: Bool = false @FocusState private var searchFocused: Bool @Environment(\.displayScale) private var displayScale var body: some View { MapReader { proxy in ZStack { MapBase(initialPosition: .amsterdam, initialZoomLevel: 64) .mapCompass(false) .ignoresSafeArea() if searchFocused { List(results) { place in HStack { Image(uiImage: place.image) .frame(width: 40, height: 40) VStack(alignment: .leading) { Text(place.title) .font(.headline) Text(place.details) .font(.subheadline) .foregroundColor(.secondary) } Spacer() Text(place.distance) } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) .onTapGesture { proxy.centerOn(coordinates: place.lmk.getCoordinates(), duration: 1200) proxy.present(highlights: [place.lmk], settings: getRenderSettings()) withAnimation(.easeInOut(duration: 0.2)) { selectedItem = place searchFocused = false } } } } } .navigationTitle("Search Text") .navigationBarTitleDisplayMode(.inline) .searchable(text: $searchQuery, placement: .automatic) .searchFocused($searchFocused) .onChange(of: searchQuery) { oldValue, newValue in performSearch(proxy) } } } ``` ##### Search with Query[​](#search-with-query "Direct link to Search with Query") The code for performing a search with a query and a location hint: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/SearchText/SearchText/ContentView.swift) ```swift private func performSearch(_ proxy: MapProxy) { proxy.removeAllHighlights() proxy.centerOn(coordinates: .amsterdam, zoomLevel: 60) results.removeAll() isSearching = true // Location Hint support: narrow the search area to a specific radius context.setLocationHint( RectangleGeographicAreaObject( location: .amsterdam, horizontalRadius: 2000, verticalRadius: 2000)) context.search(withQuery: searchQuery, location: .amsterdam) { response in isSearching = false results = response.map { item in return Place( image: item.getLandmarkImage(CGSize(width: 40 * displayScale, height: 40 * displayScale)) ?? UIImage(), title: item.getLandmarkName(), details: item.getLandmarkDescription(), distance: item.getLandmarkDistanceFormatted(withLocation: .amsterdam) + item.getLandmarkDistanceUnitFormatted(withLocation: .amsterdam), lmk: item) } } } ``` --- ### What's Nearby Last updated: April 24, 2026 | 6 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to perform searches for nearby landmarks around a specific location. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/WhatsNearby). info `SearchContext` settings and parameters showcased in the other examples, such as category and query, can also be applied in this scenario to further refine the search results. ![](/docs/ios/assets/images/example_ios_whats_nearby1-b380a6add420c3d9be00f6fadd898245.png) **Landmark Selected** ![](/docs/ios/assets/images/example_ios_whats_nearby2-71fcaf6ee1645ebd489dd3a225527772.png) **Search Around the Landmark** ##### Map Integration and Search Around Button[​](#map-integration-and-search-around-button "Direct link to Map Integration and Search Around Button") The following code outlines the map view integration with the basic selection methods and the search around button in the navigation bar: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/WhatsNearby/WhatsNearby/ViewController.swift) ```swift class ViewController: UIViewController, MapViewControllerDelegate { var mapViewController: MapViewController? var landmark: LandmarkObject? let searchContext = SearchContext.init() let label = UILabel.init() let defaultHighlightId: Int32 = 10 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.createMapView() self.mapViewController!.startRender() self.mapViewController!.delegate = self self.addLabelText() self.addSearch() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let location = CoordinatesObject.coordinates(withLatitude: 52.368447, longitude: 4.888229) self.mapViewController!.center(onCoordinates: location, zoomLevel: 70, animationDuration: 0) } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) } // MARK: - Label func addLabelText() { self.label.adjustsFontSizeToFitWidth = true self.label.font = UIFont.boldSystemFont(ofSize: 20) self.label.numberOfLines = 0 self.label.backgroundColor = UIColor.systemBackground self.label.isHidden = false self.label.textAlignment = .center self.label.layer.borderColor = UIColor.systemBlue.cgColor self.label.layer.borderWidth = 1.4 self.label.layer.cornerRadius = 8.0 self.label.layer.masksToBounds = true self.label.text = "Select a point on the map and tap the button in the top right to search around it" self.view.addSubview(self.label) self.label.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.label.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 10), self.label.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -10), self.label.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -10), self.label.heightAnchor.constraint(equalToConstant: 70) ]) } // MARK: - MapViewControllerDelegate func mapViewController(_ mapViewController: MapViewController, didSelectLandmarks landmarks: [LandmarkObject], onTouch point: CGPoint) { guard let landmark = landmarks.first else { return } self.processSelection(landmark: landmark) } func mapViewController( _ mapViewController: MapViewController, didSelectLandmarks landmarks: [LandmarkObject], onLongTouch point: CGPoint ) { guard let landmark = landmarks.first else { return } self.processSelection(landmark: landmark) } func mapViewController(_ mapViewController: MapViewController, didSelectStreets streets: [LandmarkObject], onTouch point: CGPoint) { guard let landmark = streets.first else { return } self.processSelection(landmark: landmark) } func mapViewController(_ mapViewController: MapViewController, didSelectStreets streets: [LandmarkObject], onLongTouch point: CGPoint) { guard let landmark = streets.first else { return } self.processSelection(landmark: landmark) } // MARK: - Search func addSearch() { let image1 = UIImage.init(systemName: "clear") let image2 = UIImage.init(systemName: "mappin.and.ellipse") let barButton1 = UIBarButtonItem.init(image: image1, style: .done, target: self, action: #selector(cleanMap)) let barButton2 = UIBarButtonItem.init(image: image2, style: .done, target: self, action: #selector(searchNearbyButton)) self.navigationItem.leftBarButtonItems = [barButton1] self.navigationItem.rightBarButtonItems = [barButton2] } ``` ##### Performing Search and Helper Methods[​](#performing-search-and-helper-methods "Direct link to Performing Search and Helper Methods") The code for performing the search for nearby landmarks around the selected landmark, as well as helper methods to process landmark selection and clean the map: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/Places/WhatsNearby/WhatsNearby/ViewController.swift) ```swift @objc func searchNearbyButton() { guard let landmark = self.landmark else { return } self.searchContext.searchAround(withLocation: landmark.getCoordinates()) { [weak self] (results: [LandmarkObject]) in guard let strongSelf = self else { return } strongSelf.mapViewController!.removeHighlights() let settings = HighlightRenderSettings.init() settings.options = Int32(HighlightOption.group.rawValue) settings.showPin = true settings.imageSize = 7 strongSelf.mapViewController!.presentHighlights(results, settings: settings, highlightId: strongSelf.defaultHighlightId) strongSelf.centerOnHighlightArea() } } // MARK: - Utils func showLandmark(landmark: LandmarkObject) { let text = " " + landmark.getLandmarkName() + "\n" + " " + landmark.getLandmarkDescription() self.label.text = text self.label.isHidden = false let settings = HighlightRenderSettings.init() settings.showPin = true self.mapViewController!.presentHighlights([landmark], settings: settings, highlightId: self.defaultHighlightId) } func processSelection(landmark: LandmarkObject) { self.cleanMap() self.landmark = landmark self.showLandmark(landmark: landmark) } @objc func cleanMap() { self.mapViewController!.removeHighlights() self.landmark = nil self.label.text = "Select a point on the map and tap the button in the top right to search around it" } func centerOnHighlightArea() { let list = self.mapViewController!.getHighlight(self.defaultHighlightId) guard !list.isEmpty else { return } guard let area = self.mapViewController!.getHighlightArea(self.defaultHighlightId) else { return } self.mapViewController!.center(onArea: area, zoomLevel: -1, animationDuration: 1200) } } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to perform searches for nearby landmarks around a specific location. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/WhatsNearby). info `SearchContext` settings and parameters showcased in the other examples, such as category and query, can also be applied in this scenario to further refine the search results. ![](/docs/ios/assets/images/example_ios_whats_nearby1-b380a6add420c3d9be00f6fadd898245.png) **Landmark Selected** ![](/docs/ios/assets/images/example_ios_whats_nearby2-71fcaf6ee1645ebd489dd3a225527772.png) **Search Around the Landmark** ##### Map View and Landmark Selection[​](#map-view-and-landmark-selection "Direct link to Map View and Landmark Selection") `MapBase` uses `.didSelectLandmarks` and `.didSelectStreets` to capture taps and long presses on both landmarks and streets, passing the first result to `processSelection(landmark:proxy:)`. A status label overlaid at the bottom updates to show the selected landmark's name and description. Two toolbar buttons trigger the nearby search and clear the map: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/WhatsNearby/WhatsNearby/ContentView.swift) ```swift var body: some View { MapReader { proxy in ZStack(alignment: .bottom) { MapBase(initialPosition: .amsterdam, initialZoomLevel: 70) { MapLandmark( landmarks: nearbyLandmarks, renderSettings: getGroupSettings(), highlightId: defaultHighlightId, animationDuration: 800 ) } .didSelectLandmarks { landmarks, touchPoint, isLongTouch in guard let landmark = landmarks.first else { return } processSelection(landmark: landmark, proxy: proxy) } .didSelectStreets { streets, touchPoint, isLongTouch in guard let street = streets.first else { return } processSelection(landmark: street, proxy: proxy) } .ignoresSafeArea() Text(labelText) .font(.system(size: 16, weight: .bold)) .multilineTextAlignment(.center) .padding(10) .frame(maxWidth: .infinity) .frame(height: 70) .background(Color(UIColor.systemBackground)) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(Color.blue, lineWidth: 1.4) ) .padding(.horizontal, 10) .padding(.bottom, 10) } .toolbar { ToolbarItem(placement: .topBarLeading) { Button { cleanMap(proxy: proxy) } label: { Image(systemName: "clear") } .buttonStyle(.borderedProminent) } ToolbarItem(placement: .topBarTrailing) { Button { searchNearby() } label: { Image(systemName: "mappin.and.ellipse") } .buttonStyle(.borderedProminent) } } } } ``` ##### Performing the Nearby Search and Helper Methods[​](#performing-the-nearby-search-and-helper-methods "Direct link to Performing the Nearby Search and Helper Methods") `searchNearby()` calls `searchContext.searchAround(withLocation:)` using the selected landmark's coordinates. `processSelection(landmark:proxy:)` highlights the tapped point with a pin and updates the label. `cleanMap(proxy:)` removes all highlights and resets state: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/Places/WhatsNearby/WhatsNearby/ContentView.swift) ```swift private func searchNearby() { guard let landmark = selectedLandmark else { return } searchContext.searchAround(withLocation: landmark.getCoordinates()) { results in nearbyLandmarks = results } } private func processSelection(landmark: LandmarkObject, proxy: MapProxy) { cleanMap(proxy: proxy) selectedLandmark = landmark let name = landmark.getLandmarkName() let description = landmark.getLandmarkDescription() labelText = " \(name)\n \(description)" let settings = HighlightRenderSettings.init() settings.showPin = true proxy.present(highlights: [landmark], settings: settings, highlightId: Int(defaultHighlightId)) } private func cleanMap(proxy: MapProxy) { proxy.removeAllHighlights() nearbyLandmarks = [] selectedLandmark = nil labelText = "Select a point on the map and tap the button in the top right to search around it" } ``` --- ### Routing & Navigation [![Calculate Route image](/docs/ios/assets/images/1_calculate_route-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/calculate-route.md) ##### [Calculate Route](/docs/ios/examples/routing-navigation/calculate-route.md) [Calculate a car route between two coordinates and display it on the map.](/docs/ios/examples/routing-navigation/calculate-route.md) [![Navigate Route image](/docs/ios/assets/images/2_navigate_route-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/navigate-route.md) ##### [Navigate Route](/docs/ios/examples/routing-navigation/navigate-route.md) [Navigate a calculated route using real GPS position with turn-by-turn voice guidance.](/docs/ios/examples/routing-navigation/navigate-route.md) [![Simulate Route image](/docs/ios/assets/images/3_simulate_route-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/simulate-route.md) ##### [Simulate Route](/docs/ios/examples/routing-navigation/simulate-route.md) [Calculate a route and simulate driving it at accelerated speed with voice guidance.](/docs/ios/examples/routing-navigation/simulate-route.md) [![Better Route Notification image](/docs/ios/assets/images/4_better_route_notification-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/better-route-notification.md) ##### [Better Route Notification](/docs/ios/examples/routing-navigation/better-route-notification.md) [Detect faster alternate routes during navigation and offer them without interrupting guidance.](/docs/ios/examples/routing-navigation/better-route-notification.md) [![Route Instructions image](/docs/ios/assets/images/5_route_instructions-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/route-instructions.md) ##### [Route Instructions](/docs/ios/examples/routing-navigation/route-instructions.md) [View a turn-by-turn instruction list with traffic events for a calculated route.](/docs/ios/examples/routing-navigation/route-instructions.md) [![Finger Draw Route image](/docs/ios/assets/images/6_finger_drawn_route_navigation-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/finger-drawn-route-navigation.md) ##### [Finger Draw Route](/docs/ios/examples/routing-navigation/finger-drawn-route-navigation.md) [Manually sketch a path on the map and calculate a route from that path.](/docs/ios/examples/routing-navigation/finger-drawn-route-navigation.md) [![GPX Route image](/docs/ios/assets/images/7_gpx_route-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/gpx-route.md) ##### [GPX Route](/docs/ios/examples/routing-navigation/gpx-route.md) [Load a GPX file, calculate a route following the track, and simulate that route.](/docs/ios/examples/routing-navigation/gpx-route.md) [![GPX Thumbnail Image image](/docs/ios/assets/images/8_gpx_thumbnail_image-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/gpx-thumbnail-image.md) ##### [GPX Thumbnail Image](/docs/ios/examples/routing-navigation/gpx-thumbnail-image.md) [Generate a static map thumbnail image from a GPX track without a visible map.](/docs/ios/examples/routing-navigation/gpx-thumbnail-image.md) [![Human Voices image](/docs/ios/assets/images/9_human_voices-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/human-voices.md) ##### [Human Voices](/docs/ios/examples/routing-navigation/human-voices.md) [Download and use human-recorded voice packages for navigation guidance.](/docs/ios/examples/routing-navigation/human-voices.md) [![No Map Just Routing image](/docs/ios/assets/images/10_no_map_just_routing-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/no-map-just-routing.md) ##### [No Map Just Routing](/docs/ios/examples/routing-navigation/no-map-just-routing.md) [Calculate and navigate a route entirely without a map view using only a navigation panel.](/docs/ios/examples/routing-navigation/no-map-just-routing.md) [![Public Transit Calculate Route image](/docs/ios/assets/images/11_public_transit_calculate_route-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/public-transit-calculate-route.md) ##### [Public Transit Calculate Route](/docs/ios/examples/routing-navigation/public-transit-calculate-route.md) [Calculate a public transit route and render it on the map with a route summary.](/docs/ios/examples/routing-navigation/public-transit-calculate-route.md) [![Public Transit Route Instructions image](/docs/ios/assets/images/12_public_transit_route_instructions-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/public-transit-route-instructions.md) ##### [Public Transit Route Instructions](/docs/ios/examples/routing-navigation/public-transit-route-instructions.md) [Display detailed transit segment steps including departure times and line colours.](/docs/ios/examples/routing-navigation/public-transit-route-instructions.md) [![Road Block image](/docs/ios/assets/images/13_road_block-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/road-block.md) ##### [Road Block](/docs/ios/examples/routing-navigation/road-block.md) [Insert a road block during navigation to trigger immediate rerouting.](/docs/ios/examples/routing-navigation/road-block.md) [![Route Profile image](/docs/ios/assets/images/14_route_profile-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/route-profile.md) ##### [Route Profile](/docs/ios/examples/routing-navigation/route-profile.md) [Show interactive elevation, surface, road type and steepness charts for a route.](/docs/ios/examples/routing-navigation/route-profile.md) [![Calculate Route Multi Map image](/docs/ios/assets/images/15_calculate_route_multi_map-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/calculate-route-multi-map.md) ##### [Calculate Route Multi Map](/docs/ios/examples/routing-navigation/calculate-route-multi-map.md) [Display two independent map views side-by-side each calculating its own route.](/docs/ios/examples/routing-navigation/calculate-route-multi-map.md) [![Custom Turns ID image](/docs/ios/assets/images/16_custom_turns_id-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/custom-turns-id.md) ##### [Custom Turns ID](/docs/ios/examples/routing-navigation/custom-turns-id.md) [Replace default turn icons with custom images keyed by the numeric TurnId64 value.](/docs/ios/examples/routing-navigation/custom-turns-id.md) [![Follow Position Preferences image](/docs/ios/assets/images/17_follow_position_preferences-078c17eecabb4befbaf4e5588bfad85c.png)](/docs/ios/examples/routing-navigation/follow-position-preferences.md) ##### [Follow Position Preferences](/docs/ios/examples/routing-navigation/follow-position-preferences.md) [Persist the user's zoom and tilt adjustments during navigation and reset them on demand.](/docs/ios/examples/routing-navigation/follow-position-preferences.md) --- ### Better Route Notification Last updated: April 7, 2026 | 4 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to detect when a faster alternate route becomes available during navigation and then offer it to the user. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/BetterRouteNotification). ![](/docs/ios/assets/images/example_ios_better_route1-3ccc19dacd5d97ccf20ed224e9ad2146.png) **Initial Slow Route Selection** ![](/docs/ios/assets/images/example_ios_better_route2-d54e72bac2060ae82080a2dca8c718d8.png) **Better Route Detected** ![](/docs/ios/assets/images/example_ios_better_route3-c47eab5fb32981f1f40317f9d86237ce.png) **Better Route Displayed** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The view controller tracks both the active route and any detected better route, displaying a dedicated button when an improvement is found: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/BetterRouteNotification/BetterRouteNotification/ViewController.swift) ```swift class ViewController: UIViewController, MapViewControllerDelegate, NavigationContextDelegate { var mapViewController: MapViewController? var navigationContext: NavigationContext? var soundContext: SoundContext? var trafficContext: TrafficContext? var mainRoute: RouteObject? var betterRoute: RouteObject? var timeGainMinutes: UInt = 0 var panelNavigationViewController: NavigationViewController? var myPositionButton: UIButton? var betterRouteButton: UIButton? ``` ##### Routing Preferences for Better Route Detection[​](#routing-preferences-for-better-route-detection "Direct link to Routing Preferences for Better Route Detection") `setAvoidTraffic(true)` is set on the route preferences so that the engine also actively monitors for traffic-aware alternatives: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/BetterRouteNotification/BetterRouteNotification/ViewController.swift) ```swift let preferences = RoutePreferencesObject.init() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) preferences.setAvoidTraffic(true) self.navigationContext = NavigationContext.init(preferences: preferences) self.navigationContext?.delegate = self self.trafficContext = TrafficContext.init() self.trafficContext?.setUseTraffic(.useOnline) self.soundContext = SoundContext.init() self.soundContext?.setUseTtsWithCompletionHandler({ success in }) self.departure = LandmarkObject.landmark( withName: "Munich 1", location: CoordinatesObject.coordinates(withLatitude: 48.15741, longitude: 11.53739)) self.destination = LandmarkObject.landmark( withName: "Munich 2", location: CoordinatesObject.coordinates(withLatitude: 47.56730, longitude: 11.03687)) ``` info In order for a better route to be detected, the user must be on a route that is not the fastest option. Make sure you have selected the slower alternative in the initial route selection. ##### Presenting and Acting on the Better Route[​](#presenting-and-acting-on-the-better-route "Direct link to Presenting and Acting on the Better Route") When a better route is detected, a green button appears. Tapping it shows both routes side-by-side using `showBetterRoute(_:withTraffic:timeGain:showSummary:)`: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/BetterRouteNotification/BetterRouteNotification/ViewController.swift) ```swift @objc func betterRouteAction() { guard self.mainRoute != nil else { return } guard self.betterRoute != nil else { return } self.mapViewController!.stopFollowingPosition() self.mapViewController!.removeAllRoutes() self.mapViewController!.showCompass() self.mapViewController!.showRoutes([self.mainRoute!], withTraffic: self.trafficContext!, showSummary: true) self.mapViewController! .showBetterRoute(self.betterRoute!, withTraffic: self.trafficContext!, timeGain: self.timeGainMinutes, showSummary: true) self.mapViewController! .center(onRoutes: [self.mainRoute!, self.betterRoute!], displayMode: .branches, animationDuration: 1600) self.removeBetterRouteButton() } ``` ##### Resuming on Selected Route[​](#resuming-on-selected-route "Direct link to Resuming on Selected Route") When the user taps the position button, this method checks whether the better route was selected and continues simulation on it accordingly: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/BetterRouteNotification/BetterRouteNotification/ViewController.swift) ```swift func continueNavigationOnBetterRoute() -> Bool { guard self.mainRoute != nil, self.betterRoute != nil else { return false } guard let mainNavRoute = self.mapViewController!.getMainRoute() else { return false } if mainNavRoute.isEqual(withRoute: self.betterRoute!) { self.mainRoute = mainNavRoute self.soundContext!.playText("You are on the fastest route.") self.betterRoute = nil self.startSimulation() return true } else { self.betterRoute = nil self.mapViewController!.removeAllRoutes() self.mapViewController!.showRoutes([self.mainRoute!], withTraffic: self.trafficContext!, showSummary: false) self.mapViewController!.hideCompass() return false } } ``` ##### Detecting a Better Route[​](#detecting-a-better-route "Direct link to Detecting a Better Route") The `onBetterRouteDetected` delegate fires when a faster alternative is found. Time gained is rounded to minutes, announced via TTS, and the better route is stored so the user can choose to switch. If the alternate is later invalidated, the original route display is restored: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/BetterRouteNotification/BetterRouteNotification/ViewController.swift) ```swift func navigationContext( _ navigationContext: NavigationContext, onBetterRouteDetected route: RouteObject, travelTime: Int, delay: Int, timeGain: Int ) { var minutes: Int = 0 if timeGain < 0 { // roadblock minutes = 60 } else { if timeGain < 30 { // under 30 sec. return } minutes = timeGain / 60 if timeGain % 60 >= 30 { minutes += 1 } } if minutes > 1 { let ttsMessage = "An alternative route is available which can save you " + String(minutes) + " minutes." self.soundContext!.playText(ttsMessage) self.betterRoute = route self.timeGainMinutes = UInt(minutes) self.presentBetterRouteButton() } } func navigationContext(_ navigationContext: NavigationContext, onBetterRouteInvalidated state: Bool) { self.removeBetterRouteButton() if self.betterRoute != nil { if self.mainRoute != nil { if self.mapViewController!.isFollowingPosition() == false { self.mapViewController!.hideCompass() self.mapViewController!.removeAllRoutes() self.mapViewController!.setMainRoute(self.mainRoute!) self.mapViewController!.showRoutes([self.mainRoute!], withTraffic: self.trafficContext!, showSummary: false) self.mapViewController!.startFollowingPosition(withAnimationDuration: 1600, zoomLevel: -1) { success in } } self.panelNavigationViewController!.view.isHidden = false } } self.betterRoute = nil } ``` --- ### Calculate Route Last updated: April 24, 2026 | 4 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to calculate a car route between two coordinates and display it on the map with a summary panel. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/CalculateRoute). ![](/docs/ios/assets/images/example_ios_calculate_route-90cb529a0245c53736ddd852bc4535ab.png) **Calculated Route with Insets** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The following code outlines the main view controller and the embedded map view setup: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/CalculateRoute/CalculateRoute/ViewController.swift) ```swift class ViewController: UIViewController, UISearchBarDelegate { var mapViewController: MapViewController? var navigationContext: NavigationContext? var trafficContext: TrafficContext? override func viewDidLoad() { super.viewDidLoad() self.title = "Calculate Route" self.navigationItem.largeTitleDisplayMode = .never self.createMapView() self.mapViewController!.startRender() self.addRouteButton() } // MARK: - Map View func createMapView() { self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.addChild(self.mapViewController!) self.view.addSubview(self.mapViewController!.view) self.mapViewController!.didMove(toParent: self) self.mapViewController?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor), self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) } ``` ##### Setting Route Preferences and Calculating[​](#setting-route-preferences-and-calculating "Direct link to Setting Route Preferences and Calculating") Route preferences are configured on a `RoutePreferencesObject`, then passed to a `NavigationContext` which performs the calculation. A `TrafficContext` is used to overlay live traffic data on the result: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/CalculateRoute/CalculateRoute/ViewController.swift) ```swift @objc func routeButtonAction(item: UIBarButtonItem) { if self.navigationContext == nil { let preferences = RoutePreferencesObject.init() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) preferences.setAvoidMotorways(false) preferences.setAvoidTollRoads(false) preferences.setAvoidFerries(false) preferences.setAvoidUnpavedRoads(true) self.navigationContext = NavigationContext.init(preferences: preferences) } if self.trafficContext == nil { self.trafficContext = TrafficContext.init() self.trafficContext?.setUseTraffic(.useOnline) } let waypoints = [ LandmarkObject.landmark( withName: "San Francisco", location: CoordinatesObject.coordinates(withLatitude: 37.77903, longitude: -122.41991)), LandmarkObject.landmark( withName: "San Jose", location: CoordinatesObject.coordinates(withLatitude: 37.33619, longitude: -121.89058)) ] item.isEnabled = false self.navigationContext? .calculateRoute( withWaypoints: waypoints, completionHandler: { [weak self] (results: [RouteObject]) in guard let strongSelf = self else { return } if !results.isEmpty { let insets = strongSelf.areaEdge(margin: 70) strongSelf.mapViewController?.setEdgeAreaInsets(insets) strongSelf.mapViewController? .presentRoutes(results, withTraffic: strongSelf.trafficContext, showSummary: true, animationDuration: 1600) } item.isEnabled = true }) } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to calculate a car route between two coordinates and display it on the map with a summary panel. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/CalculateRoute). ![](/docs/ios/assets/images/example_ios_calculate_route-90cb529a0245c53736ddd852bc4535ab.png) **Calculated Route with Insets** ##### Map View and Route Display[​](#map-view-and-route-display "Direct link to Map View and Route Display") The map is displayed using `MapBase` with a declarative `MapRoute` child that automatically re-renders whenever `calculatedRoutes` changes: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/CalculateRoute/CalculateRoute/ContentView.swift) ```swift struct ContentView: View { @State private var navigationContext: NavigationContext? @State private var trafficContext: TrafficContext? @State private var isCalculating: Bool = false @State private var edgeInsets: UIEdgeInsets? @State private var calculatedRoutes: [RouteObject] = [] var body: some View { MapReader { proxy in ZStack { MapBase { MapRoute( routes: calculatedRoutes, traffic: trafficContext, animationDuration: 800 ) } .ignoresSafeArea(edges: [.bottom, .horizontal]) // visually indicate the edge area padding after route is presented if let insets = edgeInsets { EdgeAreaOverlay(insets: insets) .ignoresSafeArea(edges: [.bottom, .horizontal]) .allowsHitTesting(false) } } .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("", systemImage: "point.topleft.down.curvedto.point.bottomright.up") { calculateRoute(proxy) } .disabled(isCalculating) .buttonStyle(.borderedProminent) } ToolbarItem(placement: .topBarTrailing) { Button("", systemImage: "clear") { clearRoute(proxy) } .buttonStyle(.borderedProminent) } } } .navigationTitle("Calculate Route") .navigationBarTitleDisplayMode(.inline) } ``` ##### Setting Route Preferences and Calculating[​](#setting-route-preferences-and-calculating-1 "Direct link to Setting Route Preferences and Calculating") Route preferences are configured on a `RoutePreferencesObject` and passed to a `NavigationContext`. Edge area insets are applied to ensure the route summary fits within the visible map area: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/CalculateRoute/CalculateRoute/ContentView.swift) ```swift func calculateRoute(_ proxy: MapProxy) { guard let mapViewController = proxy.mapViewController else { return } if navigationContext == nil { let preferences = RoutePreferencesObject() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) preferences.setAvoidMotorways(false) preferences.setAvoidTollRoads(false) preferences.setAvoidFerries(false) preferences.setAvoidUnpavedRoads(true) navigationContext = NavigationContext(preferences: preferences) } if trafficContext == nil { trafficContext = TrafficContext() trafficContext?.setUseTraffic(.useOnline) } let waypoints = [ LandmarkObject.landmark( withName: "San Francisco", location: CoordinatesObject.coordinates(withLatitude: 37.77903, longitude: -122.41991)), LandmarkObject.landmark( withName: "San Jose", location: CoordinatesObject.coordinates(withLatitude: 37.33619, longitude: -121.89058)) ] isCalculating = true navigationContext?.calculateRoute(withWaypoints: waypoints, completionHandler: { results in if !results.isEmpty { let scale = UIScreen.main.scale let insets = UIEdgeInsets( top: 120 * scale, left: 70 * scale, bottom: 70 * scale, right: 70 * scale) edgeInsets = insets mapViewController.setEdgeAreaInsets(insets) mapViewController.presentRoutes(results, withTraffic: trafficContext, showSummary: true, animationDuration: 1600) calculatedRoutes = results } isCalculating = false }) } ``` --- ### Calculate Route Multi Map Last updated: April 7, 2026 | 2 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to display two independent `MapViewController` instances side-by-side, each calculating and presenting its own route with a separate `NavigationContext`. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/CalculateRouteMultiMap). ![](/docs/ios/assets/images/example_ios_multi_map_route-6edb1117e672b816cc4ec62ca347dd07.png) **Calculated Routes on multiple maps** ##### Maps Setup[​](#maps-setup "Direct link to Maps Setup") Each map is created with its own constraints so they stack vertically and fill the screen. The maps are initialised lazily — only when the route buttons are tapped: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/CalculateRouteMultiMap/CalculateRouteMultiMap/ViewController.swift) ```swift class ViewController: UIViewController, UISearchBarDelegate { var mapViewController1: MapViewController? var mapViewController2: MapViewController? var navigationContext1: NavigationContext? var navigationContext2: NavigationContext? func createMap1View() { self.mapViewController1 = MapViewController.init() self.mapViewController1!.view.layer.cornerRadius = 8 self.mapViewController1!.view.layer.masksToBounds = true self.addChild(self.mapViewController1!) self.view.addSubview(self.mapViewController1!.view) self.mapViewController1!.didMove(toParent: self) self.mapViewController1?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController1!.view.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 15), self.mapViewController1!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 15), self.mapViewController1!.view.bottomAnchor.constraint(equalTo: self.view.centerYAnchor), self.mapViewController1!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -15) ]) self.mapViewController1!.startRender() } ``` ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/CalculateRouteMultiMap/CalculateRouteMultiMap/ViewController.swift) ```swift func createMap2View() { self.mapViewController2 = MapViewController.init() self.mapViewController2!.view.backgroundColor = UIColor.systemBackground self.mapViewController2!.view.layer.cornerRadius = 8 self.mapViewController2!.view.layer.masksToBounds = true self.addChild(self.mapViewController2!) self.view.addSubview(self.mapViewController2!.view) self.mapViewController2!.didMove(toParent: self) self.mapViewController2?.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController2!.view.topAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 10), self.mapViewController2!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 15), self.mapViewController2!.view.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -5), self.mapViewController2!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -15) ]) self.mapViewController2!.startRender() } ``` ##### Calculating Independent Routes[​](#calculating-independent-routes "Direct link to Calculating Independent Routes") Each route button initialises its respective `NavigationContext` on the first tap and calculates the route on the second. The two maps and contexts operate independently: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/CalculateRouteMultiMap/CalculateRouteMultiMap/ViewController.swift) ```swift @objc func route1ButtonAction(item: UIBarButtonItem) { if self.navigationContext1 == nil { self.createMap1View() let preferences = RoutePreferencesObject.init() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) self.navigationContext1 = NavigationContext.init(preferences: preferences) return } self.mapViewController1?.removeAllRoutes() let waypoints = [ LandmarkObject.landmark( withName: "San Francisco", location: CoordinatesObject.coordinates(withLatitude: 37.77903, longitude: -122.41991)), LandmarkObject.landmark( withName: "San Jose", location: CoordinatesObject.coordinates(withLatitude: 37.33619, longitude: -121.89058)) ] self.navigationContext1? .calculateRoute(withWaypoints: waypoints, completionHandler: { [weak self] (results: [RouteObject]) in guard let strongSelf = self else { return } if !results.isEmpty { strongSelf.mapViewController1? .presentRoutes(results, withTraffic: nil, showSummary: true, animationDuration: 1000) } item.isEnabled = true }) } ``` --- ### Custom Turns ID Last updated: April 7, 2026 | 2 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to replace the default SDK turn icons with custom images keyed by the numeric `TurnId64` value of each upcoming manoeuvre. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/CustomTurnsId). ![](/docs/ios/assets/images/example_ios_custom_turns_id-736e9a2e5353786871b96b807afcd029.png) **Custom Turns ID** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The view controller uses `GEMSdkDelegate` to gate the route button on connectivity and map availability. The camera focus point is moved down so the vehicle position appears centred in the lower portion of the screen during simulation: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/CustomTurnsId/CustomTurnsId/ViewController.swift) ```swift class ViewController: UIViewController, GEMSdkDelegate, MapViewControllerDelegate, NavigationContextDelegate { var mapViewController: MapViewController? var navigationContext: NavigationContext? var soundContext: SoundContext? var panelNavigationViewController: NavigationViewController? override func viewDidLoad() { super.viewDidLoad() GEMSdk.shared().delegate = self self.title = "Custom Turn ID" self.createMapView() self.addRouteButton() self.setFollowPositionCameraFocus() } func setFollowPositionCameraFocus() { guard let mapViewController = self.mapViewController else { return } let point = CGPoint.init(x: 0.5, y: 0.75) mapViewController.getPreferences().getFollowPositionPreferences().setCameraFocus(point) } ``` ##### Rendering Custom Turn Images by ID[​](#rendering-custom-turn-images-by-id "Direct link to Rendering Custom Turn Images by ID") During simulation, `updateCustomTurnInformation` retrieves the `TurnId64` integer for both the next and next-next manoeuvres and loads a matching image from the app bundle: info The set of 64 turn icons are added as image assets to the app bundle with their names set to the corresponding integer value. NavigationViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/CustomTurnsId/CustomTurnsId/NavigationViewController.swift) ```swift func updateCustomTurnInformation(navigationContext: NavigationContext) { if let turnInstruction = navigationContext.getNavigationInstruction() { if let nextTurnDetails = turnInstruction.getNextTurnDetails() { let turnId64 = nextTurnDetails.getTurnId64() if let image = UIImage(named: String(format: "%d", turnId64.rawValue)) { self.customTurnNextImage.image = image self.customTurnNextImage.isHidden = false } } if let nextNextTurnDetails = turnInstruction.getNextNextTurnDetails() { let turnId64 = nextNextTurnDetails.getTurnId64() if let image = UIImage(named: String(format: "%d", turnId64.rawValue)) { self.customTurnNextNextImage.image = image self.customTurnNextNextImage.isHidden = false } } } } ``` ##### Navigation Panel[​](#navigation-panel "Direct link to Navigation Panel") info The `NavigationViewController` used in this example extends the standard turn-by-turn panel with two extra image views that display the custom turn ID icons. Due to its size it is not reproduced here — check the [full implementation on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/blob/main/UIKit/RoutesAndNavigation/CustomTurnsId/CustomTurnsId/NavigationViewController.swift). --- ### Finger Draw Route Last updated: April 7, 2026 | 8 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to draw a path on the map and calculate a route from that path, with the option to share it as a `.gpx` file. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/FingerDrawRoute). ![](/docs/ios/assets/images/example_ios_fingerdrawroute1-f97cc3798eb3e6165332ac065b77e03d.png) **Initial screen** ![](/docs/ios/assets/images/example_ios_fingerdrawroute2-fc22fd64f9da4b0cb94174f2cebca127.png) **Calculated Route** ![](/docs/ios/assets/images/example_ios_fingerdrawroute3-eeb098c8530eef530bd217bbf803c388.png) **Calculated Route with hidden Markers** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The following code outlines the main view, actions and objects: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/FingerDrawRoute/FingerDrawRoute/ViewController.swift) ```swift class ViewController: UIViewController { var mapViewController: MapViewController? var navigationContext: NavigationContext? let buttonConfiguration = UIImage.SymbolConfiguration(pointSize: 26, weight: .semibold) var path: PathObject? var drawButton: UIButton? var visualEffectView: UIView? var statusLabel: UILabel? var showHideButton: UIButton? var routeType: RouteTransportMode = .bicycle var markerCollections: [MarkerCollectionObject] = [] override func viewDidLoad() { super.viewDidLoad() self.mapViewController = self.createMapViewController() self.refreshTitleViewButton() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) guard let mapViewController = self.mapViewController else { return } mapViewController.startRender() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) guard let mapViewController = self.mapViewController else { return } if mapViewController.view.alpha == 0 { let coordinates = CoordinatesObject.coordinates(withLatitude: 45.462514, longitude: 9.188443) // Milano mapViewController.center(onCoordinates: coordinates, zoomLevel: 70, animationDuration: 0) UIView.animate(withDuration: 0.25) { mapViewController.view.alpha = 1 } completion: { finished in self.addDrawButton() self.addStatusButton() } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) guard let mapViewController = self.mapViewController else { return } mapViewController.stopRender() } // MARK: - Map View func createMapViewController() -> MapViewController { let viewController = MapViewController.init() viewController.view.alpha = 0 viewController.view.backgroundColor = UIColor.systemBackground self.addChild(viewController) self.view.addSubview(viewController.view) viewController.didMove(toParent: self) viewController.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ viewController.view.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 0), viewController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0), viewController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -0), viewController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -0) ]) return viewController } ``` ##### Drawing Button[​](#drawing-button "Direct link to Drawing Button") The method for the main action button for drawing and clearing the path: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/FingerDrawRoute/FingerDrawRoute/ViewController.swift) ```swift // MARK: - Drawing func addDrawButton() { guard let mapViewController = self.mapViewController else { return } let image = UIImage.init(systemName: "hand.draw", withConfiguration: self.buttonConfiguration) let button = UIButton.init(type: .system) button.configuration = .bordered() button.configuration?.cornerStyle = .capsule button.configuration?.image = image button.configuration?.baseBackgroundColor = UIColor.systemBackground button.layer.shadowOpacity = 0.6 button.layer.shadowColor = UIColor.systemGray.cgColor let action = UIAction { _ in mapViewController.removeAllRoutes() mapViewController.removeAllMarkers() let state = mapViewController.getTouchViewBehaviour() if state == .default && self.path == nil { button.isHidden = true mapViewController.hideCompass() mapViewController.view.layer.borderWidth = 16 mapViewController.view.layer.borderColor = UIColor.gray.withAlphaComponent(0.26).cgColor mapViewController.setTouchViewBehaviour(.fingerDraw) { marker in self.markerCollections = mapViewController.getAvailableMarkers() self.refreshPencilImage() let attributes = AttributeContainer([NSAttributedString.Key.font: UIFont.systemFont(ofSize: 22, weight: .semibold)]) button.configuration?.attributedTitle = AttributedString("Clear", attributes: attributes) button.configuration?.image = nil button.isHidden = false mapViewController.view.layer.borderColor = nil mapViewController.view.layer.borderWidth = 0 mapViewController.setTouchViewBehaviour(.default) self.refreshTitleViewButton(enabled: false) if let coordinates = marker?.getCoordinates(), !coordinates.isEmpty { let path = PathObject.init(coordinates: coordinates) if let lmk = RouteBookmarksObject.setWaypointTrackData(path) { self.path = path self.calculateRoute(with: [lmk]) } } } } else { self.markerCollections.removeAll() if let navigationContext = self.navigationContext { navigationContext.cancelCalculateRoute() } if let view = self.visualEffectView { view.isHidden = true } mapViewController.showCompass() mapViewController.setTouchViewBehaviour(.default) button.configuration?.image = image button.configuration?.attributedTitle = nil self.path = nil self.refreshShareTrack() self.refreshTitleViewButton(enabled: true) } } button.addAction(action, for: .touchUpInside) mapViewController.view.addSubview(button) let size: CGFloat = 70 button.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ button.topAnchor.constraint(equalTo: mapViewController.view.safeAreaLayoutGuide.topAnchor, constant: 15), button.leadingAnchor.constraint(equalTo: mapViewController.view.safeAreaLayoutGuide.leadingAnchor, constant: 15), button.widthAnchor.constraint(greaterThanOrEqualToConstant: size), button.heightAnchor.constraint(equalToConstant: size) ]) self.drawButton = button } ``` ##### Calculating the Route[​](#calculating-the-route "Direct link to Calculating the Route") ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/FingerDrawRoute/FingerDrawRoute/ViewController.swift) ```swift func createNavigationContext() -> NavigationContext? { let preferences = RoutePreferencesObject.init() preferences.setRouteType(.fastest) preferences.setIgnoreRestrictionsOverTrack(true) preferences.setAccurateTrackMatch(false) // only for track data switch self.routeType { case .pedestrian: preferences.setTransportMode(.pedestrian) default: preferences.setTransportMode(.bicycle) } let navigationContext = NavigationContext.init(preferences: preferences) return navigationContext } func calculateRoute(with waypoints: [LandmarkObject]) { guard let navigationContext = self.createNavigationContext() else { return } self.navigationContext = navigationContext navigationContext.calculateRoute(withWaypoints: waypoints) { routeStatus in self.refreshStatus(routeStatus: routeStatus) } completionHandler: { [weak self] results, code in guard let strongSelf = self else { return } guard let mapViewController = strongSelf.mapViewController else { return } mapViewController.showCompass() if !results.isEmpty, let route = results.first { let insets = strongSelf.areaEdge(margin: 60) mapViewController.setEdgeAreaInsets(insets) mapViewController.presentRoutes(results, withTraffic: nil, showSummary: true, animationDuration: 1600) let preferences = mapViewController.getPreferences() if let settings = preferences.getRenderSettings(route) { settings.textSize = 3.2 settings.imageSize = 3.2 preferences.setRenderSettings(settings, route: route) } if let button = strongSelf.showHideButton { button.isHidden = false } } strongSelf.refreshShareTrack() } } ``` ##### Sharing the Path[​](#sharing-the-path "Direct link to Sharing the Path") ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/FingerDrawRoute/FingerDrawRoute/ViewController.swift) ```swift @objc func sharePathButton() { guard let path = self.path else { return } guard let data = path.export(as: .gpx) else { return } guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } let name = "Track.gpx" let fileURL = documentsURL.appendingPathComponent(name) let success = FileManager.default.createFile(atPath: fileURL.path, contents: data) if success { let activityItems: [Any] = [fileURL] let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: []) activityController.completionWithItemsHandler = { (type, completed, items, error) in } self.present(activityController, animated: true, completion: nil) } } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to draw a path on the map and calculate a route from that path, with the option to share it as a `.gpx` file. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/FingerDrawRoute). ![](/docs/ios/assets/images/example_ios_fingerdrawroute1-f97cc3798eb3e6165332ac065b77e03d.png) **Initial screen** ![](/docs/ios/assets/images/example_ios_fingerdrawroute2-fc22fd64f9da4b0cb94174f2cebca127.png) **Calculated Route** ![](/docs/ios/assets/images/example_ios_fingerdrawroute3-eeb098c8530eef530bd217bbf803c388.png) **Calculated Route with hidden Markers** ##### Map Display[​](#map-display "Direct link to Map Display") The following code outlines the main view, which displays the map and the main actions: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/FingerDrawRoute/FingerDrawRoute/ContentView.swift) ```swift struct ContentView: View { @State var drawPathOn: Bool = false @State var routeCalculated: Bool = false @State private var showStatusLabel: Bool = false @State var navigationContext: NavigationContext? @State var routeTransportMode: RouteTransportMode = .bicycle @State private var routeStatus: RouteStatus = .uninitialized @State private var markerCollections: [MarkerCollectionObject] = [] @State var gpxFileURL: URL? @State var refreshTitleMenuId: UUID = UUID() var body: some View { MapReader { proxy in ZStack(alignment: .leading) { MapBase() .onAppear { proxy.centerOn(coordinates: .milano, zoomLevel: 70) } .ignoresSafeArea(edges: [.bottom, .horizontal]) if drawPathOn == false { VStack { Button { routeCalculated ? clearRoute(proxy) : drawPath(proxy) } label: { VStack { if routeCalculated { Text("Clear") .font(.title2) .padding(.horizontal) } else { Image(systemName: "hand.draw") .font(.system(size: 32, weight: .semibold)) } } .frame(minWidth: 70, minHeight: 70) .foregroundStyle(.primary) .background(.background) .clipShape(Capsule()) .shadow(color: .gray, radius: 3) .padding() } .buttonStyle(PlainButtonStyle()) Spacer() } } VStack { Spacer() // Route status Label if showStatusLabel { HStack { Text(statusText) .font(.system(size: 24, weight: .semibold)) .frame(maxWidth: .infinity) Button(action: { handlePencilButtonTap(proxy: proxy) }) { Image(systemName: "pencil.and.outline") .font(.system(size: 26, weight: .semibold)) .symbolRenderingMode(.palette) .foregroundStyle( Color.black, Color.orange, Color.clear ) .frame(width: 50, height: 50) } .padding(.trailing, 10) } .padding(.horizontal) .frame(height: 60) .background(RoundedRectangle(cornerRadius: 8).fill(Color(.systemBackground))) .padding(.horizontal, 15) } } } .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .title, content: { Menu(content: { Button("Bike") { refreshTitleMenuId = UUID() routeTransportMode = .bicycle } Button("Pedestrian") { refreshTitleMenuId = UUID() routeTransportMode = .pedestrian } }, label: { HStack { Text(routeTransportMode == .bicycle ? "Bike Route" : "Pedestrian Route") Image(systemName: "chevron.down.circle.fill") .resizable() .scaledToFit() .frame(width: 20, height: 20) } .padding(8) .background(RoundedRectangle(cornerRadius: 8).fill(Color(.systemGray5))) }) .id(refreshTitleMenuId) }) ToolbarItem(placement: .topBarTrailing, content: { if let url = gpxFileURL { ShareLink(item: url) { Image(systemName: "square.and.arrow.up") } .buttonStyle(.borderedProminent) } }) } } } ``` ##### Drawing and Calculating the Route[​](#drawing-and-calculating-the-route "Direct link to Drawing and Calculating the Route") The methods for drawing, clearing the path and calculating the route: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/FingerDrawRoute/FingerDrawRoute/ContentView.swift) ```swift func createNavigationContext() -> NavigationContext { guard navigationContext == nil else { return navigationContext! } let preferences = RoutePreferencesObject.init() preferences.setRouteType(.fastest) preferences.setIgnoreRestrictionsOverTrack(true) preferences.setAccurateTrackMatch(false) // only for track data preferences.setTransportMode(routeTransportMode) navigationContext = NavigationContext.init(preferences: preferences) return navigationContext! } func drawPath(_ proxy: MapProxy) { guard let mapViewController = proxy.mapViewController else { return } mapViewController.removeAllRoutes() mapViewController.removeAllMarkers() mapViewController.hideCompass() mapViewController.view.layer.borderWidth = 16 mapViewController.view.layer.borderColor = UIColor.gray.withAlphaComponent(0.26).cgColor mapViewController.setTouchViewBehaviour(.fingerDraw) { marker in mapViewController.showCompass() mapViewController.view.layer.borderWidth = 0 mapViewController.view.layer.borderColor = nil markerCollections = mapViewController.getAvailableMarkers() mapViewController.setTouchViewBehaviour(.default) showStatusLabel = true if let coordinates = marker?.getCoordinates(), !coordinates.isEmpty { let path = PathObject.init(coordinates: coordinates) createShareRoute(path: path) if let lmk = RouteBookmarksObject.setWaypointTrackData(path) { calculateRoute(proxy, waypoints: [lmk]) } } } drawPathOn = true } func clearRoute(_ proxy: MapProxy) { guard let mapViewController = proxy.mapViewController else { return } markerCollections.removeAll() if let navigationContext = navigationContext { navigationContext.cancelCalculateRoute() self.navigationContext = nil } showStatusLabel = false routeCalculated = false routeStatus = .uninitialized gpxFileURL = nil mapViewController.showCompass() mapViewController.setTouchViewBehaviour(.default) mapViewController.removeAllMarkers() mapViewController.removeAllRoutes() } ``` ##### Creating the Path Share URL[​](#creating-the-path-share-url "Direct link to Creating the Path Share URL") ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/FingerDrawRoute/FingerDrawRoute/ContentView.swift) ```swift func createShareRoute(path: PathObject) { guard let data = path.export(as: .gpx) else { return } guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } let name = "Track.gpx" let fileURL = documentsURL.appendingPathComponent(name) let success = FileManager.default.createFile(atPath: fileURL.path, contents: data) if success { self.gpxFileURL = fileURL } } ``` --- ### Follow Position Preferences Last updated: April 7, 2026 | 3 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to customise the follow-position camera behaviour so the user's pinch and drag adjustments to zoom level and tilt angle are persisted across the session, and can be reset to defaults on demand. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/FollowPositionPreferences). ![](/docs/ios/assets/images/example_ios_follow_position_preferences-20edba121aa82569dddb400b7e4028f9.png) **Preferences reset button after gesture adjustments by user** ##### Enabling Persistent Follow Position Modifications[​](#enabling-persistent-follow-position-modifications "Direct link to Enabling Persistent Follow Position Modifications") `setTouchHandlerModifyPersistent(true)` is called during map setup so that any zoom or angle change the user makes while following the position is remembered: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/FollowPositionPreferences/FollowPositionPreferences/ViewController.swift) ```swift class ViewController: UIViewController, MapViewControllerDelegate, NavigationContextDelegate { var mapViewController: MapViewController? var navigationContext: NavigationContext? var soundContext: SoundContext? var trafficContext: TrafficContext? var alarmContext: AlarmContext? var mainRoute: RouteObject? var myResults: [RouteObject] = [] var departure: LandmarkObject? var destination: LandmarkObject? var label = UILabel.init() var panelNavigationViewController: NavigationViewController? var followPositionButton: UIButton? var resetFollowPreferencesButton: UIButton? var positionTrackerState: Data? override func viewDidLoad() { super.viewDidLoad() if let navigationController = self.navigationController { let appearance = navigationController.navigationBar.standardAppearance navigationController.navigationBar.scrollEdgeAppearance = appearance } self.title = "Follow Preferences" self.navigationItem.hidesSearchBarWhenScrolling = false self.navigationItem.largeTitleDisplayMode = .never self.createMapView() self.mapViewController!.startRender() self.addRouteButtons() self.addLabelText() self.addFollowPositionButton() self.addResetFollowPreferencesButton() } func setMapFollowPositionPreferences() { guard let mapViewController = self.mapViewController else { return } let followPositionPreferences = mapViewController.getPreferences().getFollowPositionPreferences() followPositionPreferences.setTouchHandlerModifyPersistent(true) } ``` ##### Restoring Default Follow Position[​](#restoring-default-follow-position "Direct link to Restoring Default Follow Position") A **Reset** button calls `restoreFollowingPosition(withAnimationDuration:completionHandler:)` to animate back to the original zoom and angle without stopping navigation: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/FollowPositionPreferences/FollowPositionPreferences/ViewController.swift) ```swift @objc func followPositionButtonAction() { guard self.navigationContext != nil else { return } guard let mapViewController = self.mapViewController else { return } mapViewController.startFollowingPosition(withAnimationDuration: 200, zoomLevel: -1) { success in // Do any action on completion } } @objc func resetFollowPositionPreferencesButtonAction() { guard self.navigationContext != nil else { return } guard let mapViewController = self.mapViewController else { return } mapViewController.restoreFollowingPosition( withAnimationDuration: 200, completionHandler: { success in // Do any action on completion }) self.positionTrackerState = nil self.resetFollowPreferencesButton!.isHidden = true } ``` ##### Saving Tracker State on Pinch or Shove[​](#saving-tracker-state-on-pinch-or-shove "Direct link to Saving Tracker State on Pinch or Shove") Both the pinch and shove `MapViewControllerDelegate` callbacks trigger `savePositionTrackerState()`. When the map is actively following the position, the current zoom and angle combination is serialised with `saveStatePositionTracker()` so it can be restored on demand: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/FollowPositionPreferences/FollowPositionPreferences/ViewController.swift) ```swift func mapViewController( _ mapViewController: MapViewController, onPinch startPoint1: CGPoint, startPoint2: CGPoint, toPoint1 endPoint1: CGPoint, toPoint2 endPoint2: CGPoint, center: CGPoint ) { self.savePositionTrackerState() } func mapViewController( _ mapViewController: MapViewController, onShove pointersAngleDeg: Double, initial: CGPoint, start: CGPoint, end: CGPoint ) { self.savePositionTrackerState() } func savePositionTrackerState() { guard self.navigationContext != nil else { return } guard let mapViewController = self.mapViewController else { return } let isFollowingPosition = mapViewController.isFollowingPosition() if isFollowingPosition { if let state = mapViewController.saveStatePositionTracker() { self.positionTrackerState = state self.resetFollowPreferencesButton!.isHidden = false } } } ``` ##### Navigation Panel[​](#navigation-panel "Direct link to Navigation Panel") info The `NavigationViewController` used in this example is a full-featured turn-by-turn panel displaying turn images, distance, lane guidance, traffic events, signpost overlays, and safety alerts. Due to its size it is not reproduced here — check the [full implementation on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/blob/main/UIKit/RoutesAndNavigation/FollowPositionPreferences/FollowPositionPreferences/NavigationViewController.swift). --- ### GPX Route Last updated: April 7, 2026 | 3 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to load a `.gpx` file bundled with the app, calculate a route that follows the track, display it on the map, and simulate riding it. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/GpxRoute). ![](/docs/ios/assets/images/example_ios_gpx_route1-cfc4c41679cbd7e358db0a8f8f5bc598.png) **GPX Route Overview** ![](/docs/ios/assets/images/example_ios_gpx_route2-bca0a22885e6340ab5af3349ff60c7af.png) **Routing** ![](/docs/ios/assets/images/example_ios_gpx_route3-b80525c06554cb03461bb1266cab4c9b.png) **Sharing GPX file** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The view controller sets up a map and navigation context and implements the `NavigationContextDelegate` to receive simulation lifecycle callbacks: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/GpxRoute/GpxRoute/ViewController.swift) ```swift class ViewController: UIViewController, UISearchBarDelegate, NavigationContextDelegate { var mapViewController: MapViewController? var navigationContext: NavigationContext? var mainRoute: RouteObject? override func viewDidLoad() { super.viewDidLoad() self.title = "GPX Route" self.navigationItem.largeTitleDisplayMode = .never self.createMapView() self.mapViewController!.startRender() self.addRouteButton() } ``` ##### Loading GPX Data and Calculating the Route[​](#loading-gpx-data-and-calculating-the-route "Direct link to Loading GPX Data and Calculating the Route") The bundled `.gpx` file is loaded as `Data` and passed to `calculateRoute(withStartWaypoints:buffer:endWaypoints:completionHandler:)`. Optional start and end waypoint arrays allow pinning the route to explicit landmarks: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/GpxRoute/GpxRoute/ViewController.swift) ```swift @objc func routeButtonAction(item: UIBarButtonItem) { guard let fileURL = Bundle.main.url(forResource: "test", withExtension: "gpx") else { return } guard let data = NSData.init(contentsOf: fileURL) as Data? else { return } if self.navigationContext == nil { let preferences = RoutePreferencesObject.init() preferences.setTransportMode(.bicycle) preferences.setAvoidUnpavedRoads(false) self.navigationContext = NavigationContext.init(preferences: preferences) self.navigationContext?.delegate = self } let startPoints: [LandmarkObject] = [] let endPoints: [LandmarkObject] = [] self.navigationContext? .calculateRoute( withStartWaypoints: startPoints, buffer: data, endWaypoints: endPoints, completionHandler: { [weak self] (results: [RouteObject]) in guard let strongSelf = self else { return } if let route = results.first { strongSelf.mainRoute = route strongSelf.mapViewController?.setEdgeAreaInsets(strongSelf.areaEdge(margin: 70)) strongSelf.mapViewController?.presentRoutes(results, withTraffic: nil, showSummary: true, animationDuration: 1000) } }) } ``` ##### Simulating Navigation Along the GPX Track[​](#simulating-navigation-along-the-gpx-track "Direct link to Simulating Navigation Along the GPX Track") Once a route is calculated, the simulation begins at 2× speed and the map starts following the simulated position: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/GpxRoute/GpxRoute/ViewController.swift) ```swift @objc func startSimulation(item: UIBarButtonItem) { guard self.mainRoute != nil else { return } self.mapViewController!.removeAllRoutes() self.navigationContext! .simulate(withRoute: self.mainRoute!, speedMultiplier: 2) { [weak self] (success) in guard let strongSelf = self else { return } if success { strongSelf.mapViewController!.showRoutes([strongSelf.mainRoute!], withTraffic: nil, showSummary: false) } } } func navigationContext(_ navigationContext: NavigationContext, navigationStartedForRoute route: RouteObject) { if self.mapViewController!.isFollowingPosition() == false { self.mapViewController!.startFollowingPosition(withAnimationDuration: 1600, zoomLevel: -1) { (success: Bool) in } } } ``` --- ### GPX Thumbnail Image Last updated: April 7, 2026 | 3 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to generate a static map thumbnail image from a `.gpx` track without displaying a full interactive map to the user. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/GpxThumbnailImage). ![](/docs/ios/assets/images/example_ios_gpx_thumbnail-041763028e5c1edca15d77556ae6f9e7.png) **GPX Thumbnail Image Result** ##### Off-screen Map Setup[​](#off-screen-map-setup "Direct link to Off-screen Map Setup") A `MapViewController` is created with a fixed frame but never added to the view hierarchy. `GEMSdkDelegate` callbacks trigger generation only when the map data is current and an internet connection is available: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/GpxThumbnailImage/GpxThumbnailImage/ViewController.swift) ```swift class ViewController: UIViewController, GEMSdkDelegate { var mapViewController: MapViewController? let thumbnailSize = CGSize(width: 300, height: 200) override func viewDidLoad() { super.viewDidLoad() GEMSdk.shared().delegate = self self.title = "GPX Thumbnail" self.addButton() self.addLabel() } func prepareMapView() { guard self.mapViewController == nil else { return } self.mapViewController = MapViewController.init() self.mapViewController!.view.backgroundColor = UIColor.systemBackground self.mapViewController!.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.mapViewController!.view.widthAnchor.constraint(equalToConstant: self.thumbnailSize.width), self.mapViewController!.view.heightAnchor.constraint(equalToConstant: self.thumbnailSize.height) ]) self.mapViewController!.view.layoutIfNeeded() self.mapViewController!.startRender() let preferences = self.mapViewController!.getPreferences() preferences.setMapLabelsFading(false) preferences.setTrafficVisibility(false) } ``` ##### Loading the GPX Path and Placing Markers[​](#loading-the-gpx-path-and-placing-markers "Direct link to Loading the GPX Path and Placing Markers") The GPX data is parsed into a `PathObject`, then start and end markers are placed using `ImageDatabaseObject` pin images. The path is rendered on the off-screen map with custom border and inner colours: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/GpxThumbnailImage/GpxThumbnailImage/ViewController.swift) ```swift func showPath(buffer: Data) { guard let mapViewController = self.mapViewController else { return } guard let collection = mapViewController.getPaths() else { return } let path = PathObject.init(dataBuffer: buffer, format: .gpx) let array = path.getCoordinates() if array.count > 1 { let imageDatabase = ImageDatabaseObject.init() var lmks: [LandmarkObject] = [] if let start = array.first { let lmk = LandmarkObject.init() lmk.setCoordinates(start) if let object = imageDatabase.getImageById(54008) { // pin start lmk.setImage(object) } lmks.append(lmk) } if let stop = array.last { let lmk = LandmarkObject.init() lmk.setCoordinates(stop) if let object = imageDatabase.getImageById(54006) { // pin end lmk.setImage(object) } lmks.append(lmk) } let settings = HighlightRenderSettings.init() settings.imageSize = 4 mapViewController.presentHighlights(lmks, settings: settings) } let success = collection.add(path, colorBorder: UIColor.black, colorInner: UIColor.orange, szBorder: 0.5, szInner: 1.1) if success { if let area = path.getArea() { mapViewController.setEdgeAreaInsets(self.calculateInsets()) mapViewController.center(onArea: area, zoomLevel: -1, animationDuration: 10) { [weak self] finished in // Capture screenshot after centering completes } } } } ``` ##### Waiting for Map Tiles and Capturing the Screenshot[​](#waiting-for-map-tiles-and-capturing-the-screenshot "Direct link to Waiting for Map Tiles and Capturing the Screenshot") Once the path is centred, a `setOnMapViewRendered` callback waits until the map is fully rendered and stationary before calling `makeScreenshot()`, which uses `snapshotImage(with:capture:)` to produce the final thumbnail: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/GpxThumbnailImage/GpxThumbnailImage/ViewController.swift) ```swift func waitingMapTiles(completion: @escaping (_ finished: Bool) -> Void) { guard let mapViewController = self.mapViewController else { return } self.updateStatus(message: "Waiting for data...") mapViewController.setOnMapViewRendered { [weak self] transitionStatus, cameraStatus in if transitionStatus == .complete && cameraStatus == .stationary { guard let strongSelf = self else { return } strongSelf.mapViewController?.resetOnMapViewRenderedCompletion() completion(true) } } } func makeScreenshot() { guard let mapViewController = self.mapViewController else { return } self.updateStatus(message: "Done.") Task { let image = mapViewController.snapshotImage(with: self.thumbnailSize, capture: .zero) let tag = 100 if let imageView = self.view.viewWithTag(tag) as? UIImageView { imageView.image = image } else { let imageView = UIImageView.init() imageView.tag = tag imageView.image = image imageView.contentMode = .scaleAspectFit imageView.layer.borderWidth = 1 imageView.layer.borderColor = UIColor.black.cgColor imageView.layer.cornerRadius = 4 self.view.addSubview(imageView) imageView.translatesAutoresizingMaskIntoConstraints = false let array: [NSLayoutConstraint] = [ imageView.widthAnchor.constraint(equalToConstant: self.thumbnailSize.width), imageView.heightAnchor.constraint(equalToConstant: self.thumbnailSize.height), imageView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0), imageView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0) ] NSLayoutConstraint.activate(array) } } } ``` --- ### Human Voices Last updated: April 7, 2026 | 2 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to download and use human-recorded voice packages for turn-by-turn navigation guidance via `HumanVoiceContext`. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/HumanVoices). ![](/docs/ios/assets/images/example_ios_human_voices1-7f97fee3aa55a8cc9b0277bde01e41ff.png) **Intial screen with Voices and Route buttons** ![](/docs/ios/assets/images/example_ios_human_voices2-12fe6a6f1bdcb37f110cf70d5f4c17bd.png) **Voices List** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") A dedicated **Voices** button navigates to a list screen for browsing and downloading voice packages. A `SoundContext` activates the selected voice before simulation: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/HumanVoices/HumanVoices/ViewController.swift) ```swift class ViewController: UIViewController, MapViewControllerDelegate, NavigationContextDelegate { var mapViewController: MapViewController? var navigationContext: NavigationContext? var soundContext: SoundContext? override func viewDidLoad() { super.viewDidLoad() self.createMapView() self.mapViewController!.startRender() self.addVoicesButton() self.addRouteButton() } func addVoicesButton() { let barButton = UIBarButtonItem.init(title: "Voices", style: .done, target: self, action: #selector(voicesList)) self.navigationItem.leftBarButtonItems = [barButton] } @objc func voicesList() { let viewController = VoicesViewController.init() self.navigationController?.pushViewController(viewController, animated: true) } ``` ##### Browsing and Downloading Voice Packages[​](#browsing-and-downloading-voice-packages "Direct link to Browsing and Downloading Voice Packages") `VoicesViewController` uses `HumanVoiceContext` to fetch both the locally installed list and the online catalogue. Each row shows a country flag and download progress bar. Tapping a row starts or pauses the download: VoicesViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/HumanVoices/HumanVoices/VoicesViewController.swift) ```swift class VoicesViewController: UITableViewController, ContentStoreObjectDelegate { var humanVoiceContext: HumanVoiceContext? var contentStoreList: [ContentStoreObject] = [] override func viewDidLoad() { super.viewDidLoad() self.humanVoiceContext = HumanVoiceContext.init() self.refreshWithLocal() self.refreshWithOnline() } func refreshWithOnline() { self.humanVoiceContext! .getOnlineList { [weak self] array in guard let weakSelf = self else { return } if !array.isEmpty { weakSelf.contentStoreList = array weakSelf.tableView.reloadData() } } } func refreshWithLocal() { self.contentStoreList = self.humanVoiceContext!.getLocalList() } ``` ##### Activating a Human Voice for Navigation[​](#activating-a-human-voice-for-navigation "Direct link to Activating a Human Voice for Navigation") When a row with a completed download is tapped, the voice package identifier is passed to `setUseHumanVoiceWithIdentifier(_:completionHandler:)` on the root view controller's `SoundContext`, then the screen is dismissed: VoicesViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/HumanVoices/HumanVoices/VoicesViewController.swift) ```swift } else if status == .completed { if let rootViewController = self.navigationController?.viewControllers.first as? ViewController { if let soundContext = rootViewController.soundContext { soundContext.setUseHumanVoiceWithIdentifier(object.getIdentifier()) { success in } } self.navigationController?.popViewController(animated: true) } } ``` --- ### Navigate Route Last updated: April 24, 2026 | 7 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to calculate a route using the device's real GPS position as the departure point and navigate it turn-by-turn with voice guidance. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/NavigateRoute). ![](/docs/ios/assets/images/example_ios_navigate_route1-e6ea8bdcb8ee463ccea6b969f07c8f1c.png) **Initial screen** ![](/docs/ios/assets/images/example_ios_navigate_route2-d1e1cd59876d770b38b7f16aa163d8a8.png) **Location Permission** ![](/docs/ios/assets/images/example_ios_navigate_route3-c238539ce6780539544097c06cde6ee3.png) **Navigating on Route** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The view controller manages a `MapViewController`, a `PositionContext` for GPS tracking, and a `DataSourceContext` configured for automotive navigation: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/NavigateRoute/NavigateRoute/ViewController.swift) ```swift class ViewController: UIViewController, CLLocationManagerDelegate, MapViewControllerDelegate, NavigationContextDelegate, PositionContextDelegate { var mapViewController: MapViewController? var locationManager: CLLocationManager? var navigationContext: NavigationContext? var trafficContext: TrafficContext? var mainRoute: RouteObject? var soundContext: SoundContext? var alarmContext: AlarmContext? var positionContext: PositionContext? var dataSource: DataSourceContext? var panelNavigationViewController: NavigationViewController? override func viewDidLoad() { super.viewDidLoad() let configuration = DataSourceConfigurationObject.init() configuration.setPositionDistanceFilter(0) configuration.setPositionAccuracy(.whenMoving) configuration.setPositionActivity(.automotive) configuration.setAllowBackgroundLocationUpdates(true) self.dataSource = DataSourceContext.init() self.dataSource!.setConfiguration(configuration, for: .improvedPosition) self.positionContext = PositionContext.init(context: self.dataSource!) self.positionContext!.delegate = self self.positionContext!.startUpdatingPositionDelegate(.improvedPosition) self.createMapView() self.setMapFollowPositionPreferences() self.mapViewController!.startRender() } ``` ##### Follow Position Preferences[​](#follow-position-preferences "Direct link to Follow Position Preferences") Persistent follow position is enabled so that the user's zoom and tilt adjustments are remembered during simulation/navigation until clearing the `NavigationContext`: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/NavigateRoute/NavigateRoute/ViewController.swift) ```swift func setMapFollowPositionPreferences() { guard let mapViewController = self.mapViewController else { return } let followPositionPreferences = mapViewController.getPreferences().getFollowPositionPreferences() // Allow user to change follow position angle and zoom by touch (pinch or 2x finger drag) followPositionPreferences.setTouchHandlerModifyPersistent(true) } ``` ##### Calculating and Starting Navigation[​](#calculating-and-starting-navigation "Direct link to Calculating and Starting Navigation") After the route is calculated, tapping play starts real GPS navigation. A `SoundContext` provides TTS turn announcements and an `AlarmContext` monitors safety cameras: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/NavigateRoute/NavigateRoute/ViewController.swift) ```swift let preferences = RoutePreferencesObject.init() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) self.navigationContext = NavigationContext.init(preferences: preferences) self.soundContext = SoundContext.init() self.soundContext?.setUseTtsWithCompletionHandler({ success in }) self.alarmContext = AlarmContext.init() self.alarmContext?.setAlarmDistance(600) self.alarmContext?.setMonitorWithoutRoute(false) self.alarmContext?.registerSafetyCameraNotifications(completionHandler: { success in }) self.alarmContext?.registerSocialReportNotifications(completionHandler: { success in }) ``` ##### Getting GPS Position and Calculating the Route[​](#getting-gps-position-and-calculating-the-route "Direct link to Getting GPS Position and Calculating the Route") The route button reads the current device location from `PositionContext` and uses it as the departure point. The destination is set by tapping a landmark or street on the map. Tapping the play button then calls `navigate(withRoute:)` to begin real GPS navigation: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/NavigateRoute/NavigateRoute/ViewController.swift) ```swift guard let position = positionContext.getPosition() else { return } let location = position.getCoordinates() if location.isValid() == false { return } self.departure = LandmarkObject.landmark(withName: "My Position", location: location) guard let start = self.departure, let stop = self.destination else { return } self.navigationContext? .calculateRoute(withWaypoints: [start, stop]) { [weak self] (results: [RouteObject]) in guard let strongSelf = self else { return } if !results.isEmpty { strongSelf.mainRoute = results.first strongSelf.mapViewController? .presentRoutes(results, withTraffic: strongSelf.trafficContext, showSummary: true, animationDuration: 1000) } } } @objc func startNavigation(item: UIBarButtonItem) { guard self.mainRoute != nil else { return } self.mapViewController!.removeAllRoutes() self.navigationContext! .navigate(withRoute: self.mainRoute!) { [weak self] (success) in guard let strongSelf = self else { return } if success { strongSelf.mapViewController!.hideCompass() strongSelf.mapViewController! .presentRoutes( [strongSelf.mainRoute!], withTraffic: strongSelf.trafficContext!, showSummary: false, animationDuration: 1600) strongSelf.startFollowLocation() } } } ``` ##### Navigation Instruction Updates[​](#navigation-instruction-updates "Direct link to Navigation Instruction Updates") `navigationInstructionUpdatedForRoute` is the primary delegate callback that fires on every navigation tick. It updates ETA, remaining time and distance, lazily creates the panel on the first call, then feeds turn, lane, traffic, signpost, safety camera and social report data into the panel on each subsequent update: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/NavigateRoute/NavigateRoute/ViewController.swift) ```swift func navigationContext( _ navigationContext: NavigationContext, navigationInstructionUpdatedForRoute route: RouteObject, updatedEvents: Int32 ) { let eta = navigationContext.getEstimateTimeOfArrivalFormatted() + navigationContext.getEstimateTimeOfArrivalUnitFormatted() let rtt = navigationContext.getRemainingTravelTimeFormatted() + navigationContext.getRemainingTravelTimeUnitFormatted() let rtd = navigationContext.getRemainingTravelDistanceFormatted() + navigationContext.getRemainingTravelDistanceUnitFormatted() let text = eta + " " + rtt + " " + rtd self.label.text = text self.label.isHidden = false if !self.navigationController!.isNavigationBarHidden { if self.panelNavigationViewController == nil { self.createNavigationPanel() } self.navigationController?.setNavigationBarHidden(true, animated: true) } if let turnInstruction = navigationContext.getNavigationInstruction(), turnInstruction.getNavigationStatus() == .running { if turnInstruction.hasNextTurnInfo() { self.panelNavigationViewController?.updateTurnInformation(navigationContext: navigationContext) self.panelNavigationViewController?.updateLaneInformation(navigationContext: navigationContext) self.panelNavigationViewController?.updateTrafficInformation(navigationContext: navigationContext, route: route) self.panelNavigationViewController?.updateSignpostInformation(navigationContext: navigationContext) self.panelNavigationViewController?.updateRoadCodeInformation(navigationContext: navigationContext) self.panelNavigationViewController? .updateSafetyCameraInformation(navigationContext: navigationContext, alarmContext: self.alarmContext!) self.panelNavigationViewController? .updateSocialReportInformation(navigationContext: navigationContext, alarmContext: self.alarmContext!) self.panelNavigationViewController?.refreshContentLayout() } } } ``` ##### Navigation Panel[​](#navigation-panel "Direct link to Navigation Panel") info The `NavigationViewController` used in this example is a full-featured turn-by-turn panel displaying turn images, distance, lane guidance, traffic events, signpost overlays, and safety alerts. Due to its size it is not reproduced here — check the [full implementation on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/blob/main/UIKit/RoutesAndNavigation/NavigateRoute/NavigateRoute/NavigationViewController.swift). This example demonstrates how to use **GEMKit** in a SwiftUI application to calculate a route using the device's real GPS position as the departure point and navigate it turn-by-turn with voice guidance. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/NavigateRoute). ![](/docs/ios/assets/images/example_ios_navigate_route1-e6ea8bdcb8ee463ccea6b969f07c8f1c.png) **Initial screen** ![](/docs/ios/assets/images/example_ios_navigate_route2-d1e1cd59876d770b38b7f16aa163d8a8.png) **Location Permission** ![](/docs/ios/assets/images/example_ios_navigate_route3-c238539ce6780539544097c06cde6ee3.png) **Navigating on Route** ##### Map View and Navigation Panel[​](#map-view-and-navigation-panel "Direct link to Map View and Navigation Panel") Navigation state is managed in `NavigateRouteModel`. The view switches between a route summary and live navigation `MapRoute`, and shows a `NavigationPanelView` overlay during navigation. Map delegate modifiers handle tapping landmarks and streets to set the destination: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/NavigateRoute/NavigateRoute/ContentView.swift) ```swift struct ContentView: View { @StateObject private var model = NavigateRouteModel() var body: some View { MapReader { proxy in ZStack(alignment: .top) { MapBase { if model.isNavigating, let mainRoute = model.mainRoute { MapRoute(routes: [mainRoute], bubbleSummary: false, animationDuration: -1) } else if !model.presentedRoutes.isEmpty { MapRoute(routes: model.presentedRoutes, bubbleSummary: true, animationDuration: 800) } } .didSelectLandmarks { landmarks, point, longTouch in guard let landmark = landmarks.first else { return } model.landmarkSelected(proxy, landmark: landmark) } .didSelectStreets { streets, point, longTouch in guard let street = streets.first else { return } model.landmarkSelected(proxy, landmark: street) } .ignoresSafeArea() .onAppear { model.setupDataSource() model.setupLocation() model.setupFollowPositionPreferences(proxy) } VStack { if model.isNavigating { NavigationPanelView( model: model, onStop: { model.stopNavigation(proxy) } ) .padding(.horizontal, 10) .padding(.top, 5) } Spacer() } } .toolbar(model.isNavigating ? .hidden : .visible, for: .navigationBar) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("", systemImage: "play") { model.startNavigation(proxy) } .disabled(model.mainRoute == nil) .buttonStyle(.borderedProminent) } } } .navigationTitle("Navigate Route") } } ``` ##### GPS Position, Route Calculation and Starting Navigation[​](#gps-position-route-calculation-and-starting-navigation "Direct link to GPS Position, Route Calculation and Starting Navigation") The model sets up a `DataSourceContext` and `PositionContext` for GPS. `calculateRoute(_:)` reads the current location from `positionContext.getPosition()` and uses it as the departure point. `startNavigation(_:)` calls `navigate(withRoute:)` and a `NavigationHandler` closure handles all delegate callbacks: NavigateRouteModel.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/NavigateRoute/NavigateRoute/NavigateRouteModel.swift) ```swift func setupDataSource() { let configuration = DataSourceConfigurationObject() configuration.setPositionDistanceFilter(0) configuration.setPositionAccuracy(.whenMoving) configuration.setPositionActivity(.automotive) dataSource = DataSourceContext() dataSource!.setConfiguration(configuration, for: .improvedPosition) positionContext = PositionContext(context: dataSource!) positionContext!.startUpdatingPositionDelegate(.improvedPosition) } func calculateRoute(_ proxy: MapProxy) { guard let positionContext, let position = positionContext.getPosition() else { return } let location = position.getCoordinates() guard location.isValid(), let destination else { return } let departure = LandmarkObject.landmark(withName: "My Position", location: location) if navigationContext == nil { let preferences = RoutePreferencesObject() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) navigationContext = NavigationContext(preferences: preferences) } if soundContext == nil { soundContext = SoundContext() soundContext?.setUseTtsWithCompletionHandler({ _ in }) } if alarmContext == nil { alarmContext = AlarmContext() alarmContext?.setAlarmDistance(600) alarmContext?.setMonitorWithoutRoute(false) alarmContext?.registerSafetyCameraNotifications(completionHandler: { _ in }) alarmContext?.registerSocialReportNotifications(completionHandler: { _ in }) } navigationContext?.calculateRoute(withWaypoints: [departure, destination], completionHandler: { [weak self] results in Task { @MainActor in guard let self else { return } if !results.isEmpty { self.presentedRoutes = results self.mainRoute = results.first } self.isCalculating = false } }) } func startNavigation(_ proxy: MapProxy) { guard let mapViewController = proxy.mapViewController, let navigationContext, let mainRoute else { return } mapViewController.restoreFollowingPosition(withAnimationDuration: 0) { _ in } mapViewController.removeAllRoutes() let navDelegate = NavigationHandler( onInstructionUpdated: { [weak self] context, route in guard let self else { return } let eta = context.getEstimateTimeOfArrivalFormatted() + context.getEstimateTimeOfArrivalUnitFormatted() let rtt = context.getRemainingTravelTimeFormatted() + context.getRemainingTravelTimeUnitFormatted() let rtd = context.getRemainingTravelDistanceFormatted() + context.getRemainingTravelDistanceUnitFormatted() labelText = eta + " " + rtt + " " + rtd showLabel = true isNavigating = true alarmItems = alarmContext?.getOverlayItemAlarms() ?? [] navigationInstruction = context.getNavigationInstruction() }, onDestinationReached: { [weak self] in self?.stopNavigation(proxy) }, onSound: { [weak self] sound in self?.soundContext?.playSound(sound) } ) navigationContext.delegate = navDelegate navigationHandler = navDelegate navigationContext.navigate(withRoute: mainRoute) { [weak self] success in Task { @MainActor in guard let self, success, let mainRoute = self.mainRoute else { return } proxy.removeAllRoutes() mapViewController.hideCompass() self.presentedRoutes = [mainRoute] self.startFollowLocation(proxy) } } } ``` info The `NavigationPanelView` used in this example is a full-featured SwiftUI turn-by-turn panel displaying turn images, distance, lane guidance, traffic events, and safety alerts. Due to its size it is not reproduced here — check the [full implementation on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/blob/main/SwiftUI/RoutesAndNavigation/NavigateRoute/NavigateRoute/NavigationPanelView.swift). --- ### No Map Just Routing Last updated: April 7, 2026 | 3 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to calculate a route and run turn-by-turn guidance entirely without a map view, using only labels and a navigation panel overlay. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/NoMapJustRouting). ![](/docs/ios/assets/images/example_ios_no_map_routing-e9762316d500161c52faa2366b09e313.png) **Active Routing Without a Map** ##### Routing Without a Map[​](#routing-without-a-map "Direct link to Routing Without a Map") There is no `MapViewController` in this example. The view displays only a status label and a times label. Route calculation status updates are delivered through the progress closure: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/NoMapJustRouting/NoMapJustRouting/ViewController.swift) ```swift class ViewController: UIViewController, NavigationContextDelegate, GEMSdkDelegate { var navigationContext: NavigationContext? var soundContext: SoundContext? var mainRoute: RouteObject? var timesLabel = UILabel.init() var statusLabel = UILabel.init() var panelNavigationViewController: NavigationViewController? override func viewDidLoad() { super.viewDidLoad() GEMSdk.shared().delegate = self self.title = "Just Routing" self.addLabelText() self.addStatusLabelText() self.refreshButtons() } ``` ##### Calculating the Route and Displaying Status[​](#calculating-the-route-and-displaying-status "Direct link to Calculating the Route and Displaying Status") Route status changes are shown directly in the `statusLabel` while the calculation is in progress: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/NoMapJustRouting/NoMapJustRouting/ViewController.swift) ```swift let preferences = RoutePreferencesObject.init() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) self.navigationContext = NavigationContext.init(preferences: preferences) self.navigationContext?.delegate = self self.soundContext = SoundContext.init() self.soundContext?.setUseTtsWithCompletionHandler({ success in }) self.departure = LandmarkObject.landmark( withName: "Hamburg 1", location: CoordinatesObject.coordinates(withLatitude: 53.554010, longitude: 10.027508)) self.destination = LandmarkObject.landmark( withName: "Hamburg 2", location: CoordinatesObject.coordinates(withLatitude: 53.618284, longitude: 10.028659)) self.navigationContext! .calculateRoute(withWaypoints: [self.departure!, self.destination!]) { routeStatus in switch routeStatus { case .calculating: self.statusLabel.text = "Calculating" case .waitingInternetConnection: self.statusLabel.text = "Waiting Internet Connection" case .ready: self.statusLabel.text = "Ready" case .error: self.statusLabel.text = "Error" default: self.statusLabel.text = "" } } completionHandler: { [weak self] results, code in guard let strongSelf = self else { return } if !results.isEmpty { strongSelf.mainRoute = results.first strongSelf.startSimulation() } } ``` ##### Navigation Delegate Callbacks Without a Map[​](#navigation-delegate-callbacks-without-a-map "Direct link to Navigation Delegate Callbacks Without a Map") Since there is no map, all navigation feedback is driven through the delegate. Estimated arrival time, remaining travel time, and distance are displayed in a label, and the navigation panel is created lazily on the first update: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/NoMapJustRouting/NoMapJustRouting/ViewController.swift) ```swift func navigationContext(_ navigationContext: NavigationContext, navigationStartedForRoute route: RouteObject) { if self.navigationController?.isNavigationBarHidden == false { self.navigationController?.popToRootViewController(animated: true) self.navigationController?.setNavigationBarHidden(true, animated: false) } } func navigationContext( _ navigationContext: NavigationContext, navigationInstructionUpdatedForRoute route: RouteObject, updatedEvents: Int32 ) { let eta = navigationContext.getEstimateTimeOfArrivalFormatted() + navigationContext.getEstimateTimeOfArrivalUnitFormatted() let rtt = navigationContext.getRemainingTravelTimeFormatted() + navigationContext.getRemainingTravelTimeUnitFormatted() let rtd = navigationContext.getRemainingTravelDistanceFormatted() + navigationContext.getRemainingTravelDistanceUnitFormatted() let text = eta + " " + rtt + " " + rtd self.timesLabel.text = text self.timesLabel.isHidden = false if self.panelNavigationViewController == nil { self.createNavigationPanel() } if let turnInstruction = navigationContext.getNavigationInstruction(), turnInstruction.getNavigationStatus() == .running { if turnInstruction.hasNextTurnInfo() { self.panelNavigationViewController?.updateTurnInformation(navigationContext: navigationContext) self.panelNavigationViewController?.updateLaneInformation(navigationContext: navigationContext) self.panelNavigationViewController?.updateSignpostInformation(navigationContext: navigationContext) self.panelNavigationViewController?.updateRoadCodeInformation(navigationContext: navigationContext) self.panelNavigationViewController?.refreshContentLayout() } } } ``` ##### Navigation Panel[​](#navigation-panel "Direct link to Navigation Panel") info The `NavigationViewController` used in this example is a full-featured turn-by-turn panel displaying turn images, distance, lane guidance, traffic events, signpost overlays, and safety alerts. Due to its size it is not reproduced here — check the [full implementation on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/blob/main/UIKit/RoutesAndNavigation/NoMapJustRouting/NoMapJustRouting/NavigationViewController.swift). --- ### Public Transit Calculate Route Last updated: April 7, 2026 | 2 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to calculate a public transit route between two locations and render it on the map with a route summary. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/PublicTransitCalculateRoute). ![](/docs/ios/assets/images/example_ios_pt_calculate_route-14de9425a64dd8d71d81ba6da4ea5f3b.png) **Public Transit Calculated Routes** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The view controller is minimal — a map view and a `NavigationContext` configured for public transit: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/PublicTransitCalculateRoute/PublicTransitCalculateRoute/ViewController.swift) ```swift class ViewController: UIViewController, UISearchBarDelegate { var mapViewController: MapViewController? var navigationContext: NavigationContext? var trafficContext: TrafficContext? override func viewDidLoad() { super.viewDidLoad() self.title = "Public Transit" self.navigationItem.largeTitleDisplayMode = .never self.createMapView() self.mapViewController!.startRender() self.addRouteButton() } ``` ##### Calculating the Public Transit Route[​](#calculating-the-public-transit-route "Direct link to Calculating the Public Transit Route") Setting `setTransportMode(.public)` on the preferences switches the engine to the public transit planner. The result is presented on the map with a route summary: info For a public transport route the maximum number of waypoints is 2. ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/PublicTransitCalculateRoute/PublicTransitCalculateRoute/ViewController.swift) ```swift @objc func routeButtonAction(item: UIBarButtonItem) { if self.navigationContext == nil { let preferences = RoutePreferencesObject.init() preferences.setTransportMode(.public) preferences.setRouteType(.fastest) self.navigationContext = NavigationContext.init(preferences: preferences) } if self.trafficContext == nil { self.trafficContext = TrafficContext.init() self.trafficContext?.setUseTraffic(.useOnline) } let waypoints = [ LandmarkObject.landmark( withName: "San Francisco", location: CoordinatesObject.coordinates(withLatitude: 37.77903, longitude: -122.41991)), LandmarkObject.landmark( withName: "San Jose", location: CoordinatesObject.coordinates(withLatitude: 37.33619, longitude: -121.89058)) ] item.isEnabled = false self.navigationContext? .calculateRoute( withWaypoints: waypoints, completionHandler: { [weak self] (results: [RouteObject]) in guard let strongSelf = self else { return } if !results.isEmpty { strongSelf.mapViewController? .presentRoutes(results, withTraffic: strongSelf.trafficContext, showSummary: true, animationDuration: 1000) } item.isEnabled = true }) } ``` --- ### Public Transit Route Instructions Last updated: April 7, 2026 | 2 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to calculate a public transit route and display a detailed step-by-step breakdown of each transit segment, including departure times, line colours, and walking legs. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/PublicTransitRouteInstructions). ![](/docs/ios/assets/images/example_ios_pt_route_instructions-88c05eed8908ee8acd4496e8508e3e4d.png) **Public Transit Route Instructions** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The view controller calculates the route and stores the first result. A list-button in the navigation bar launches the instructions screen: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/PublicTransitRouteInstructions/PublicTransitRouteInstructions/ViewController.swift) ```swift class ViewController: UIViewController, UISearchBarDelegate { var mapViewController: MapViewController? var navigationContext: NavigationContext? var mainRoute: RouteObject? override func viewDidLoad() { super.viewDidLoad() self.title = "Public Transit" self.navigationItem.largeTitleDisplayMode = .never self.createMapView() self.mapViewController!.startRender() self.addRouteButton() } @objc func routeDescription(item: UIBarButtonItem) { if let route = self.mainRoute { let viewController = PTRouteDescriptionViewController.init(route: route) self.navigationController?.pushViewController(viewController, animated: true) } } ``` ##### Building the Instructions Model[​](#building-the-instructions-model "Direct link to Building the Instructions Model") `PTRouteDescriptionViewController` iterates the route's segments, casting each to `PTRouteSegmentObject` to access transit-specific properties such as departure time and line colour. Walking legs are handled separately: PTRouteDescriptionViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/PublicTransitRouteInstructions/PublicTransitRouteInstructions/PTRouteDescriptionViewController.swift) ```swift func prepareModelData() { guard self.route != nil else { return } let segmentList = self.route!.getSegments() for segment in segmentList { guard let segmentPT = segment as? PTRouteSegmentObject else { continue } guard segmentPT.isSignificant() else { continue } let section = ModelDataSection.init() section.area = segment.getGeographicArea() if segment.isCommon() { let t1 = segmentPT.getDepartureTimeFormatted() let t2 = segmentPT.getDepartureTimeUnitFormatted() let transitType = segmentPT.getTransitType() var image = UIImage.init(systemName: "tram") if transitType == .bus { image = UIImage.init(systemName: "bus") } else if transitType == .underground { image = UIImage.init(systemName: "tram.tunnel.fill") } section.departureTime = "Departure: " + t1 + t2 section.image = image section.color = segmentPT.getLineColor().withAlphaComponent(0.4) } else { section.image = UIImage.init(systemName: "figure.walk") let d1 = segment.getTimeDistance()!.getTotalDistanceFormatted() let d2 = segment.getTimeDistance()!.getTotalDistanceUnitFormatted() let t1 = segment.getTimeDistance()!.getTotalTimeFormatted() let t2 = segment.getTimeDistance()!.getTotalTimeUnitFormatted() section.departureTime = d1 + d2 + " (" + t1 + t2 + ")" } for routeInstruction in segment.getInstructions() { let item = ModelDataItem.init() item.routeInstruction = routeInstruction section.items.append(item) } self.modelData.append(section) } } ``` --- ### Road Block Last updated: April 7, 2026 | 2 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to set a road block ahead of the simulated position during navigation, forcing the engine to recalculate around the blocked segment. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RoadBlock). ![](/docs/ios/assets/images/example_ios_roadblock-2d363a8c3f712b9e617ed2cbea8e8532.png) **Rerouting after adding roadblocks** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") A **Road Block** button is added to the left side of the navigation bar and is enabled only once a simulation is active: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RoadBlock/RoadBlock/ViewController.swift) ```swift class ViewController: UIViewController, MapViewControllerDelegate, NavigationContextDelegate { var mapViewController: MapViewController? var navigationContext: NavigationContext? var soundContext: SoundContext? var mainRoute: RouteObject? var panelNavigationViewController: NavigationViewController? override func viewDidLoad() { super.viewDidLoad() self.createMapView() self.mapViewController!.startRender() self.addRoadBlockButton() self.addRouteButton() } func addRoadBlockButton() { let barButton = UIBarButtonItem.init(title: "Road Block", style: .done, target: self, action: #selector(roadBlockAction)) barButton.isEnabled = false self.navigationItem.leftBarButtonItem = barButton } ``` ##### Adding a Road Block During Navigation[​](#adding-a-road-block-during-navigation "Direct link to Adding a Road Block During Navigation") When the button is tapped while navigation or simulation is active, a 100-metre road block is inserted starting from the current position. The engine immediately reroutes: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RoadBlock/RoadBlock/ViewController.swift) ```swift @objc func roadBlockAction() { guard let navigationContext = self.navigationContext else { return } if navigationContext.isSimulationActive() || navigationContext.isNavigationActive() { let length = 100 // m navigationContext.setRoadBlockWithLength(length, starting: -1) } } ``` ##### Navigation Panel[​](#navigation-panel "Direct link to Navigation Panel") info The `NavigationViewController` used in this example is a full-featured turn-by-turn panel displaying turn images, distance, lane guidance, traffic events, signpost overlays, and safety alerts. Due to its size it is not reproduced here — check the [full implementation on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/blob/main/UIKit/RoutesAndNavigation/RoadBlock/RoadBlock/NavigationViewController.swift). --- ### Route Instructions Last updated: April 24, 2026 | 5 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to calculate a route and display a detailed list of turn-by-turn instructions alongside any traffic events along the path. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RouteInstructions). ![](/docs/ios/assets/images/example_ios_route_instructions1-bf97f50b927c5151ed84d1fed774c7ac.png) **Route Overview** ![](/docs/ios/assets/images/example_ios_route_instructions2-3fc3ea82e8b26afaf7409ee406500bbd.png) **List of Route Instructions** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The view controller calculates the route and stores the first result for the instructions view: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RouteInstructions/RouteInstructions/ViewController.swift) ```swift class ViewController: UIViewController { var mapViewController: MapViewController? var navigationContext: NavigationContext? var trafficContext: TrafficContext? var mainRoute: RouteObject? override func viewDidLoad() { super.viewDidLoad() self.title = "Route Instructions" self.navigationItem.largeTitleDisplayMode = .never self.createMapView() self.mapViewController!.startRender() self.addRouteButton() } ``` ##### Calculating the Route[​](#calculating-the-route "Direct link to Calculating the Route") The route is calculated with standard car preferences. The first result is saved on `mainRoute` for later access by the instructions screen: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RouteInstructions/RouteInstructions/ViewController.swift) ```swift let preferences = RoutePreferencesObject.init() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) self.navigationContext = NavigationContext.init(preferences: preferences) self.trafficContext = TrafficContext.init() self.trafficContext?.setUseTraffic(.useOnline) let waypoints = [ LandmarkObject.landmark( withName: "San Francisco", location: CoordinatesObject.coordinates(withLatitude: 37.77903, longitude: -122.41991)), LandmarkObject.landmark( withName: "San Jose", location: CoordinatesObject.coordinates(withLatitude: 37.33619, longitude: -121.89058)) ] self.navigationContext? .calculateRoute(withWaypoints: waypoints, completionHandler: { [weak self] (results: [RouteObject]) in guard let strongSelf = self else { return } if !results.isEmpty { strongSelf.mainRoute = results.first strongSelf.mapViewController? .presentRoutes(results, withTraffic: strongSelf.trafficContext, showSummary: true, animationDuration: 1000) } }) ``` ##### Showing Route Instructions[​](#showing-route-instructions "Direct link to Showing Route Instructions") Tapping the list button pushes the `RouteInstructionsViewController`, which iterates over segments and their instructions to build a table, also incorporating any traffic events: RouteInstructionsViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RouteInstructions/RouteInstructions/RouteInstructionsViewController.swift) ```swift func prepareModelData() { guard self.route != nil else { return } let scale = UIScreen.main.scale let imgSize = CGSize.init(width: 40.0 * scale, height: 40.0 * scale) let segmentList = self.route!.getSegments() for segment in segmentList { for routeInstruction in segment.getInstructions() { let item = ModelDataItem.init() item.routeInstruction = routeInstruction if routeInstruction.hasTurnInfo() { item.title = routeInstruction.getTurnInstruction() } if routeInstruction.hasFollowRoadInfo() { item.description = routeInstruction.getFollowRoadInstruction() } if let turn = routeInstruction.getTurnDetails() { item.image = turn.getTurnImage( imgSize, colorActiveInner: UIColor.black, colorActiveOuter: UIColor.white, colorInactiveInner: UIColor.lightGray, colorInactiveOuter: UIColor.lightGray) } self.modelData.append(item) } } } ``` This example demonstrates how to use **GEMKit** in a SwiftUI application to calculate a route and display a detailed list of turn-by-turn instructions alongside any traffic events along the path. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/RouteInstructions). ![](/docs/ios/assets/images/example_ios_route_instructions1-bf97f50b927c5151ed84d1fed774c7ac.png) **Route Overview** ![](/docs/ios/assets/images/example_ios_route_instructions2-3fc3ea82e8b26afaf7409ee406500bbd.png) **List of Route Instructions** ##### Map View and Instructions Navigation[​](#map-view-and-instructions-navigation "Direct link to Map View and Instructions Navigation") The map is displayed with a `MapRoute` declarative child. A toolbar button navigates to `RouteInstructionsView` once a route is available. When the user selects an instruction and pops back, the map centres on that instruction: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/RouteInstructions/RouteInstructions/ContentView.swift) ```swift struct ContentView: View { @State private var navigationContext: NavigationContext? @State private var trafficContext: TrafficContext? @State private var presentedRoutes: [RouteObject] = [] @State private var mainRoute: RouteObject? @State private var isCalculating: Bool = false @State private var showInstructions: Bool = false @State private var selectedItem: InstructionItem? var body: some View { MapReader { proxy in MapBase { MapRoute(routes: presentedRoutes) } .ignoresSafeArea() .toolbar { ToolbarItem(placement: .topBarLeading) { Button("", systemImage: "list.bullet") { showInstructions = true } .disabled(mainRoute == nil) .buttonStyle(.borderedProminent) } ToolbarItem(placement: .topBarTrailing) { Button("", systemImage: "point.topleft.down.curvedto.point.bottomright.up") { calculateRoute(proxy) } .disabled(isCalculating) .buttonStyle(.borderedProminent) } } .onChange(of: showInstructions) { newValue in if !newValue, let item = selectedItem { selectedItem = nil if let instruction = item.routeInstruction { proxy.mapViewController?.center(onRouteInstruction: instruction, zoomLevel: -1, animationDuration: 2600) } else if let event = item.routeTrafficEvent { proxy.mapViewController?.center(onRouteTrafficEvent: event, zoomLevel: -1, animationDuration: 2000) } } } } .navigationDestination(isPresented: $showInstructions) { if let route = mainRoute { RouteInstructionsView( route: route, selectedItem: $selectedItem, showInstructions: $showInstructions ) } } .navigationTitle("Route Instructions") } } ``` ##### Calculating the Route[​](#calculating-the-route-1 "Direct link to Calculating the Route") Route preferences and contexts are initialised lazily. The first result is stored in `mainRoute` to enable the instructions button and pass the route to `RouteInstructionsView`: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/RouteInstructions/RouteInstructions/ContentView.swift) ```swift func calculateRoute(_ proxy: MapProxy) { if navigationContext == nil { let preferences = RoutePreferencesObject() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) navigationContext = NavigationContext(preferences: preferences) } if trafficContext == nil { trafficContext = TrafficContext() trafficContext?.setUseTraffic(.useOnline) } let waypoints = [ LandmarkObject.landmark( withName: "San Francisco", location: CoordinatesObject.coordinates(withLatitude: 37.77903, longitude: -122.41991)), LandmarkObject.landmark( withName: "San Jose", location: CoordinatesObject.coordinates(withLatitude: 37.33619, longitude: -121.89058)) ] isCalculating = true navigationContext?.calculateRoute(withWaypoints: waypoints, completionHandler: { results in if !results.isEmpty { mainRoute = results.first presentedRoutes = results } isCalculating = false }) } ``` ##### Building the Instruction List[​](#building-the-instruction-list "Direct link to Building the Instruction List") `RouteInstructionsView` iterates over route segments and their instructions to build the list. Traffic events are also included and sorted by their distance position along the route. Tapping an item pops back and centres the map on that instruction or event: RouteInstructionsView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/RouteInstructions/RouteInstructions/RouteInstructionsView.swift) ```swift private func prepareItems() -> [InstructionItem] { var result: [InstructionItem] = [] let scale = UIScreen.main.scale let imgSize = CGSize(width: 40.0 * scale, height: 40.0 * scale) for segment in route.getSegments() { for routeInstruction in segment.getInstructions() { var title = "" var description = "" var image: UIImage? if routeInstruction.hasTurnInfo() { title = routeInstruction.getTurnInstruction() } if routeInstruction.hasFollowRoadInfo() { description = routeInstruction.getFollowRoadInstruction() } if let turn = routeInstruction.getTurnDetails() { image = turn.getTurnImage( imgSize, colorActiveInner: UIColor.black, colorActiveOuter: UIColor.white, colorInactiveInner: UIColor.lightGray, colorInactiveOuter: UIColor.lightGray) } result.append(InstructionItem( title: title, description: description, image: image, // ... statusText, sortKey, routeInstruction routeInstruction: routeInstruction, routeTrafficEvent: nil )) } } // Traffic events sorted by distance along the route if let timeDistance = route.getTimeDistance() { let routeLength = timeDistance.getTotalDistance() for event in route.getTrafficEvents() { if event.hasTrafficEvent(onDistance: routeLength) { result.append(InstructionItem( title: event.getDelayTimeFormatted() + event.getDelayTimeUnitFormatted() + " (" + event.getDescription() + ")", description: event.getFromLandmark()?.getLandmarkName() ?? "", image: event.getImage(imgSize), // ... statusText, sortKey routeInstruction: nil, routeTrafficEvent: event )) } } result.sort { $0.sortKey < $1.sortKey } } return result } ``` --- ### Route Profile Last updated: April 7, 2026 | 5 minutes read

* UIKit This example demonstrates how to use **GEMKit** in a UIKit application to calculate a pedestrian route with terrain profiling enabled and display interactive elevation, surface, road type, and steepness charts. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RouteProfile). ![](/docs/ios/assets/images/example_ios_route_profile-bc80c0b3d27a5873826f4b90accfd4ae.png) **Route Profile** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The view uses a `MapViewControllerDelegate` to update the profile charts when the user taps a different route: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RouteProfile/RouteProfile/ViewController.swift) ```swift class ViewController: UIViewController, UISearchBarDelegate, MapViewControllerDelegate { var mapViewController: MapViewController? var navigationContext: NavigationContext? var trafficContext: TrafficContext? var routeProfileViewController: RouteProfileViewController? override func viewDidLoad() { super.viewDidLoad() self.title = "Route Profile" self.navigationItem.largeTitleDisplayMode = .never self.createMapView() self.addButtons() self.mapViewController!.startRender() } func mapViewController(_ mapViewController: MapViewController, didSelectRoute route: RouteObject) { mapViewController.setMainRoute(route) if let routeProfileViewController = self.routeProfileViewController { routeProfileViewController.refreshWithRoute(route) } } ``` ##### Route Preferences with Terrain Profiling[​](#route-preferences-with-terrain-profiling "Direct link to Route Preferences with Terrain Profiling") `setBuildTerrainProfile(true)` must be set on the `RoutePreferencesObject` before calculation for elevation data to be available: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RouteProfile/RouteProfile/ViewController.swift) ```swift @objc func routeButtonAction(item: UIBarButtonItem) { if self.navigationContext == nil { let preferences = RoutePreferencesObject.init() preferences.setTransportMode(.pedestrian) preferences.setRouteType(.fastest) preferences.setBuildTerrainProfile(true) self.navigationContext = NavigationContext.init(preferences: preferences) } let waypoints = [ LandmarkObject.landmark( withName: "Murren 1", location: CoordinatesObject.coordinates(withLatitude: 46.593443, longitude: 7.910699)), LandmarkObject.landmark( withName: "Murren 2", location: CoordinatesObject.coordinates(withLatitude: 46.559458, longitude: 7.892932)) ] self.navigationContext? .calculateRoute( withWaypoints: waypoints, completionHandler: { [weak self] (results: [RouteObject]) in guard let strongSelf = self else { return } if !results.isEmpty { strongSelf.mapViewController? .presentRoutes(results, withTraffic: self?.trafficContext, showSummary: true, animationDuration: 1600) strongSelf.showRouteProfile() } }) } ``` ##### Embedding the Profile Panel Below the Map[​](#embedding-the-profile-panel-below-the-map "Direct link to Embedding the Profile Panel Below the Map") `showRouteProfile()` embeds `RouteProfileViewController` as a child of `MapViewController`, pinned to the bottom edge. The map area edge insets account for the panel height so the calculated route is not hidden behind it: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RouteProfile/RouteProfile/ViewController.swift) ```swift func showRouteProfile() { guard let mapViewController = self.mapViewController else { return } guard self.routeProfileViewController == nil else { return } let route = mapViewController.getMainRoute() self.routeProfileViewController = RouteProfileViewController() self.routeProfileViewController!.mapViewController = mapViewController self.routeProfileViewController!.route = route self.mapViewController!.addChild(self.routeProfileViewController!) self.mapViewController!.view.addSubview(self.routeProfileViewController!.view) self.routeProfileViewController!.didMove(toParent: self.mapViewController!) self.routeProfileViewController!.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.routeProfileViewController!.view!.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.routeProfileViewController!.view!.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), self.routeProfileViewController!.view!.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), self.routeProfileViewController!.view!.heightAnchor.constraint(equalToConstant: bottomViewHeight) ]) } ``` ##### Route Profile Charts[​](#route-profile-charts "Direct link to Route Profile Charts") `RouteProfileViewController` presents four chart types — elevation, surfaces, road types, and steepness — in a scroll view. Each chart is interactive and highlights the corresponding segment on the map: RouteProfileViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RouteProfile/RouteProfile/RouteProfileViewController.swift) ```swift class RouteProfileViewController: UIViewController { var route: RouteObject? var mapViewController: MapViewController? var elevationChart: ElevationChartViewController? var surfaceChart: SurfacesChartViewController? var roadChart: RoadsChartViewController? var steepnessChart: SteepnessChartViewController? func prepareCharts() { self.elevationChart = ElevationChartViewController() self.elevationChart!.mapViewController = self.mapViewController self.elevationChart!.route = self.route self.surfaceChart = SurfacesChartViewController() self.surfaceChart!.mapViewController = self.mapViewController self.surfaceChart!.route = self.route self.roadChart = RoadsChartViewController() self.roadChart!.mapViewController = self.mapViewController self.roadChart!.route = self.route self.steepnessChart = SteepnessChartViewController() self.steepnessChart!.mapViewController = self.mapViewController self.steepnessChart!.route = self.route } ``` ##### Populating the Elevation Chart using the Terrain Profile[​](#populating-the-elevation-chart-using-the-terrain-profile "Direct link to Populating the Elevation Chart using the Terrain Profile") `refreshChartData()` retrieves the terrain profile from the route via `getTerrainProfile()`, reads the elevation range and samples the altitude values at evenly-spaced distance intervals. Each sample becomes a `ChartDataEntry` that is fed into the `LineChartView`: info This example uses a 3rd party charting solution, use the terrain samples and implement them in your data visualization implementation or use them to populate the charting library of your choice. ElevationChartViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RouteProfile/RouteProfile/ElevationChartViewController.swift) ```swift func refreshChartData(minX: Double = 0.0, maxX: Double = 0.0) { guard let route = self.route else { return } guard let profile = route.getTerrainProfile() else { return } let minElevationLocal = self.localElevation(Double(profile.getMinElevation())) let maxElevationLocal = self.localElevation(Double(profile.getMaxElevation())) let minElevDistanceLocal = self.localDistance(Double(profile.getMinElevationDistance())) let maxElevDistanceLocal = self.localDistance(Double(profile.getMaxElevationDistance())) // configure axis bounds from the profile elevation range lineChartView.leftAxis.axisMinimum = Double((Int(minElevationLocal / 100) - 1) * 100) lineChartView.leftAxis.axisMaximum = Double((Int(maxElevationLocal / 100) + 2) * 100) let timeDist = route.getTimeDistance() if let timeDist = timeDist { let maxXValue = maxX > 0 ? maxX : self.localDistance(Double(timeDist.getTotalDistance())) let distBegin = Int32(self.localDistanceToMeters(minX)) let distEnd = Int32(self.localDistanceToMeters(maxXValue)) let samples = profile.getElevationSamples(Int32(self.sampleSize), distBegin: distBegin, distEnd: distEnd) var chartDataEntries: [ChartDataEntry] = [] var x = minX let step = Double((maxXValue - minX) / Double(self.sampleSize - 1)) for index in 0.. 0 { x += step } x = min(x, maxXValue) let entryX = x let entryY = self.localElevation(y) // insert exact min/max elevation markers at their distance positions if let last = chartDataEntries.last { if minElevDistanceLocal <= entryX && minElevDistanceLocal >= last.x { chartDataEntries.append(ChartDataEntry(x: minElevDistanceLocal, y: minElevationLocal)) } else if maxElevDistanceLocal <= entryX && maxElevDistanceLocal >= last.x { chartDataEntries.append(ChartDataEntry(x: maxElevDistanceLocal, y: maxElevationLocal)) } } chartDataEntries.append(ChartDataEntry(x: entryX, y: entryY)) } let set1 = LineChartDataSet(entries: chartDataEntries, label: "DataSet 1") set1.setColor(.systemBlue) set1.drawCirclesEnabled = false set1.lineWidth = 4 set1.drawFilledEnabled = true set1.fillColor = .systemBlue let data: LineChartData = [set1] data.setDrawValues(false) lineChartView.data = data } } ``` info `ElevationChartViewController`, `SurfacesChartViewController`, `RoadsChartViewController`, and `SteepnessChartViewController` each contain extensive charting logic and use a third-party charting library (`Charts`) for rendering. The full implementations, including axis formatting, highlight callbacks, zoom synchronisation with the map and more, are too large to reproduce here efficiently. Check the [full implementation on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/RouteProfile/RouteProfile) for details. --- ### Simulate Route Last updated: April 24, 2026 | 5 minutes read

* UIKit * SwiftUI This example demonstrates how to use **GEMKit** in a UIKit application to calculate a route and simulate driving it at an accelerated speed with turn-by-turn voice guidance. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/SimulateRoute). ![](/docs/ios/assets/images/example_ios_simulate_route1-736ac46bd3c190db150105819d987f85.png) **Initial screen** ![](/docs/ios/assets/images/example_ios_simulate_route2-0506a14f66ac35c675159a248721902f.png) **Simulating Route** ##### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration") The view controller embeds a `MapViewController` and sets up delegates for both map interaction and navigation event callbacks: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/SimulateRoute/SimulateRoute/ViewController.swift) ```swift class ViewController: UIViewController, MapViewControllerDelegate, NavigationContextDelegate { var mapViewController: MapViewController? var navigationContext: NavigationContext? var soundContext: SoundContext? var trafficContext: TrafficContext? var alarmContext: AlarmContext? var mainRoute: RouteObject? var panelNavigationViewController: NavigationViewController? override func viewDidLoad() { super.viewDidLoad() self.createMapView() self.setMapFollowPositionPreferences() self.mapViewController!.startRender() self.addRouteButton() self.addLabelText() } func setMapFollowPositionPreferences() { guard let mapViewController = self.mapViewController else { return } let followPositionPreferences = mapViewController.getPreferences().getFollowPositionPreferences() // Allow user to change follow position angle and zoom by touch (pinch or 2x finger drag) followPositionPreferences.setTouchHandlerModifyPersistent(true) } ``` ##### Calculating and Simulating the Route[​](#calculating-and-simulating-the-route "Direct link to Calculating and Simulating the Route") Once waypoints are set, the route is calculated and the simulation starts, auto-following the simulated position at 2× speed: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/SimulateRoute/SimulateRoute/ViewController.swift) ```swift let preferences = RoutePreferencesObject.init() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) preferences.setAvoidMotorways(false) preferences.setAvoidTollRoads(false) preferences.setAvoidFerries(false) preferences.setAvoidUnpavedRoads(true) self.navigationContext = NavigationContext.init(preferences: preferences) self.navigationContext?.delegate = self self.soundContext = SoundContext.init() self.soundContext?.setUseTtsWithCompletionHandler({ success in }) self.alarmContext = AlarmContext.init() self.alarmContext?.setAlarmDistance(600) self.alarmContext?.setMonitorWithoutRoute(false) self.alarmContext?.registerSafetyCameraNotifications(completionHandler: { success in }) self.alarmContext?.registerSocialReportNotifications(completionHandler: { success in }) self.departure = LandmarkObject.landmark( withName: "Munich 1", location: CoordinatesObject.coordinates(withLatitude: 48.15741, longitude: 11.53739)) self.destination = LandmarkObject.landmark( withName: "Munich 2", location: CoordinatesObject.coordinates(withLatitude: 48.166730, longitude: 11.53687)) self.navigationContext? .calculateRoute(withWaypoints: [self.departure!, self.destination!], completionHandler: { [weak self] (results: [RouteObject]) in guard let strongSelf = self else { return } if !results.isEmpty { strongSelf.mapViewController? .presentRoutes(results, withTraffic: strongSelf.trafficContext, showSummary: true, animationDuration: 1600) } }) ``` ##### Starting Simulation[​](#starting-simulation "Direct link to Starting Simulation") After a route is presented, tapping the play button starts the simulation at 2× real speed: ViewController.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/UIKit/RoutesAndNavigation/SimulateRoute/SimulateRoute/ViewController.swift) ```swift @objc func startSimulation(item: UIBarButtonItem) { self.navigationContext! .simulate(withRoute: self.mainRoute!, speedMultiplier: 2) { [weak self] (success) in guard let strongSelf = self else { return } if success { strongSelf.mapViewController!.showRoutes([strongSelf.mainRoute!], withTraffic: nil, showSummary: false) } } } ``` ##### Navigation Panel[​](#navigation-panel "Direct link to Navigation Panel") info The `NavigationViewController` used in this example is a full-featured turn-by-turn panel displaying turn images, distance, lane guidance, traffic events, signpost overlays, and safety alerts. Due to its size it is not reproduced here — check the [full implementation on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/blob/main/UIKit/RoutesAndNavigation/SimulateRoute/SimulateRoute/NavigationViewController.swift). This example demonstrates how to use **GEMKit** in a SwiftUI application to calculate a route and simulate driving it at an accelerated speed with turn-by-turn voice guidance. Check the full implementation on [GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/SimulateRoute). ![](/docs/ios/assets/images/example_ios_simulate_route1-736ac46bd3c190db150105819d987f85.png) **Initial screen** ![](/docs/ios/assets/images/example_ios_simulate_route2-0506a14f66ac35c675159a248721902f.png) **Simulating Route** ##### Map View and Navigation Panel[​](#map-view-and-navigation-panel "Direct link to Map View and Navigation Panel") Navigation state is managed in `SimulateRouteModel`. The view switches between a route summary `MapRoute` and a live navigation `MapRoute` once simulation starts, and a `NavigationPanelView` overlay is shown during simulation: ContentView.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/SimulateRoute/SimulateRoute/ContentView.swift) ```swift struct ContentView: View { @StateObject private var model = SimulateRouteModel() var body: some View { MapReader { proxy in ZStack(alignment: .top) { MapBase { if model.isSimulating, let mainRoute = model.mainRoute { MapRoute( routes: [mainRoute], bubbleSummary: false, animationDuration: -1 ) } else { if !model.presentedRoutes.isEmpty { MapRoute( routes: model.presentedRoutes, bubbleSummary: true, animationDuration: 800 ) } } } .ignoresSafeArea() .onAppear { model.setupFollowPositionPreferences(proxy) } VStack { if model.isSimulating { NavigationPanelView( model: model, onStop: { model.stopSimulation(proxy) } ) .padding(.horizontal, 10) .padding(.top, 5) } Spacer() } } .toolbar(model.isSimulating ? .hidden : .visible, for: .navigationBar) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("", systemImage: "play") { model.startSimulation(proxy) } .disabled(model.mainRoute == nil) .buttonStyle(.borderedProminent) } } } .navigationTitle("Simulate Route") } } ``` ##### Route Calculation and Starting Simulation[​](#route-calculation-and-starting-simulation "Direct link to Route Calculation and Starting Simulation") Route preferences and all contexts are initialised lazily in the model. Once a route is available `startSimulation(_:)` calls `simulate(withRoute:speedMultiplier:)` and a `NavigationHandler` closure receives all delegate callbacks: SimulateRouteModel.swift[View on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/tree/main/SwiftUI/RoutesAndNavigation/SimulateRoute/SimulateRoute/SimulateRouteModel.swift) ```swift func calculateRoute(_ proxy: MapProxy) { if navigationContext == nil { let preferences = RoutePreferencesObject() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) navigationContext = NavigationContext(preferences: preferences) } if soundContext == nil { soundContext = SoundContext() soundContext?.setUseTtsWithCompletionHandler({ _ in }) } if alarmContext == nil { alarmContext = AlarmContext() alarmContext?.setAlarmDistance(600) alarmContext?.setMonitorWithoutRoute(false) alarmContext?.registerSafetyCameraNotifications(completionHandler: { _ in }) alarmContext?.registerSocialReportNotifications(completionHandler: { _ in }) } let departure = LandmarkObject.landmark( withName: "Munich 1", location: CoordinatesObject.coordinates(withLatitude: 48.15741, longitude: 11.53739)) let destination = LandmarkObject.landmark( withName: "Munich 2", location: CoordinatesObject.coordinates(withLatitude: 48.166730, longitude: 11.53687)) navigationContext?.calculateRoute(withWaypoints: [departure, destination], completionHandler: { [weak self] results in Task { @MainActor in guard let self else { return } if !results.isEmpty { self.presentedRoutes = results self.mainRoute = results.first } self.isCalculating = false } }) } func startSimulation(_ proxy: MapProxy) { guard let navigationContext, let mainRoute else { return } let navDelegate = NavigationHandler( onNavigationStarted: { [weak self] context, route in self?.startFollowPosition(proxy) }, onInstructionUpdated: { [weak self] context, route in guard let self else { return } let eta = context.getEstimateTimeOfArrivalFormatted() + context.getEstimateTimeOfArrivalUnitFormatted() let rtt = context.getRemainingTravelTimeFormatted() + context.getRemainingTravelTimeUnitFormatted() let rtd = context.getRemainingTravelDistanceFormatted() + context.getRemainingTravelDistanceUnitFormatted() labelText = eta + " " + rtt + " " + rtd showLabel = true isSimulating = true navigationInstruction = context.getNavigationInstruction() alarmItems = alarmContext?.getOverlayItemAlarms() ?? [] }, onDestinationReached: { [weak self] in self?.stopSimulation(proxy) }, onSound: { [weak self] sound in self?.soundContext?.playSound(sound) } ) navigationContext.delegate = navDelegate navigationHandler = navDelegate navigationContext.simulate(withRoute: mainRoute, speedMultiplier: 2) { [weak self] success in Task { @MainActor in guard let self, success, let mainRoute = self.mainRoute else { return } proxy.removeAllRoutes() self.presentedRoutes = [mainRoute] } } } ``` info The `NavigationPanelView` used in this example is a full-featured SwiftUI turn-by-turn panel displaying turn images, distance, lane guidance, traffic events, and safety alerts. Due to its size it is not reproduced here — check the [full implementation on GitHub](https://github.com/magiclane/magiclane-maps-sdk-examples-for-ios/blob/main/SwiftUI/RoutesAndNavigation/SimulateRoute/SimulateRoute/NavigationPanelView.swift). --- ### Alarms In modern mobile applications, providing real-time notifications and alerts is crucial for ensuring a seamless user experience, especially in location-based services. The Maps SDK for iOS offers a robust alarm system that enables developers to monitor and respond to various events based on the user's location, speed, and interactions with geographical boundaries or landmarks. #### [📄️Get started with Alarms](/docs/ios/guides/alarms/get-started-alarms.md) [This guide explains how to set up and configure the alarm system to monitor geographic areas, routes, and receive notifications for various events.](/docs/ios/guides/alarms/get-started-alarms.md) #### [📄️Speed warnings](/docs/ios/guides/alarms/speed-alarms.md) [This guide explains how to configure speed limit monitoring and receive notifications about speed violations and speed limit changes.](/docs/ios/guides/alarms/speed-alarms.md) #### [📄️Landmark and overlay alarms](/docs/ios/guides/alarms/landmark-and-overlay-alarms.md) [This guide explains how to configure notifications when approaching specific landmarks or overlay items within a defined proximity.](/docs/ios/guides/alarms/landmark-and-overlay-alarms.md) #### [📄️Areas alarms](/docs/ios/guides/alarms/areas-alarms.md) [Trigger operations when users enter or exit defined geographic areas using the AlarmContext.](/docs/ios/guides/alarms/areas-alarms.md) #### [📄️Other alarms](/docs/ios/guides/alarms/other-alarms.md) [Receive alerts for environmental changes such as entering or exiting tunnels, transitions between day and night, and country border crossings.](/docs/ios/guides/alarms/other-alarms.md) --- ### Areas alarms Last updated: April 3, 2026 | 2 minutes read

Trigger operations when users enter or exit defined geographic areas using the `AlarmContext`. #### Add areas to monitor[​](#add-areas-to-monitor "Direct link to Add areas to monitor") Define geographic areas and call `monitorArea(_:areaId:)` on your `AlarmContext` instance. You can monitor three types: `RectangleGeographicAreaObject`, `CircleGeographicAreaObject`, and `PolygonGeographicAreaObject`. Assign a unique string identifier to each area so you can determine which zone a user has entered or exited: ```swift let topLeft = CoordinatesObject.coordinates(withLatitude: 1, longitude: 0.5) let bottomRight = CoordinatesObject.coordinates(withLatitude: 0.5, longitude: 1) let rect = RectangleGeographicAreaObject(topLeftLocation: topLeft, bottomRightLocation: bottomRight) let center = CoordinatesObject.coordinates(withLatitude: 1, longitude: 0.5) let circle = CircleGeographicAreaObject(center: center, radius: 100) let points = [ CoordinatesObject.coordinates(withLatitude: 1, longitude: 0.5), CoordinatesObject.coordinates(withLatitude: 0.5, longitude: 1), CoordinatesObject.coordinates(withLatitude: 1, longitude: 1), CoordinatesObject.coordinates(withLatitude: 1, longitude: 0.5), // close the polygon ] let polygon = PolygonGeographicAreaObject(coordinates: points) alarmContext?.monitorArea(rect, areaId: "areaRect") alarmContext?.monitorArea(circle, areaId: "areaCircle") alarmContext?.monitorArea(polygon, areaId: "areaPolygon") ``` Tip When defining a `PolygonGeographicAreaObject`, always close the shape by making the first and last coordinates identical. Otherwise, the SDK may not match the polygon you provided. #### Unmonitor an area[​](#unmonitor-an-area "Direct link to Unmonitor an area") Remove a monitored area by passing the same `GeographicAreaObject` instance used in `monitorArea(_:areaId:)`: ```swift alarmContext?.unmonitorArea(rect) ``` Remove multiple areas by passing an array of their identifiers: ```swift alarmContext?.unmonitorAreas(["areaRect", "areaCircle"]) ``` Remove all monitored areas: ```swift alarmContext?.unmonitorAllAreas() ``` #### Get notified when users enter or exit areas[​](#get-notified-when-users-enter-or-exit-areas "Direct link to Get notified when users enter or exit areas") Implement `alarmContext(onBoundaryCrossed:enteredArea:exitedAreas:)` in your `AlarmContextDelegate`. The callback provides arrays of `AlarmMonitoredAreaObject` for entered and exited areas: ```swift func alarmContext(onBoundaryCrossed alarmContext: AlarmContext, enteredArea arrayIn: [AlarmMonitoredAreaObject], exitedAreas arrayOut: [AlarmMonitoredAreaObject]) { for area in arrayIn { print("Entered area: \(area.getMonitorIdentifier())") } for area in arrayOut { print("Exited area: \(area.getMonitorIdentifier())") } } ``` Retrieve the geographic area from an `AlarmMonitoredAreaObject` using `getMonitorGeographicArea()`. --- ### Get started with Alarms Last updated: April 3, 2026 | 3 minutes read

This guide explains how to set up and configure the alarm system to monitor geographic areas, routes, and receive notifications for various events. The alarm system offers monitoring and notification functionalities for different alarm types, such as boundary crossings, speed limit violations, and landmark alerts. You can configure parameters like alarm distance, overspeed thresholds, and whether monitoring should occur even without a route being followed. The system monitors specific geographic areas or routes and triggers alarms when predefined conditions are met, such as crossing a boundary or entering or exiting a tunnel. It provides customization options for alarm behavior based on location (e.g., inside or outside city limits). You can implement `AlarmContextDelegate` to receive notifications about specific events, including monitoring state changes or when a landmark alarm is triggered. The system supports interaction with various alarm types, including overlay item and landmark alarms, and offers an easy interface for both setting and getting alarm-related information. Tip Multiple `AlarmContext` instances and delegates can operate simultaneously, allowing you to monitor various events concurrently. #### Configure AlarmContext and delegate[​](#configure-alarmcontext-and-delegate "Direct link to Configure AlarmContext and delegate") Create an `AlarmContext` instance and assign a delegate conforming to `AlarmContextDelegate`: ```swift // Retain as a property to keep the alarm active var alarmContext: AlarmContext? func configureAlarms() { alarmContext = AlarmContext() alarmContext?.delegate = self } // MARK: - AlarmContextDelegate func alarmContext(onBoundaryCrossed alarmContext: AlarmContext, enteredArea arrayIn: [AlarmMonitoredAreaObject], exitedAreas arrayOut: [AlarmMonitoredAreaObject]) {} func alarmContext(onMonitoringStateChanged alarmContext: AlarmContext) {} func alarmContext(onTunnelEntered alarmContext: AlarmContext) {} func alarmContext(onTunnelLeft alarmContext: AlarmContext) {} func alarmContext(onLandmarkAlarmsUpdated alarmContext: AlarmContext) {} func alarmContext(onOverlayItemAlarmsUpdated alarmContext: AlarmContext) {} func alarmContext(onLandmarkAlarmsPassedOver alarmContext: AlarmContext) {} func alarmContext(onOverlayItemAlarmsPassedOver alarmContext: AlarmContext) {} func alarmContext(_ alarmContext: AlarmContext, onHighSpeed limit: Double, insideCityArea: Bool) {} func alarmContext(_ alarmContext: AlarmContext, onSpeedLimit speed: Double, limit: Double, insideCityArea: Bool) {} func alarmContext(_ alarmContext: AlarmContext, onNormalSpeed limit: Double, insideCityArea: Bool) {} func alarmContext(onEnterDayMode alarmContext: AlarmContext) {} func alarmContext(onEnterNightMode alarmContext: AlarmContext) {} func alarmContext(_ alarmContext: AlarmContext, onCountryLeft countryCode: String) {} func alarmContext(_ alarmContext: AlarmContext, onCountryEntered code: String) {} ``` All `AlarmContextDelegate` methods are `@optional`. Implement only the callbacks relevant to your use case. warning The `AlarmContext` object must remain in memory for the duration of the monitoring period. Store it as a property on a long-lived object. Deallocating it will stop all alarms. #### AlarmContextDelegate methods[​](#alarmcontextdelegate-methods "Direct link to AlarmContextDelegate methods") | Delegate method | Description | | ---------------------------------------------------------- | ------------------------------------------------------------------------ | | `alarmContext(onBoundaryCrossed:enteredArea:exitedAreas:)` | Called when the user enters or exits monitored geographic areas | | `alarmContext(onMonitoringStateChanged:)` | Called when the alarm monitoring state changes | | `alarmContext(onTunnelEntered:)` | Called when the user enters a tunnel | | `alarmContext(onTunnelLeft:)` | Called when the user exits a tunnel | | `alarmContext(onLandmarkAlarmsUpdated:)` | Called when the list of approaching landmarks changes | | `alarmContext(onOverlayItemAlarmsUpdated:)` | Called when the list of approaching overlay items changes | | `alarmContext(onLandmarkAlarmsPassedOver:)` | Called when a monitored landmark has been passed | | `alarmContext(onOverlayItemAlarmsPassedOver:)` | Called when a monitored overlay item has been passed | | `alarmContext(_:onHighSpeed:insideCityArea:)` | Called when the user exceeds the speed limit by the configured threshold | | `alarmContext(_:onSpeedLimit:limit:insideCityArea:)` | Called when the speed limit on the current road segment changes | | `alarmContext(_:onNormalSpeed:insideCityArea:)` | Called when the user returns to a normal speed | | `alarmContext(onEnterDayMode:)` | Called when the location transitions to daylight conditions | | `alarmContext(onEnterNightMode:)` | Called when the location transitions to night conditions | | `alarmContext(_:onCountryLeft:)` | Called when the user crosses a country border (leaving) | | `alarmContext(_:onCountryEntered:)` | Called when the user crosses a country border (entering) | --- ### Landmark and overlay alarms Last updated: April 3, 2026 | 4 minutes read

This guide explains how to configure notifications when approaching specific landmarks or overlay items within a defined proximity. The `AlarmContext` can be configured to send notifications when approaching specific landmarks or overlay items within a defined proximity. This behavior can be tailored to trigger notifications exclusively during navigation or simulation modes, or while freely exploring the map without a predefined route. Use cases include: * Notify users about incoming reports such as speed cameras, police, accidents, or other road hazards * Notify users when approaching points of interest, such as historical landmarks or scenic viewpoints * Notify users about traffic signs such as stop and give way signs Tip You can search for landmarks along the active route and add them for monitoring. Be sure to account for potential route deviations. warning If notifications are not triggered via the delegate, make sure that: * The `AlarmContext` is properly initialized and kept alive * The alarm distance and `monitorWithoutRoute` are configured as needed * The landmark stores or overlays to be monitored are successfully added to the `AlarmContext` * Overlay items are on the correct side of the road — items on the opposite side will not trigger notifications #### Configure alarm distance[​](#configure-alarm-distance "Direct link to Configure alarm distance") Set the distance threshold in meters for triggering notifications when approaching a landmark or overlay item: ```swift alarmContext?.setAlarmDistance(200) ``` Retrieve the current alarm distance: ```swift let distance = alarmContext?.getAlarmDistance() ?? 0 ``` #### Configure alarms without active navigation[​](#configure-alarms-without-active-navigation "Direct link to Configure alarms without active navigation") By default, alarms are only triggered during active navigation or simulation. To enable notifications at all times regardless of navigation state: ```swift alarmContext?.setMonitorWithoutRoute(true) ``` Retrieve the current state: ```swift let isMonitoringWithoutRoute = alarmContext?.getMonitorWithoutRoute() ?? false ``` #### Landmark alarms[​](#landmark-alarms "Direct link to Landmark alarms") ##### Configure alarm callbacks[​](#configure-alarm-callbacks "Direct link to Configure alarm callbacks") Implement `alarmContext(onLandmarkAlarmsUpdated:)` and `alarmContext(onLandmarkAlarmsPassedOver:)` to receive notifications when approaching landmarks and when they have been passed: ```swift func alarmContext(onLandmarkAlarmsUpdated alarmContext: AlarmContext) { guard let alarmsObject = alarmContext.getLandmarkAlarmsObject() else { return } let positions = alarmsObject.getLandmarkPositions() // Sorted ascending by distance from the current position if let closest = positions.first { let landmark = closest.getLandmark() let distance = closest.getDistance() print("Approaching \(landmark.getLandmarkName()) — \(distance) m away") } } func alarmContext(onLandmarkAlarmsPassedOver alarmContext: AlarmContext) { print("A landmark was passed over") } ``` info `alarmContext(onLandmarkAlarmsUpdated:)` is continuously triggered once the threshold distance is exceeded, until the landmark is passed. `alarmContext(onLandmarkAlarmsPassedOver:)` is called once when the landmark is intercepted. ##### Specify landmarks to monitor[​](#specify-landmarks-to-monitor "Direct link to Specify landmarks to monitor") Add a `LandmarkStoreContext` to the `AlarmContext` landmark collection using `getLandmarkStoreCollection()`: ```swift // Create a landmark store and add landmarks to it let landmarkStore = LandmarkStoreContext(identifier: 0) let landmark = LandmarkObject() landmark.setCoordinates(CoordinatesObject.coordinates(withLatitude: 49.0576, longitude: 1.9705)) landmarkStore.addLandmark(landmark) // Add all categories of this store to the alarm collection alarmContext?.getLandmarkStoreCollection()?.addAllStoreCategories(landmarkStore.getId()) ``` Multiple stores can be added simultaneously. Remove them using `removeAllStoreCategories(_:)` or `removeStoreCategoryId(_:categoryId:)`. #### Overlay alarms[​](#overlay-alarms "Direct link to Overlay alarms") The workflow for overlay items mirrors that for landmarks, with comparable behavior and functionality. All notices specified for landmarks also apply to overlay items. warning To enable overlay alarms, a `MapViewController` must be present with a style that includes the overlay. The overlay must also be enabled for alarms to function. ##### Configure alarm callbacks[​](#configure-alarm-callbacks-1 "Direct link to Configure alarm callbacks") Implement `alarmContext(onOverlayItemAlarmsUpdated:)` and `alarmContext(onOverlayItemAlarmsPassedOver:)`: ```swift func alarmContext(onOverlayItemAlarmsUpdated alarmContext: AlarmContext) { guard let alarmsObject = alarmContext.getOverlayItemAlarmsObject() else { return } let positions = alarmsObject.getOverlayItemPositions() // Sorted ascending by distance from the current position if let closest = positions.first { let overlayItem = closest.getOverlayItem() let distance = closest.getDistance() print("Approaching overlay item — \(distance) m away") } } func alarmContext(onOverlayItemAlarmsPassedOver alarmContext: AlarmContext) { print("An overlay item was passed over") } ``` ##### Specify overlays to monitor[​](#specify-overlays-to-monitor "Direct link to Specify overlays to monitor") Add overlays to the `AlarmContext` using `geOverlayMutableCollection()`. Overlays are specified by their overlay ID: ```swift // Add the social reports overlay (e.g. speed cameras, hazards) let socialReportsOverlayId = Int32(CommonOverlayIdentifier.socialReports.rawValue) // use the appropriate overlay ID alarmContext?.geOverlayMutableCollection()?.add(Int32(socialReportsOverlayId)) ``` Remove an overlay from monitoring: ```swift alarmContext?.geOverlayMutableCollection()?.remove(Int32(socialReportsOverlayId)) ``` --- ### Other alarms Last updated: April 3, 2026 | 1 minute read

Receive alerts for environmental changes such as entering or exiting tunnels, transitions between day and night, and country border crossings. #### Tunnel events[​](#tunnel-events "Direct link to Tunnel events") Implement `alarmContextOnTunnelEntered(_:)` and `alarmContextOnTunnelLeft(_:)` to receive notifications when entering or exiting a tunnel: ```swift func alarmContext(onTunnelEntered alarmContext: AlarmContext) { print("Tunnel entered") } func alarmContext(onTunnelLeft alarmContext: AlarmContext) { print("Tunnel left") } ``` #### Day and night transitions[​](#day-and-night-transitions "Direct link to Day and night transitions") Receive notifications when your location transitions between day and night based on geographical region and seasonal changes: ```swift func alarmContext(onEnterDayMode alarmContext: AlarmContext) { print("Day mode entered") } func alarmContext(onEnterNightMode alarmContext: AlarmContext) { print("Night mode entered") } ``` #### Country border crossings[​](#country-border-crossings "Direct link to Country border crossings") Receive notifications when the user crosses a country border: ```swift func alarmContext(_ alarmContext: AlarmContext, onCountryLeft countryCode: String) { print("Left country: \(countryCode)") } func alarmContext(_ alarmContext: AlarmContext, onCountryEntered code: String) { print("Entered country: \(code)") } ``` Country codes are provided in ISO 3166-1 alpha-3 format. --- ### Speed warnings Last updated: April 3, 2026 | 2 minutes read

This guide explains how to configure speed limit monitoring and receive notifications about speed violations and speed limit changes. The SDK monitors and notifies you about speed limits and violations. You can configure alerts for when a user exceeds the speed limit, when the speed limit changes on a new road segment, and when the user returns to a normal speed range. Thresholds for speed violations can be adjusted independently for city and non-city areas. #### Configure speed limit callbacks[​](#configure-speed-limit-callbacks "Direct link to Configure speed limit callbacks") Implement the speed-related `AlarmContextDelegate` methods: ```swift func alarmContext(_ alarmContext: AlarmContext, onHighSpeed limit: Double, insideCityArea: Bool) { if insideCityArea { print("Speed limit exceeded inside city area — limit: \(limit) m/s") } else { print("Speed limit exceeded outside city area — limit: \(limit) m/s") } } func alarmContext(_ alarmContext: AlarmContext, onSpeedLimit speed: Double, limit: Double, insideCityArea: Bool) { if insideCityArea { print("New speed limit \(limit) m/s inside city area. Current speed: \(speed) m/s") } else { print("New speed limit \(limit) m/s outside city area. Current speed: \(speed) m/s") } } func alarmContext(_ alarmContext: AlarmContext, onNormalSpeed limit: Double, insideCityArea: Bool) { if insideCityArea { print("Normal speed restored inside city area — limit: \(limit) m/s") } else { print("Normal speed restored outside city area — limit: \(limit) m/s") } } ``` The `onHighSpeed` callback is continuously triggered while the user exceeds the speed limit by more than the configured threshold. The `onSpeedLimit` callback fires when the road segment speed limit changes. The `onNormalSpeed` callback fires when the user's speed returns within the limit. info Although the parameter is named `insideCityArea`, it refers to areas with generally lower speed limits, such as cities, towns, villages, or similar settlements, regardless of their classification. warning The `limit` value provided to `onSpeedLimit` will be `0` if the matched road section has no speed limit available or if no road could be found. #### Set speed threshold[​](#set-speed-threshold "Direct link to Set speed threshold") Configure the overspeed threshold using `setOverSpeedThreshold(_:insideCityArea:)`: ```swift // Trigger onHighSpeed when speed limit is exceeded by 1 m/s inside a city area alarmContext?.setOverSpeedThreshold(1, insideCityArea: true) // Trigger onHighSpeed when speed limit is exceeded by 3 m/s outside a city area alarmContext?.setOverSpeedThreshold(3, insideCityArea: false) ``` #### Get speed threshold[​](#get-speed-threshold "Direct link to Get speed threshold") Retrieve the currently configured threshold using `getOverSpeedThreshold(_:)`: ```swift let thresholdInsideCity = alarmContext?.getOverSpeedThreshold(true) ?? 0 let thresholdOutsideCity = alarmContext?.getOverSpeedThreshold(false) ?? 0 ``` --- ### Core This section covers the core runtime entities used across the Maps SDK for iOS: geographic primitives, positions, landmarks, markers, overlays, routes, navigation instructions, traffic events, and image rendering APIs. You can use these guides as a reference path from low-level map entities to real-time navigation data. #### [📄️Base entities](/docs/ios/guides/core/base-entities.md) [This page covers the fundamental building blocks of the iOS SDK: coordinates, paths, and geographic areas.](/docs/ios/guides/core/base-entities.md) #### [📄️Positions](/docs/ios/guides/core/positions.md) [This page covers position handling in iOS using PositionObject and PositionContext.](/docs/ios/guides/core/positions.md) #### [📄️Landmarks](/docs/ios/guides/core/landmarks.md) [A landmark is a rich point-of-interest entity represented by LandmarkObject. It combines location, metadata, media, categories, and store membership. Landmarks represent significant, categorized locations with rich metadata and structured context about a place.](/docs/ios/guides/core/landmarks.md) #### [📄️Markers](/docs/ios/guides/core/markers.md) [A marker is a visual geometry represented by MarkerObject. Markers are ideal for temporary annotations, shapes, user-defined graphics, and lightweight map overlays that are local to your app session.](/docs/ios/guides/core/markers.md) #### [📄️Overlays](/docs/ios/guides/core/overlays.md) [An overlay is an additional map layer with data stored on Magic Lane servers, accessible in both online and offline modes. Overlays can be default or user-defined, and they support category hierarchies and downloadable offline data.](/docs/ios/guides/core/overlays.md) #### [📄️Landmarks vs Markers vs Overlays](/docs/ios/guides/core/landmarks-markers-overlays.md) [When building map features, choose the entity that matches your data lifecycle and interaction model.](/docs/ios/guides/core/landmarks-markers-overlays.md) #### [📄️Routes](/docs/ios/guides/core/routes.md) [A route represents a navigable path between two or more landmarks (waypoints), including distance, estimated time, and navigation instructions.](/docs/ios/guides/core/routes.md) #### [📄️Navigation Instructions](/docs/ios/guides/core/navigation-instructions.md) [The iOS SDK provides real-time navigation guidance through NavigationInstructionObject. It exposes current street and road details, speed-limit context, remaining time and distance, lane guidance, and upcoming maneuvers during active navigation or simulation.](/docs/ios/guides/core/navigation-instructions.md) #### [📄️Traffic Events](/docs/ios/guides/core/traffic-events.md) [The Maps SDK for iOS provides real-time traffic information about delays, incidents, and road restrictions that can affect routing and navigation.](/docs/ios/guides/core/traffic-events.md) #### [📄️Images](/docs/ios/guides/core/images.md) [The Maps SDK for iOS uses ImageObject for plain SDK images and UIKit-native UIImage rendering for display. Some APIs expose ImageObject so you can control rendering yourself, while navigation-oriented APIs render UIImage directly.](/docs/ios/guides/core/images.md) --- ### Base entities Last updated: March 18, 2026 | 7 minutes read

This page covers the fundamental building blocks of the iOS SDK: coordinates, paths, and geographic areas. #### Coordinates[​](#coordinates "Direct link to Coordinates") `CoordinatesObject` represents a geographic point in WGS84 using latitude, longitude, and optional altitude. The Maps SDK for iOS uses the [WGS](https://en.wikipedia.org/wiki/World_Geodetic_System) coordinates standard. **Key components:** * **Latitude**: North-south position, valid range `-90.0 ... +90.0` * **Longitude**: East-west position, valid range `-180.0 ... +180.0` * **Altitude**: Height in meters (optional) ##### Create coordinates[​](#create-coordinates "Direct link to Create coordinates") Create a `CoordinatesObject` using the factory method with latitude and longitude: ```swift let eiffelTower = CoordinatesObject.coordinates(withLatitude: 48.858844, longitude: 2.294351) ``` To include altitude: ```swift let withAltitude = CoordinatesObject.coordinates(withLatitude: 48.858844, longitude: 2.294351, altitude: 324.0) ``` ##### Validate coordinates[​](#validate-coordinates "Direct link to Validate coordinates") Use `isValid` to confirm a coordinate object is usable before passing it into other SDK calls: ```swift if eiffelTower.isValid { // safe to use } ``` ##### Calculate distance[​](#calculate-distance "Direct link to Calculate distance") `getDistance(_:)` calculates the distance in meters between two `CoordinatesObject` instances. It computes the Haversine (great-circle) distance — the shortest path over the Earth's surface — and accounts for altitude if both coordinates carry one. ```swift let coords1 = CoordinatesObject.coordinates(withLatitude: 48.858844, longitude: 2.294351) let coords2 = CoordinatesObject.coordinates(withLatitude: 48.854520, longitude: 2.299751) let distanceInMeters = coords1.getDistance(coords2) ``` This result is the geographic (straight-line) distance, not the road distance that would be followed along a route. ##### Calculate azimuth[​](#calculate-azimuth "Direct link to Calculate azimuth") `getAzimuth(_:)` returns the bearing in degrees from one coordinate to another, calculated using the WGS84 ellipsoid model. A result of `0.0` points north, `90.0` points east, and so on. ```swift let bearing = coords1.getAzimuth(coords2) ``` info `getDistance(_:)` and `getAzimuth(_:)` operate on the WGS84 ellipsoid and may exhibit slight inaccuracies over very long distances due to Earth curvature approximation. danger Do not compare coordinates using strict floating-point equality (`==`). Minor variations in representation can cause incorrect results. For example, `48.858395` may be stored internally as `48.858394583109785`. Use a small numerical tolerance (epsilon) for reliable comparisons. #### Path[​](#path "Direct link to Path") `PathObject` is a core component for representing and managing an ordered sequence of `CoordinatesObject` points. It supports creation from raw coordinates or file data, manipulation, and export. **Key capabilities:** * Create a path from raw coordinates or a data buffer (`GPX`, `KML`, `NMEA`, `GeoJSON`, and other supported formats) * Read path coordinates, waypoints, and metadata * Compute enclosing area and total length * Clone complete or partial paths, including in reverse * Export path data back to file formats ##### Supported formats[​](#supported-formats "Direct link to Supported formats") The `PathFileFormat` enum defines the supported import and export formats: | Format | Description | | ----------------- | --------------------------------- | | `.gpx` | GPX | | `.kml` | KML | | `.nmea` | NMEA | | `.geoJson` | GeoJSON | | `.latLonTxt` | Latitude, Longitude text file | | `.lonLatTxt` | Longitude, Latitude text file | | `.packedGeometry` | Packed geometry (internal format) | ##### Create from coordinates[​](#create-from-coordinates "Direct link to Create from coordinates") Create a `PathObject` directly from an array of `CoordinatesObject` values: ```swift let coordinates = [ CoordinatesObject.coordinates(withLatitude: 40.786, longitude: -74.202), CoordinatesObject.coordinates(withLatitude: 40.690, longitude: -74.209), CoordinatesObject.coordinates(withLatitude: 40.695, longitude: -73.814), CoordinatesObject.coordinates(withLatitude: 40.782, longitude: -73.710), ] let path = PathObject(coordinates: coordinates) ``` ##### Create from data[​](#create-from-data "Direct link to Create from data") Create a `PathObject` from file data in a supported format: ```swift let data: Data = ... // GPX file contents let path = PathObject(dataBuffer: data, format: .gpx) ``` ##### Get coordinate at position[​](#get-coordinate-at-position "Direct link to Get coordinate at position") Retrieve the coordinate at a normalized position along the path (0.0 = start, 1.0 = end): ```swift if let midPoint = path.getCoordinatesAtPercent(0.5) { // midPoint is a CoordinatesObject at the halfway mark } ``` ##### Export path data[​](#export-path-data "Direct link to Export path data") `exportAs(_:)` returns the path as `Data?` in the requested format: ```swift if let exportedData = path.exportAs(.geoJson) { // write exportedData to disk or share it } ``` ##### Main operations[​](#main-operations "Direct link to Main operations") | Method | Description | | ------------------------------- | ----------------------------------------- | | `init(coordinates:)` | Creates a path from coordinate list | | `init(dataBuffer:format:)` | Creates a path from file data | | `getCoordinates()` | Returns the internal coordinates | | `getWayPoints()` | Returns waypoint indices | | `getArea()` | Returns path bounding rectangle | | `getLength()` | Returns path length in meters | | `getName()` / `setName(_:)` | Gets or sets the path name | | `cloneStartEnd(_:endLocation:)` | Clones sub-path between coordinates | | `cloneReverse()` | Clones reversed path | | `exportAs(_:)` | Exports path as `Data?` in chosen format | | `getCoordinatesAtPercent(_:)` | Returns coordinate at normalized progress | #### Geographic areas[​](#geographic-areas "Direct link to Geographic areas") Geographic areas represent specific regions used for map framing, search restrictions, geofencing, and spatial checks. Multiple SDK entities return a bounding box as a geographic area. **Available types:** * **Rectangle geographic area** — Rectangular area with sides aligned to longitude and latitude lines * **Circle geographic area** — Area around a center point with a specified radius in meters * **Polygon geographic area** — Complex area with high precision for detailed boundaries The abstract base class is `GeographicAreaObject`. The `GeographicAreaType` enum identifies which subtype a given instance is: | Type | Description | | ----------------- | -------------------- | | `.undefined` | No type set | | `.circle` | Circle area | | `.rectangle` | Rectangle area | | `.polygon` | Polygon area | | `.tileCollection` | Tile-collection area | ##### Base operations[​](#base-operations "Direct link to Base operations") | Method | Description | | ------------------------- | ------------------------------------------------------- | | `getType()` | Returns the `GeographicAreaType` of this area | | `containsCoordinates(_:)` | Checks if a point is inside the area | | `getCenterPoint()` | Returns the geographic center | | `equals(_:)` | Compares with another area | | `isDefault()` | Checks if the area is in its default (unmodified) state | | `isEmpty()` | Checks if the area is empty | | `reset()` | Resets to default | ##### Rectangle geographic area[​](#rectangle-geographic-area "Direct link to Rectangle geographic area") `RectangleGeographicAreaObject` represents an axis-aligned bounding rectangle. You can create one from a center point and radii, or by setting the top-left and bottom-right corners explicitly. Create from a center point with horizontal and vertical radii in meters: ```swift let center = CoordinatesObject.coordinates(withLatitude: 48.858844, longitude: 2.294351) let rect = RectangleGeographicAreaObject(location: center, horizontalRadius: 500.0, verticalRadius: 300.0) ``` Alternatively, set corners directly after construction: ```swift let topLeft = CoordinatesObject.coordinates(withLatitude: 44.93343, longitude: 25.09946) let bottomRight = CoordinatesObject.coordinates(withLatitude: 44.93324, longitude: 25.09987) let rect = RectangleGeographicAreaObject(location: topLeft, horizontalRadius: 0, verticalRadius: 0) rect.setTopLeft(topLeft) rect.setBottomRight(bottomRight) ``` danger A valid `RectangleGeographicAreaObject` requires the latitude of `topLeft` to be greater than the latitude of `bottomRight`, and the longitude of `topLeft` to be less than the longitude of `bottomRight`. Additional operations: * `intersects(_:)` and `contains(_:)` for rectangle overlap/containment checks * `makeIntersection(_:)` and `makeUnion(_:)` to produce new rectangles from geometry composition * `getBoundingBox()` to retrieve the normalized enclosing rectangle ##### Circle geographic area[​](#circle-geographic-area "Direct link to Circle geographic area") `CircleGeographicAreaObject` represents a radial area defined by a center point and a radius in meters. ```swift let center = CoordinatesObject.coordinates(withLatitude: 40.748817, longitude: -73.985428) let circle = CircleGeographicAreaObject(center: center, radius: 500) ``` You can also update the center and radius after creation: ```swift circle.setCenter(newCenter, radius: 1000) ``` ##### Polygon geographic area[​](#polygon-geographic-area "Direct link to Polygon geographic area") `PolygonGeographicAreaObject` represents a custom area defined by an arbitrary list of coordinates. It is suited for complex or irregular boundaries. ```swift let coordinates = [ CoordinatesObject.coordinates(withLatitude: 10, longitude: 0), CoordinatesObject.coordinates(withLatitude: 10, longitude: 10), CoordinatesObject.coordinates(withLatitude: 0, longitude: 10), CoordinatesObject.coordinates(withLatitude: 0, longitude: 0), ] let polygon = PolygonGeographicAreaObject(coordinates: coordinates) ``` danger A valid `PolygonGeographicAreaObject` requires at least 3 coordinates. Avoid overlapping or self-intersecting edges. *** These entities are used throughout landmarks, overlays, routing, traffic, and navigation instructions. --- ### Images Last updated: March 18, 2026 | 3 minutes read

The Maps SDK for iOS uses `ImageObject` for plain SDK images and UIKit-native `UIImage` rendering for display. Some APIs expose `ImageObject` so you can control rendering yourself, while navigation-oriented APIs render `UIImage` directly. *** #### Understand the image model[​](#understand-the-image-model "Direct link to Understand the image model") The SDK exposes three practical image paths: * `ImageObject` for plain SDK images that you render on demand * `AbstractGeometryImageObject` for schematic turn geometry with custom colors * Direct `UIImage` rendering from APIs such as `NavigationInstructionObject` and `PositionObject` Common sources of `ImageObject` include `ImageDatabaseObject`, `LandmarkObject`, `OverlayCategoryObject`, `MapDetailsContext`, and weather condition objects. info You can render `ImageObject` directly to `UIImage`, or use APIs that already return `UIImage`. *** #### ImageObject structure[​](#imageobject-structure "Direct link to ImageObject structure") `ImageObject` is the base representation for SDK images. | Method | Returns | Description | | -------------------------------------- | ------------- | ----------------------------------------------------------------------------------- | | `init(dataBuffer:format:)` | `ImageObject` | Create an image object from encoded image data | | `getType()` | `ImageType` | Logical image kind (`Base`, `AbstractGeometry`, `RoadInfo`, `Signpost`, `LaneInfo`) | | `getUid()` | `UInt32` | Stable identifier for redraw optimization | | `isValid()` | `Bool` | Checks whether the image can be rendered | | `isScalable()` | `Bool` | Whether the underlying representation is scalable | | `getSize()` | `CGSize` | Recommended/native size | | `getAspectRatio()` | `CGSize` | Aspect ratio expressed as width/height proportions | | `renderImageWithSize(_:)` | `UIImage?` | Render at an explicit size | | `renderImageWithSize(_:scale:ppi:)` | `UIImage?` | Render with explicit scale and PPI | | `renderAspectRatioImage(_:)` | `UIImage?` | Render using a target height while preserving aspect ratio | | `renderAspectRatioImage(_:scale:ppi:)` | `UIImage?` | Aspect-ratio render with explicit scale and PPI | `ImageFormat` supports `Bmp`, `Jpeg`, `Gif`, `Png`, `Tga`, `WebP`, and `AutoDetect`. *** #### Render Images[​](#render-images "Direct link to Render Images") Use `renderImageWithSize(_:)` when you need an exact size. Use `renderAspectRatioImage(_:)` when icon proportions must stay stable. The following examples demonstrate this from a variety of image sources. The same workflow applies to all `ImageObject` instances, regardless of origin. *** #### Get and render image objects from the SDK[​](#get-and-render-image-objects-from-the-sdk "Direct link to Get and render image objects from the SDK") Many SDK objects already provide `ImageObject`, so you can render them with the same workflow. Common examples: * `ImageDatabaseObject.getImageById(_:)` * `ImageDatabaseObject.getPinStartImage()` and `getPinStopImage()` * `LandmarkObject.getImage()` and `getExtraImage()` * `OverlayCategoryObject.getImage()` ```swift let imageDatabase = ImageDatabaseObject() if let startPin = imageDatabase.getPinStartImage() { pinImageView.image = startPin.renderAspectRatioImage(40) } if let landmarkImage = landmark.getImage() { landmarkImageView.image = landmarkImage.renderImageWithSize( CGSize(width: 48, height: 48) ) } if let categoryImage = overlayCategory.getImage() { categoryImageView.image = categoryImage.renderAspectRatioImage(28) } ``` If you only need a ready-to-display landmark icon, `LandmarkObject` also provides `getLandmarkImage(_:)`, which returns a cached `UIImage` directly. *** #### Render abstract geometry images[​](#render-abstract-geometry-images "Direct link to Render abstract geometry images") Use `AbstractGeometryImageObject` when you want a schematic turn image with your own colors. The most common source is `TurnDetailsObject.getAbstractGeometryImage()`. ```swift if let turnDetails = instruction.getNextTurnDetails(), let geometryImage = turnDetails.getAbstractGeometryImage() { let image = geometryImage.getImage( CGSize(width: 72, height: 72), colorActiveInner: .white, colorActiveOuter: .systemBlue, colorInactiveInner: UIColor.white.withAlphaComponent(0.25), colorInactiveOuter: .lightGray ) nextTurnImageView.image = image } ``` --- ### Landmarks Last updated: March 25, 2026 | 11 minutes read

A **landmark** is a rich point-of-interest entity represented by `LandmarkObject`. It combines location, metadata, media, categories, and store membership. Landmarks represent significant, categorized locations with rich metadata and structured context about a place. #### Landmark structure[​](#landmark-structure "Direct link to Landmark structure") ##### Identity and store linkage[​](#identity-and-store-linkage "Direct link to Identity and store linkage") | Method | Description | | ------------------------------ | ------------------------------------------------- | | `getLandmarkIdentifier()` | Returns the landmark ID; `-1` when not in a store | | `getLandmarkStoreIdentifier()` | Returns the parent store ID | | `detachFromStore()` | Removes store linkage for local-only handling | ##### Geographic details[​](#geographic-details "Direct link to Geographic details") A landmark's position is defined by its centroid coordinates. The contour area provides the full boundary when available. | Method | Description | | ------------------------------- | -------------------------------------------------------- | | `getCoordinates()` | Returns the centroid `CoordinatesObject` | | `setCoordinates(_:)` | Sets the centroid coordinates | | `getContourGeograficArea()` | Returns contour bounding area (`nil` when not available) | | `isContourGeograficAreaEmpty()` | Checks if the contour area is absent | Calculate the distance between two landmarks using their coordinates: ```swift let distanceInMeters = landmarkA.getCoordinates().getDistance(landmarkB.getCoordinates()) ``` See the [Base entities](/docs/ios/guides/core/base-entities.md) guide for more on `CoordinatesObject`. ##### Descriptive information[​](#descriptive-information "Direct link to Descriptive information") Names adapt to the SDK language setting for localization. | Method | Description | | --------------------------------------------------------- | ----------------------------------- | | `getLandmarkName()` / `setLandmarkName(_:)` | Landmark name | | `getLandmarkDescription()` / `setLandmarkDescription(_:)` | Landmark description | | `getAuthor()` / `setAuthor(_:)` | Author string | | `getProviderId()` / `setProviderId(_:)` | Provider identifier | | `getTimeStamp()` / `setTimeStamp(_:)` | Insertion or modification timestamp | ##### Categories[​](#categories "Direct link to Categories") A landmark can belong to multiple categories simultaneously. | Method | Description | | ----------------- | ------------------------------------------------------- | | `getCategories()` | Returns `[LandmarkCategoryObject]`, empty array if none | ##### Media and images[​](#media-and-images "Direct link to Media and images") | Method | Description | | --------------------------------------- | ----------------------------------------------------- | | `getImage()` / `setImage(_:)` | Primary `ImageObject` | | `getExtraImage()` / `setExtraImage(_:)` | Secondary `ImageObject` | | `setImageData(_:format:)` | Set image from raw `Data` and `ImageFormat` | | `getLandmarkImage(_:)` | Renders and returns a `UIImage` at the given `CGSize` | | `getLandmarkImage(_:scale:ppi:)` | Renders `UIImage` with explicit screen scale and PPI | | `setLandmarkImage(_:)` | Sets the landmark image from a `UIImage` (PNG format) | info `getLandmarkImage(_:)` caches the rendered result after the first call. Call `resetCacheImage` when your landmark image source changes. ##### Extra info and contact data[​](#extra-info-and-contact-data "Direct link to Extra info and contact data") | Method | Description | | ----------------------------------------- | -------------------------------------------------------- | | `getExtraInfo()` / `setExtraInfo(_:)` | Extra metadata as `[String]` | | `findExtraInfo(_:)` | Looks up a value in extra info by key string | | `getContactInfo()` / `setContactInfo(_:)` | `ContactInfoObject` with phone numbers, emails, and URLs | danger If you retrieve a `ContactInfoObject` or extra info from a landmark, modify it, and want to persist the change, you must call the corresponding setter to update the landmark's stored value. danger The extra info array also stores data for geographic area, contour geographic area, and Wikipedia information. Modifying `extraInfo` without preserving existing entries may cause data loss. ##### Address fields[​](#address-fields "Direct link to Address fields") Use `getAddressFieldNameWithType(_:)` with `AddressSearchFieldType` to retrieve structured address components: | Field | Description | | ---------------------------------------------------------------------- | ---------------------------------------------------- | | `.streetName` | Street or road name | | `.streetNumber` | Building or house number | | `.postalCode` | ZIP or postal code | | `.settlement` | Settlement name | | `.city` | City or town name | | `.county` | County (administrative level between state and city) | | `.district` | Municipal district | | `.state` | State or province | | `.stateCode` | State abbreviation | | `.country` | Country name | | `.countryCode` | ISO 3166-1 alpha-3 country code | | `.extension` | Address extension, e.g. flat number | | `.buildingFloor` / `.buildingName` / `.buildingRoom` / `.buildingZone` | Building-level fields | | `.crossing1` / `.crossing2` | Intersecting street names | | `.segmentName` | Road segment name | ##### Overlay and geofence linkage[​](#overlay-and-geofence-linkage "Direct link to Overlay and geofence linkage") | Method | Description | | ---------------------------- | -------------------------------------------------------------------- | | `getOverlayItem()` | Returns the associated `OverlayItemObject` if from an overlay search | | `getGeofenceProximityArea()` | Returns `GeofenceProximityAreaObject` if from a geofence search | #### Create landmarks[​](#create-landmarks "Direct link to Create landmarks") Create a landmark in memory using the factory method, then set optional metadata: ```swift let location = CoordinatesObject.coordinates(withLatitude: 48.858844, longitude: 2.294351) let landmark = LandmarkObject.landmark(withName: "Eiffel Tower", location: location) landmark.setLandmarkDescription("Iconic iron lattice tower in Paris.") landmark.setAuthor("My App") ``` danger Creating a landmark does not display it automatically. Add it to a map workflow (highlights, landmark store, routing, etc.) to use it. #### Landmark categories[​](#landmark-categories "Direct link to Landmark categories") Categories are represented by `LandmarkCategoryObject`. A landmark may belong to multiple categories simultaneously. ##### Category structure[​](#category-structure "Direct link to Category structure") | Method | Description | | ------------------------------ | ------------------------------------------------------------- | | `init(name:)` | Creates a new category with the given name | | `getIdentifier()` | Returns the category ID | | `getName()` / `setName(_:)` | Gets or sets the category name | | `getImage(_:)` | Renders the category image as `UIImage` at the given `CGSize` | | `getImage(_:scale:ppi:)` | Renders `UIImage` with explicit scale and PPI | | `setImage(_:)` | Sets the category image from a `UIImage` | | `getLandmarkStoreIdentifier()` | Returns the owning store ID (`0` if not in a store) | ##### Predefined generic categories[​](#predefined-generic-categories "Direct link to Predefined generic categories") Access predefined map categories via `GenericCategoriesContext` using `GenericCategoryType`: | `GenericCategoryType` | ID | Description | | ------------------------------- | ---- | ------------------------------------- | | `.gasStation` | 1000 | Fuel stations | | `.parking` | 1001 | Parking lots and garages | | `.foodAndDrink` | 1002 | Restaurants, cafes, bars | | `.accommodation` | 1003 | Hotels, hostels, lodging | | `.medicalServices` | 1004 | Hospitals, clinics, pharmacies | | `.shopping` | 1005 | Retail stores and markets | | `.carServices` | 1006 | Auto repair and vehicle services | | `.publicTransport` | 1007 | Transit stops and stations | | `.wikipedia` | 1008 | POIs with Wikipedia information | | `.education` | 1009 | Schools and universities | | `.entertainment` | 1010 | Cinemas, theaters, amusement parks | | `.publicServices` | 1011 | Post offices and civic buildings | | `.geographicalArea` | 1012 | Geographic zones and regions | | `.business` | 1013 | Offices and corporate buildings | | `.sightseeing` | 1014 | Tourist attractions | | `.religiousPlaces` | 1015 | Churches, mosques, temples | | `.roadside` | 1016 | Roadside amenities and rest areas | | `.sports` | 1017 | Stadiums and fitness facilities | | `.uncategorised` | 1018 | Landmarks with no specific category | | `.hydrants` | 1019 | Water hydrant locations | | `.emergencyServicesSupport` | 1020 | Emergency service facilities | | `.civilEmergencyInfrastructure` | 1021 | Emergency preparedness infrastructure | | `.chargingStation` | 1022 | EV charging stations | | `.bicycleChargingStation` | 1023 | E-bike charging stations | | `.bicycleParking` | 1024 | Bicycle parking areas | Access generic categories using `GenericCategoriesContext`: ```swift let ctx = GenericCategoriesContext() // All predefined categories let all = ctx.getCategories() // A specific category by type if let parking = ctx.getCategory(.parking) { let name = parking.getName() let icon = parking.getImage(CGSize(width: 32, height: 32)) } // POI subcategories for a generic category let parkingPois = ctx.getPoiCategories(Int32(GenericCategoryType.parking.rawValue)) if let parkingSubcategory = parkingPois.first { // get parent POI generic category for a subcategory let genericCategory = ctx.getGenericCategory(parkingSubcategory.getIdentifier()) } ``` ##### Category hierarchy[​](#category-hierarchy "Direct link to Category hierarchy") Each generic category can contain multiple POI subcategories. For example, *Parking* contains *Park and Ride*, *Parking Garage*, *Parking Lot*, *RV Park*, *Truck Parking*, *Truck Stop*, and *Parking meter*. Use `getPoiCategories(_:)` to retrieve subcategories, and `getGenericCategory(_:)` to find the parent of a POI subcategory. danger **Important distinction:** * `getCategory(_:)` — Returns a `LandmarkCategoryObject` by `GenericCategoryType` * `getGenericCategory(_:)` — Returns the parent generic category of a POI subcategory #### Landmark stores[​](#landmark-stores "Direct link to Landmark stores") Landmark stores are persistent collections of landmarks backed by SQLite on the device. Each store has a unique `name` and integer `id`. danger Landmark coordinate precision is limited by floating-point representation, which may produce positional inaccuracies of a few centimeters to meters. ##### Manage landmark stores[​](#manage-landmark-stores "Direct link to Manage landmark stores") `LandmarkStoreContextService` manages store registration, retrieval, and removal. ###### Register a store[​](#register-a-store "Direct link to Register a store") ```swift let service = LandmarkStoreContextService() let storeId = service.registerLandmarkStoreContext("MyLandmarkStore", path: "/path/to/store.db") ``` The name must be unique; if a store with that name already exists at the path, the existing store ID is returned. danger Stores persist across app sessions. Registering a name that already exists returns that existing store, which may already contain landmarks and categories. ###### Get a store by ID or name[​](#get-a-store-by-id-or-name "Direct link to Get a store by ID or name") ```swift let storeById = service.getLandmarkStoreContext(withIdentifier: storeId) let storeByName = service.getLandmarkStoreContext(withName: "MyLandmarkStore") ``` ###### Get all stores[​](#get-all-stores "Direct link to Get all stores") ```swift let allStores = service.getStores() ``` ###### Remove a store[​](#remove-a-store "Direct link to Remove a store") ```swift let result = service.removeLandmarkStoreContext(storeId) ``` ###### Predefined map stores[​](#predefined-map-stores "Direct link to Predefined map stores") The SDK provides three built-in read-only stores: ```swift let poisStoreId = service.getMapPoisLandmarkStoreId() let addressStoreId = service.getMapAddressLandmarkStoreId() let citiesStoreId = service.getMapCitiesLandmarkStoreId() ``` Use these IDs to determine whether a landmark originated from map data. danger Do not modify predefined stores. They are used for landmark category visibility, origin checks, and search/alarm filtering. ##### Store operations[​](#store-operations "Direct link to Store operations") `LandmarkStoreContext` provides the following operations: | Method | Description | | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `getId()` | Returns the store ID | | `getName()` | Returns the store name | | `getType()` | Returns `LandmarkStoreType` (`.none`, `.default`, `.mapAddress`, `.mapPoi`, `.mapCity`, `.mapHighwayExit`, `.mapCountry`) | | `getFilePath()` | Returns the on-disk SQLite file path | | `addCategory(_:)` | Adds a new category; must have a name | | `updateCategory(_:)` | Updates an existing category (must belong to this store) | | `removeCategory(_:)` | Removes category; its landmarks become uncategorized | | `removeCategoryWithAllContent(_:)` | Removes category and all its landmarks | | `getCategoryById(_:)` | Returns `LandmarkCategoryObject?` by ID | | `getCategories()` | Returns all categories | | `addLandmark(_:)` | Adds landmark as uncategorized | | `addLandmark(_:toCategoryId:)` | Adds landmark to a specific category | | `updateLandmark(_:)` | Updates landmark info (does not affect its category) | | `removeLandmark(_:)` | Removes a landmark | | `removeLandmark(_:fromCategoryId:)` | Removes landmark from a category | | `removeAllLandmarks()` | Removes all landmarks in the store | | `removeAllLandmarksFromCategoryId(_:)` | Removes all landmarks from a category | | `getLandmark(_:)` | Returns `LandmarkObject?` by landmark ID | | `getLandmarkCount()` | Returns total landmark count | | `getLandmarks()` | Returns all landmarks | | `getLandmarkCount(_:)` | Returns landmark count for a category ID | | `getLandmarks(_:)` | Returns landmarks in a category | | `getLandmarksWithRectangleGeographicArea(_:)` | Spatial query by `RectangleGeographicAreaObject` | | `getLandmarksWithGeographicArea(_:)` | Spatial query by any `GeographicAreaObject` | | `importLandmarks(_:format:completionHandler:)` | Async import from file | | `importLandmarks(_:format:progressHandler:completionHandler:)` | Async import with progress reporting | | `importLandmarks(_:format:categoryId:progressHandler:completionHandler:)` | Async import directly into a category | | `cancelImportLandmarks()` | Cancels an in-progress import | ##### Import landmarks[​](#import-landmarks "Direct link to Import landmarks") Import from a local file in KML or GeoJSON format: ```swift let store = service.getLandmarkStoreContext(withName: "MyLandmarkStore") store.importLandmarks( "/path/to/landmarks.kml", format: .kml, progressHandler: { progress in print("Import progress: \(Int(progress * 100))%") }, completionHandler: { errorCode in if errorCode == .success { // Landmarks added as uncategorized } } ) ``` To import into a specific category: ```swift store.importLandmarks( "/path/to/landmarks.geojson", format: .geoJson, categoryId: myCategory.getIdentifier(), progressHandler: { _ in }, completionHandler: { errorCode in } ) ``` warning The `categoryId` must already exist in the target store. Call `addCategory(_:)` first if needed. #### Landmark browse sessions[​](#landmark-browse-sessions "Direct link to Landmark browse sessions") Use `LandmarkBrowseSessionContext` for efficient paged browsing of large stores. ##### Create a browse session[​](#create-a-browse-session "Direct link to Create a browse session") ```swift let store = service.getLandmarkStoreContext(withIdentifier: storeId) let settings = LandmarkBrowseSessionSettingsObject() settings.orderBy = .name settings.descendingOrder = false settings.nameFilter = "cafe" let session = LandmarkBrowseSessionContext(storeId: store.getId(), settings: settings) ``` danger Only landmarks present in the store at the time of session creation are included in the session. ##### Browse session settings[​](#browse-session-settings "Direct link to Browse session settings") `LandmarkBrowseSessionSettingsObject` uses the following properties: | Property | Type | Default | Description | | ------------------ | --------------------- | -------------------------------- | ----------------------------------------------------------------------- | | `descendingOrder` | `Bool` | `false` | Sort direction; `false` = ascending | | `orderBy` | `LandmarkObjectOrder` | `.name` | Sort criteria: `.name`, `.date`, or `.distance` | | `nameFilter` | `String?` | `nil` | Filters to landmarks whose name contains this substring | | `categoryIdFilter` | `NSNumber?` (int) | `-2` (`CategoryIdFilterInvalid`) | Filter by category ID; `-2` = all categories, `-1` = uncategorized only | | `coordinates` | `CoordinatesObject?` | `nil` | Reference point used when `orderBy == .distance` | ##### Browse session operations[​](#browse-session-operations "Direct link to Browse session operations") | Method | Description | | ------------------------- | ----------------------------------------------------------- | | `getId()` | Returns the session ID | | `getLandmarkStoreId()` | Returns the owning store ID | | `getLandmarkCount()` | Total number of landmarks in the session | | `getLandmarksFrom(_:to:)` | Returns landmarks between the given indices (exclusive end) | | `getLandmarkPos(_:)` | Returns 0-based index of a landmark by its ID | | `getSettings()` | Returns the current session settings | Retrieve a paginated page of landmarks: ```swift let total = session.getLandmarkCount() let page = session.getLandmarksFrom(0, to: min(20, total)) for landmark in page { print(landmark.getLandmarkName()) } ``` #### Common uses[​](#common-uses "Direct link to Common uses") * POI display and details panels * Search results and route waypoints * Category-filtered map content * Persistent user-defined locations *** Landmarks are the SDK's most complete place model and are the recommended entity when you need searchable, routable, and metadata-rich map objects. --- ### Landmarks vs Markers vs Overlays Last updated: March 18, 2026 | 1 minute read

When building map features, choose the entity that matches your data lifecycle and interaction model. | Characteristic | Landmarks | Markers | Overlays | | ----------------------- | ------------------------------------------ | ---------------------------------------------- | -------------------------------------------------- | | Primary class | `LandmarkObject` | `MarkerObject` + `MarkerCollectionObject` | `OverlayInfoObject` + `OverlayItemObject` | | On map by default | Yes, for enabled stores/categories | No, must be added explicitly | Yes, when present in style and enabled | | Searchable | Yes | No | Yes | | Route waypoint friendly | Yes | No | Indirect (convert to landmark/coordinates) | | Metadata richness | High (address, contact, categories, media) | Low (geometry-focused) | High (preview data, categories, URLs) | | Creation model | Local creation + persistent stores | Local creation | Style/server dataset driven | | Offline behavior | Yes | Yes | Supported when overlay offline data is available | | Best use | POIs, user places, search/routing entities | Temporary graphics, shapes, visual annotations | Shared thematic datasets (safety, transit, social) | #### Quick selection guide[​](#quick-selection-guide "Direct link to Quick selection guide") * Choose **landmarks** when you need searchable, categorizable places with full metadata. * Choose **markers** when you need fast, fully-custom visual geometry on the map. * Choose **overlays** when data is maintained as a shared dataset and controlled by style/service configuration. --- ### Markers Last updated: April 9, 2026 | 7 minutes read

A **marker** is a visual geometry represented by `MarkerObject`. Markers are ideal for temporary annotations, shapes, user-defined graphics, and lightweight map overlays that are local to your app session. Markers are geometry-first objects. They expose coordinates, parts, and a name, but they do not provide the rich searchable metadata of `LandmarkObject`. #### Create markers[​](#create-markers "Direct link to Create markers") `MarkerObject` supports multiple creation patterns: * `initWithCoordinates:` for point/polyline/polygon coordinate lists * `initWithCircleCenter:radius:` for circular geometry * `initWithRectangleShapeCenter:horizRadius:vertRadius:` for axis-aligned rectangle from center/radii * `initWithRectangleFirstCorner:secondCorner:` for rectangle from corners * `initWithGeographicArea:` for area-driven creation ##### Create from coordinates[​](#create-from-coordinates "Direct link to Create from coordinates") Create a marker from an array of coordinates: ```swift let coordinates = [ CoordinatesObject.coordinates(withLatitude: 48.858844, longitude: 2.294351), CoordinatesObject.coordinates(withLatitude: 48.859200, longitude: 2.295000), ] let marker = MarkerObject(coordinates: coordinates) marker.setName("Sample marker") ``` ##### Create a circular marker[​](#create-a-circular-marker "Direct link to Create a circular marker") ```swift let center = CoordinatesObject.coordinates(withLatitude: 40.748817, longitude: -73.985428) let circleMarker = MarkerObject(circleCenter: center, radius: 500) ``` ##### Create a rectangle marker from center and radii[​](#create-a-rectangle-marker-from-center-and-radii "Direct link to Create a rectangle marker from center and radii") ```swift let center = CoordinatesObject.coordinates(withLatitude: 44.4378, longitude: 26.0969) let rectMarker = MarkerObject(rectangleShapeCenter: center, horizRadius: 400, vertRadius: 250) ``` ##### Create a rectangle marker from corners[​](#create-a-rectangle-marker-from-corners "Direct link to Create a rectangle marker from corners") ```swift let topLeft = CoordinatesObject.coordinates(withLatitude: 44.93343, longitude: 25.09946) let bottomRight = CoordinatesObject.coordinates(withLatitude: 44.93324, longitude: 25.09987) let marker = MarkerObject(rectangleFirstCorner: topLeft, secondCorner: bottomRight) ``` ##### Create from a geographic area[​](#create-from-a-geographic-area "Direct link to Create from a geographic area") ```swift let area = CircleGeographicAreaObject(center: center, radius: 500) let marker = MarkerObject(geographicArea: area) ``` danger Markers are not displayed automatically when instantiated. Add them to a `MarkerCollectionObject` that is rendered by your map workflow. #### Marker structure[​](#marker-structure "Direct link to Marker structure") Markers can contain one or more **parts**. Without an explicit part index, methods operate on the default part (`0`). Each part is interpreted according to the `MarkerCollectionObject` type that renders it: * **Point collection**: each part is a group of points * **Polyline collection**: each part is a polyline * **Polygon/area collection**: each part is a polygon ![](/docs/ios/assets/images/ios_core_markers1-7f1c50dd4738b7e1f306c287cff3630a.png) **Point Markers** ![](/docs/ios/assets/images/ios_core_markers2-42ec431370779e1398f7651a688eb74d.png) **Line Markers** ![](/docs/ios/assets/images/ios_core_markers3-4c047cd50ca6c4bd0cc5e9fd73ed7ffa.png) **Polygon Markers** ##### Work with parts[​](#work-with-parts "Direct link to Work with parts") If you add a coordinate using `part == getPartCount()`, the SDK automatically creates a new part. ```swift let marker = MarkerObject(coordinates: [ CoordinatesObject.coordinates(withLatitude: 48.858844, longitude: 2.294351) ]) // Append to the first part. marker.add(CoordinatesObject.coordinates(withLatitude: 48.859000, longitude: 2.295000)) // Create a second part automatically. marker.add( CoordinatesObject.coordinates(withLatitude: 48.860000, longitude: 2.296000), index: -1, part: Int(marker.getPartCount()) ) ``` ##### Marker operations[​](#marker-operations "Direct link to Marker operations") | Method | Description | | --------------------------- | -------------------------------------------------- | | `getId()` | Returns the marker unique ID | | `getName()` / `setName(_:)` | Gets or sets the marker name | | `getPartCount()` | Returns the number of parts | | `getCoordinates()` | Returns coordinates of the first/default part | | `getCoordinates(_:)` | Returns coordinates for the specified part | | `setCoordinates(_:)` | Replaces coordinates of the first part | | `setCoordinates(_:part:)` | Replaces coordinates of the specified part | | `add(_:)` | Appends a coordinate to the default part | | `add(_:index:part:)` | Inserts or appends a coordinate in a specific part | | `deleteFromIndex(_:part:)` | Removes a coordinate from a part | | `update(_:index:part:)` | Replaces a coordinate at a given index | | `deletePart(_:)` | Removes an entire part | | `getArea()` | Returns the bounding area of the first part | | `getPartArea(_:)` | Returns the bounding area of the specified part | #### Marker collections[​](#marker-collections "Direct link to Marker collections") `MarkerCollectionObject` groups markers of the same visual type: | Type | Description | | ------------------------------ | ------------------------------------------------ | | `MarkerCollectionTypePoint` | Multi-point markers | | `MarkerCollectionTypePolyline` | Polyline markers | | `MarkerCollectionTypePolygon` | Polygon markers | | `MarkerCollectionTypeArea` | Area markers rendered above the map area's stack | ##### Create a marker collection[​](#create-a-marker-collection "Direct link to Create a marker collection") Create a collection with a name and type, then add markers to it: ```swift let collection = MarkerCollectionObject(name: "myPoints", type: .point) let marker = MarkerObject(coordinates: [ CoordinatesObject.coordinates(withLatitude: 48.858844, longitude: 2.294351) ]) collection.addMarker(marker) ``` ##### Collection operations[​](#collection-operations "Direct link to Collection operations") | Method | Description | | ------------------------- | ---------------------------------------------------------- | | `getId()` | Returns the collection unique ID | | `getType()` | Returns the `MarkerCollectionType` | | `getName()` | Returns the collection name | | `getSize()` | Returns the number of markers in the collection | | `getMarkerAt(_:)` | Returns the marker at an index | | `getMarkerById(_:)` | Returns the marker with a specific marker ID | | `indexOf(_:)` | Returns the index of a marker | | `addMarker(_:)` | Appends a marker to the collection | | `addMarker(_:atIndex:)` | Inserts a marker at a given index | | `deleteMarkerAtIndex(_:)` | Removes a marker by index | | `clear()` | Removes all markers | | `getArea()` | Returns the geographic area enclosing the whole collection | ##### Typical usage[​](#typical-usage "Direct link to Typical usage") Collections are the unit you style and attach to your map workflow. Every collection holds markers of the same render type, which keeps display configuration consistent. #### Customize rendering[​](#customize-rendering "Direct link to Customize rendering") `MarkerCollectionObject` exposes collection-level styling controls: ##### Style and render settings[​](#style-and-render-settings "Direct link to Style and render settings") | Method | Description | | ----------------------------------------------------------------------- | ------------------------------------------ | | `setInnerColor(_:)` / `getInnerColor()` | Polyline inner color | | `setOuterColor(_:)` / `getOuterColor()` | Polyline outer color | | `setFillColor(_:)` / `getFillColor()` | Polygon fill color | | `setInnerSize(_:)` / `getInnerSize()` | Polyline inner width in millimeters | | `setOuterSize(_:)` / `getOuterSize()` | Polyline outer width in millimeters | | `setPointImage(_:)` / `getPointImage()` | Point marker `UIImage` | | `setImage(_:)` / `getImage()` | Collection `ImageObject` | | `setImageSize(_:)` / `getImageSize()` | Image size in millimeters | | `setLabelGroupTextSize(_:)` / `getLabelGroupTextSize()` | Group label text size | | `setLabelGroupTextColor(_:)` / `getLabelGroupTextColor()` | Group label text color | | `setLabelingMode(_:)` / `getLabelingMode()` | Labeling mode bitset | | `setPointsGroupingZoomLevel(_:)` / `getPointsGroupingZoomLevel()` | Zoom level where point grouping begins | | `setMinVisibilityZoomLevel(_:)` / `getMinVisibilityZoomLevel()` | Minimum zoom where collection is visible | | `setMaxVisibilityZoomLevel(_:)` / `getMaxVisibilityZoomLevel()` | Maximum zoom where collection is visible | | `setPolylineDirArrow(_:)` / `getPolylineDirArrow()` | Enables/disables polyline direction arrows | | `setPolylineDirArrowInnerColor(_:)` / `getPolylineDirArrowInnerColor()` | Arrow inner color | | `setPolylineDirArrowOuterColor(_:)` / `getPolylineDirArrowOuterColor()` | Arrow outer color | ##### Style a point collection[​](#style-a-point-collection "Direct link to Style a point collection") ```swift let collection = MarkerCollectionObject(name: "poiMarkers", type: .point) if let icon = UIImage(systemName: "mappin.circle.fill") { collection.setPointImage(icon) } collection.setImageSize(4.0) collection.setMinVisibilityZoomLevel(4) collection.setMaxVisibilityZoomLevel(20) collection.setPointsGroupingZoomLevel(10) ``` ##### Style a polyline collection[​](#style-a-polyline-collection "Direct link to Style a polyline collection") ```swift let polylineCollection = MarkerCollectionObject(name: "paths", type: .polyline) polylineCollection.setInnerColor(.systemBlue) polylineCollection.setOuterColor(.white) polylineCollection.setInnerSize(1.8) polylineCollection.setOuterSize(3.0) polylineCollection.setPolylineDirArrow(true) polylineCollection.setPolylineDirArrowInnerColor(.systemBlue) polylineCollection.setPolylineDirArrowOuterColor(.white) ``` ##### Labeling modes[​](#labeling-modes "Direct link to Labeling modes") `setLabelingMode(_:)` accepts a bitset of `MarkerLabelingMode` values. | Value | Description | | ------------------------------------ | ------------------------------ | | `MarkerLabelingModeNone` | No labels | | `MarkerLabelingModeItem` | Label each marker item | | `MarkerLabelingModeGroup` | Label grouped markers | | `MarkerLabelingModeItemCenter` | Center item label on image | | `MarkerLabelingModeGroupCenter` | Center group label on image | | `MarkerLabelingModeFitImage` | Fit label to image | | `MarkerLabelingModeIconBottomCenter` | Align icon bottom-center | | `MarkerLabelingModeTextAbove` | Place item label above icon | | `MarkerLabelingModeTextBellow` | Place item label below icon | | `MarkerLabelingModeGroupTopRight` | Place group label at top-right | Combine modes with bitwise OR: ```swift let mode = MarkerLabelingMode.item.rawValue | MarkerLabelingMode.textAbove.rawValue collection.setLabelingMode(Int(mode)) ``` #### Marker usage guidance[​](#marker-usage-guidance "Direct link to Marker usage guidance") * Use markers for visual-only map annotations * Use landmarks for searchable/routable POIs * Use overlays for server-backed shared datasets Markers are not searchable and are not the primary entity for route calculation. If you want to route to a marker-like place, create a `LandmarkObject` from representative coordinates and a display name. ```swift if let first = marker.getCoordinates().first { let landmark = LandmarkObject.landmark(withName: "Marker destination", location: first) // Use landmark in your search/routing workflow. } ``` Markers are lightweight and fully local to your app session/data model. --- ### Navigation Instructions Last updated: April 9, 2026 | 5 minutes read

The iOS SDK provides real-time navigation guidance through `NavigationInstructionObject`. It exposes current street and road details, speed-limit context, remaining time and distance, lane guidance, and upcoming maneuvers during active navigation or simulation. info Distinguish between `NavigationInstructionObject` and `RouteInstructionObject`. * `NavigationInstructionObject`: live, continuously updated turn-by-turn guidance during navigation or simulation * `RouteInstructionObject`: route-plan guidance available immediately after route calculation #### Get navigation instructions[​](#get-navigation-instructions "Direct link to Get navigation instructions") You do not instantiate navigation instructions directly. The SDK provides them only while navigation or simulation is active. Use `NavigationContext` in two common ways: * **Pull current state** with `getNavigationInstruction()` * **React to updates** through `NavigationContextDelegate` and `navigationInstructionUpdatedForRoute:updatedEvents:` warning Call `getNavigationInstruction()` only when navigation or simulation is running. ##### Read the current instruction[​](#read-the-current-instruction "Direct link to Read the current instruction") ```swift if let instruction = navigationContext.getNavigationInstruction() { print(instruction.getNextTurnInstruction()) if let remaining = instruction.getRemainingTravelTimeDistance() { print("Remaining distance: \(remaining.getTotalDistance()) m") print("Remaining time: \(remaining.getTotalTime()) s") } } ``` ##### React to updates from `NavigationContext`[​](#react-to-updates-from-navigationcontext "Direct link to react-to-updates-from-navigationcontext") ```swift final class NavigationObserver: NSObject, NavigationContextDelegate { func navigationContext( _ navigationContext: NavigationContext, navigationInstructionUpdatedForRoute route: RouteObject, updatedEvents events: Int32 ) { guard let instruction = navigationContext.getNavigationInstruction() else { return } print("Updated events bitmask: \(events)") print("Next turn: \(instruction.getNextTurnInstructionFormatted())") } } ``` ##### Navigation instruction update events[​](#navigation-instruction-update-events "Direct link to Navigation instruction update events") `updatedEvents` is a bitmask that can contain: | Value | Description | | ------------------------------------------------------ | -------------------------- | | `NavigationInstructionUpdateEventNextTurnUpdated` | Next maneuver data changed | | `NavigationInstructionUpdateEventNextTurnImageUpdated` | Next-turn image changed | | `NavigationInstructionUpdateEventLaneInfoUpdated` | Lane guidance changed | #### Understand the structure[​](#understand-the-structure "Direct link to Understand the structure") | Method | Return type | Description | | ------------------------------------------------ | --------------------- | -------------------------------------------------------- | | `getNavigationStatus()` | `NavigationStatus` | Current navigation or simulation state | | `getCurrentStreetName()` | `String` | Current street name | | `getCurrentStreetSpeedLimit()` | `Double` | Current street speed limit in m/s (`0` when unavailable) | | `getCurrentStreetSpeedLimitFormatted()` | `String` | Localized speed-limit text | | `getDriveSide()` | `DriveSideType` | Current drive side | | `hasNextTurnInfo()` | `Bool` | Whether next-turn information is available | | `getNextTurnInstruction()` | `String` | Next maneuver text | | `getNextTurnDetails()` | `TurnDetailsObject?` | Full next-turn details | | `hasNextNextTurnInfo()` | `Bool` | Whether next-next-turn information is available | | `getNextNextTurnInstruction()` | `String` | Next-next maneuver text | | `getNextNextTurnDetails()` | `TurnDetailsObject?` | Full next-next-turn details | | `getTimeDistanceToNextTurn()` | `TimeDistanceObject?` | Time and distance to next turn | | `getTimeDistanceToNextNextTurn()` | `TimeDistanceObject?` | Time and distance to next-next turn | | `getRemainingTravelTimeDistance()` | `TimeDistanceObject?` | Remaining route metrics | | `getRemainingTravelTimeDistanceToNextWaypoint()` | `TimeDistanceObject?` | Remaining metrics to next waypoint | | `getTraveledTimeDistance()` | `TimeDistanceObject?` | Traveled time and distance | | `getSegmentIndex()` / `getInstructionIndex()` | `Int32` | Current segment and instruction indices | | `getCurrentPosition()` | `PositionObject?` | Current position snapshot | #### Turn and maneuver details[​](#turn-and-maneuver-details "Direct link to Turn and maneuver details") ##### Next turn[​](#next-turn "Direct link to Next turn") Use the next-turn APIs to populate maneuver UI with text, images, and detailed turn metadata: ```swift if instruction.hasNextTurnInfo() { let nextTurnText = instruction.getNextTurnInstruction() let formattedText = instruction.getNextTurnInstructionFormatted() let turnDetails = instruction.getNextTurnDetails() let turnImage = instruction.getNextTurnImage(CGSize(width: 96, height: 96)) print(nextTurnText) print(formattedText) print(turnDetails?.getRoundaboutExitNumber() ?? -1) print(turnImage != nil) } ``` You can also render customized turn images with the overloads that accept active and inactive colors. ##### Next-next turn[​](#next-next-turn "Direct link to Next-next turn") Use the same pattern for the turn after the next one: ```swift if instruction.hasNextNextTurnInfo() { let nextNextTurnText = instruction.getNextNextTurnInstruction() let nextNextDetails = instruction.getNextNextTurnDetails() let nextNextImage = instruction.getNextNextTurnImage(CGSize(width: 96, height: 96)) print(nextNextTurnText) print(nextNextDetails?.getRoundaboutExitNumber() ?? -1) print(nextNextImage != nil) } ``` info `hasNextNextTurnInfo()` is typically `false` when the next instruction is the destination. #### Street and road information[​](#street-and-road-information "Direct link to Street and road information") Use these APIs to populate current, next, and next-next street panels. ```swift let currentStreetName = instruction.getCurrentStreetName() let currentCountryCode = instruction.getCurrentCountryCodeISO() let driveSide = instruction.getDriveSide() print(currentStreetName) print(currentCountryCode) print(driveSide) if instruction.hasCurrentRoadInfo() { for road in instruction.getCurrentRoadInformation() { print(road.getRoadName()) } } let currentRoadCodeImage = instruction.getCurrentRoadCodeImage(CGSize(width: 120, height: 36)) let nextStreetName = instruction.getNextStreetName() let nextCountryCode = instruction.getNextCountryCodeISO() if instruction.hasNextRoadInfo() { let nextRoadCodeImage = instruction.getNextRoadCodeImage(CGSize(width: 120, height: 36)) print(nextStreetName) print(nextCountryCode) print(nextRoadCodeImage != nil) } if instruction.hasNextNextRoadInfo() { let nextNextStreetName = instruction.getNextNextStreetName() let nextNextRoadCodeImage = instruction.getNextNextRoadCodeImage(CGSize(width: 120, height: 36)) print(nextNextStreetName) print(nextNextRoadCodeImage != nil) } ``` info Some roads may not have a street name. In that case, `getCurrentStreetName()` returns an empty string. #### Signpost guidance[​](#signpost-guidance "Direct link to Signpost guidance") When signpost data is available, you can show both text and a rendered sign image: ```swift if instruction.hasSignpostInfo() { let signpostText = instruction.getSignpostInstruction() let signpostDetails = instruction.getSignpostDetails() let signpostImage = instruction.getSignpostImage( CGSize(width: 320, height: 96), border: 2, roundCorners: true, rows: 2 ) print(signpostText) print(signpostDetails?.getItems().count ?? 0) print(signpostImage != nil) } ``` #### Get speed limit information[​](#get-speed-limit-information "Direct link to Get speed limit information") `NavigationInstructionObject` exposes the current street speed limit and formatted text. ```swift let speedLimitMS = instruction.getCurrentStreetSpeedLimit() let speedLimitText = instruction.getCurrentStreetSpeedLimitFormatted() if speedLimitMS == 0 { print("Speed limit unavailable") } else { print("Speed limit: \(speedLimitText)") } ``` #### Display lane guidance[​](#display-lane-guidance "Direct link to Display lane guidance") Lane guidance is rendered directly as `UIImage` in iOS: ```swift if instruction.hasLaneInfo() { let laneImage = instruction.getLaneImage( CGSize(width: 400, height: 120), backgroundColor: .black, activeColor: .white, inactiveColor: .lightGray ) print(laneImage != nil) } ``` ![](/docs/ios/assets/images/ios_core_lanes-5dd44558ff5fa590b9dd0afe7d5a9c79.png) **Example of lane guidance image** #### Return-to-route support[​](#return-to-route-support "Direct link to Return-to-route support") When navigation status is `NavigationStatusWaitingReturnToRoute`, use the return-to-route APIs to guide the user back to the navigation path. ```swift if instruction.getNavigationStatus() == .waitingReturnToRoute { let returnPosition = instruction.getReturnToRoutePosition() let returnImage = instruction.getReturnToRouteImage( CGSize(width: 96, height: 96), colorActiveInner: .systemBlue, colorActiveOuter: .white, colorInactiveInner: .lightGray, colorInactiveOuter: .darkGray ) print("\(returnPosition.latitude), \(returnPosition.longitude)") print(returnImage != nil) } ``` #### Change instruction language[​](#change-instruction-language "Direct link to Change instruction language") Navigation instruction texts follow the language configured in the SDK. The same language setting affects the strings returned by methods such as `getNextTurnInstruction()`, `getSignpostInstruction()`, and `getCurrentStreetSpeedLimitFormatted()`. *** Use `NavigationInstructionObject` as the live UI model for maneuver text, images, street and road info, signposts, lane guidance, and progress metrics during navigation. --- ### Overlays Last updated: April 17, 2026 | 9 minutes read

An **overlay** is an additional map layer with data stored on Magic Lane servers, accessible in both online and offline modes. Overlays can be default or user-defined, and they support category hierarchies and downloadable offline data. Core classes: * `OverlayInfoObject` — dataset metadata and category tree * `OverlayCategoryObject` — category node in the hierarchy * `OverlayItemObject` — single item on the map * `OverlayCollectionObject` — all available datasets * `OverlayServiceContext` — enable/disable and offline control danger Overlays require map content to be downloaded for offline use. Most overlay features require an active map view with a style that supports the overlay. See details in the offline content management guide. #### OverlayInfoObject[​](#overlayinfoobject "Direct link to OverlayInfoObject") `OverlayInfoObject` describes one overlay dataset. You retrieve instances from `OverlayCollectionObject`. | Method | Return type | Description | | ------------------- | ------------------------- | -------------------------------------- | | `getUid()` | `Int` | Unique overlay ID | | `getName()` | `String` | Overlay name | | `getImage(_:)` | `UIImage?` | Overlay icon at the given height | | `getCategories()` | `[OverlayCategoryObject]` | Root category list | | `getCategory(_:)` | `OverlayCategoryObject?` | Category by ID | | `hasCategories(_:)` | `Bool` | Checks if a category has subcategories | ##### Usage[​](#usage "Direct link to Usage") Use `OverlayInfoObject` to: * Access categories within an overlay * Get the overlay `uid` for filtering search results * Toggle overlay visibility on the map * Display overlay information in the UI ```swift let service = OverlayServiceContext() if let collection = service.getAvailableOverlays() { for overlay in collection.getOverlays() { print("\(overlay.getUid()): \(overlay.getName())") for category in overlay.getCategories() { print(" Category: \(category.getName())") } } } ``` #### OverlayCategoryObject[​](#overlaycategoryobject "Direct link to OverlayCategoryObject") `OverlayCategoryObject` represents a node in an overlay's category hierarchy. Categories can be nested to any depth. | Method | Return type | Description | | -------------------- | ------------------------- | ------------------------------ | | `getUid()` | `Int` | Category ID within the overlay | | `getOverlayUid()` | `Int` | Parent overlay ID | | `getName()` | `String` | Category name | | `getImage()` | `ImageObject?` | Category icon | | `getSubcategories()` | `[OverlayCategoryObject]` | Direct subcategories | | `hasSubcategories()` | `Bool` | Subcategory existence check | ##### Usage[​](#usage-1 "Direct link to Usage") Use the category `uid` to: * Filter search results * Filter and manage overlay items that trigger alerts in `AlarmContext` ##### Traverse the category tree[​](#traverse-the-category-tree "Direct link to Traverse the category tree") ```swift func printCategories(_ categories: [OverlayCategoryObject], depth: Int = 0) { let indent = String(repeating: " ", count: depth) for cat in categories { print("\(indent)\(cat.getName()) (uid: \(cat.getUid()))") if cat.hasSubcategories() { printCategories(cat.getSubcategories(), depth: depth + 1) } } } if let collection = service.getAvailableOverlays() { for overlay in collection.getOverlays() { printCategories(overlay.getCategories()) } } ``` #### OverlayItemObject[​](#overlayitemobject "Direct link to OverlayItemObject") `OverlayItemObject` represents a single item within an overlay. Items are returned by map cursor selection, search operations, or alarm callbacks. danger Do not confuse the `uid` of `OverlayInfoObject`, `OverlayCategoryObject`, and `OverlayItemObject` — each serves a distinct purpose. info `getCategoryId()` returns the **root** category ID, not necessarily the direct parent category. Use `getOverlayInfo()?.getCategory(_:)` to resolve the full category object. Tip Check if an `OverlayItemObject` belongs to an `OverlayInfoObject` using `getOverlayUid()`, or retrieve the full `OverlayInfoObject` via `getOverlayInfo()`. ##### Core item data[​](#core-item-data "Direct link to Core item data") | Method | Return type | Description | | ------------------ | -------------------- | ------------------------------------ | | `getUid()` | `Int` | Unique item ID within the overlay | | `getOverlayUid()` | `Int` | Parent overlay UID | | `getCategoryId()` | `Int` | Root category ID (`0` = no category) | | `getName()` | `String` | Item name | | `getCoordinates()` | `CoordinatesObject?` | Item position | | `getOverlayInfo()` | `OverlayInfoObject?` | Parent overlay dataset metadata | ##### Images[​](#images "Direct link to Images") | Method | Description | | ----------------------------------- | ------------------------------------------------------------- | | `getAspectRatioImage(_:)` | Renders item icon at given height (cached after first call) | | `getAspectRatioImage(_:scale:ppi:)` | Renders with explicit scale and PPI | | `getImage(_:)` | Renders item icon at given `CGSize` (cached after first call) | | `getImage(_:scale:ppi:)` | Renders with explicit scale and PPI | | `resetCacheImage()` | Clears the cached rendered image | ##### Preview data[​](#preview-data "Direct link to Preview data") | Method | Return type | Description | | ------------------------------------------------- | ----------- | ------------------------------------------------------------------------ | | `getPreviewUrl()` | `URL?` | Web URL for a browser-based detail view | | `getPreviewData(_:)` | `String` | Raw preview payload in the given `PreviewDataType` format (e.g. `.json`) | | `search(inPreviewDataSafetyCameraParameterType:)` | `NSValue?` | Typed lookup for safety camera overlay fields | | `search(inPreviewDataSocialReportParameterType:)` | `NSValue?` | Typed lookup for social report overlay fields | ##### Extended preview data (async)[​](#extended-preview-data-async "Direct link to Extended preview data (async)") Some predefined overlays expose dynamic data that must be downloaded before it is available. | Method | Description | | ------------------------------------------------------- | --------------------------------------------------------- | | `hasPreviewExtendedData()` | Checks if dynamic preview data is available for this item | | `getPreviewExtendedDataWithCompletionHandler(_:)` | Async fetch; populates a `SearchableParameterListObject` | | `cancelGetPreviewExtendedDataWithCompletionHandler(_:)` | Cancels an in-progress fetch | ```swift if item.hasPreviewExtendedData() { item.getPreviewExtendedData { parameterList in // parameterList is a SearchableParameterListObject print("Extended data received") } } ``` ##### Usage[​](#usage-2 "Direct link to Usage") Select `OverlayItemObject` instances from the map or receive them from `AlarmContext` on approach. Display overlay item fields and information in the UI. #### OverlayCollectionObject[​](#overlaycollectionobject "Direct link to OverlayCollectionObject") `OverlayCollectionObject` holds all overlay datasets available in the SDK. | Method | Return type | Description | | --------------------- | --------------------- | ------------------------------------ | | `size()` | `Int` | Number of available overlay datasets | | `getOverlayAt(_:)` | `OverlayInfoObject?` | Overlay at the given index | | `getOverlayByUid(_:)` | `OverlayInfoObject?` | Overlay by UID | | `getOverlays()` | `[OverlayInfoObject]` | All overlay datasets | #### Overlay service operations[​](#overlay-service-operations "Direct link to Overlay service operations") Use `OverlayServiceContext` to toggle overlay and category visibility. Enabling or disabling affects all registered consumers — map views, alarm services, and search. | Method | Return type | Description | | ------------------------------- | -------------------------- | --------------------------------------- | | `getAvailableOverlays()` | `OverlayCollectionObject?` | All available overlay datasets | | `enableOverlay(_:)` | `SDKErrorCode` | Enables the full overlay | | `enableOverlay(_:category:)` | `SDKErrorCode` | Enables a single category | | `disableOverlay(_:)` | `SDKErrorCode` | Disables the full overlay | | `disableOverlay(_:category:)` | `SDKErrorCode` | Disables a single category | | `isOverlayEnabled(_:)` | `Bool` | Checks if the overlay is active | | `isOverlayEnabled(_:category:)` | `Bool` | Checks if a specific category is active | ##### Enable and disable overlays[​](#enable-and-disable-overlays "Direct link to Enable and disable overlays") ```swift let service = OverlayServiceContext() let safetyId = Int32(CommonOverlayIdentifier.safety.rawValue) // Enable overlay let enableResult = service.enableOverlay(safetyId) // Disable overlay let disableResult = service.disableOverlay(safetyId) // Check if overlay is enabled let isEnabled = service.isOverlayEnabled(safetyId) ``` The `enableOverlay(_:category:)`, `disableOverlay(_:category:)`, and `isOverlayEnabled(_:category:)` variants also accept a category UID to target a specific category within the overlay. ##### Enable a specific category[​](#enable-a-specific-category "Direct link to Enable a specific category") ```swift let service = OverlayServiceContext() let overlayId = Int32(CommonOverlayIdentifier.publicTransport.rawValue) let categoryId = service.getAvailableOverlays()?.getOverlayByUid(overlayId)?.getCategories().first?.getUid() ?? 0 service.enableOverlay(overlayId, category: categoryId) ``` ##### Offline support[​](#offline-support "Direct link to Offline support") The offline data grabber automatically fetches overlay data for every road map content download. | Method | Return type | Description | | ---------------------------------------------- | -------------- | ----------------------------------------------------------------- | | `enableOverlayOfflineDataGrabber(_:)` | `SDKErrorCode` | Enables auto-download of overlay data with offline maps | | `disableOverlayOfflineDataGrabber(_:)` | `SDKErrorCode` | Disables auto-download | | `isOverlayOfflineDataGrabberEnabled(_:)` | `Bool` | Checks if grabber is active | | `isOverlayOfflineDataGrabberSupported(_:)` | `Bool` | Checks if grabber is supported for this overlay | | `grabOverlayOfflineData(_:completionHandler:)` | `void` | Manually triggers offline data grab over all downloaded map areas | | `cancelGrabOverlayOfflineData(_:)` | `void` | Cancels an in-progress grab | ```swift let safetyId = Int32(CommonOverlayIdentifier.safety.rawValue) if service.isOverlayOfflineDataGrabberSupported(safetyId) { service.enableOverlayOfflineDataGrabber(safetyId) service.grabOverlayOfflineData(safetyId) { success in print("EV charging offline data grab succeeded: \(success)") } } ``` #### Predefined overlays[​](#predefined-overlays "Direct link to Predefined overlays") `CommonOverlayIdentifier` defines the UIDs for built-in overlays: | Constant | Description | | ---------------------------------------- | --------------------------------- | | `CommonOverlayIdentifierSafety` | Speed cameras, red light controls | | `CommonOverlayIdentifierPublicTransport` | Bus stops, transit stations | | `CommonOverlayIdentifierSocialLabels` | Social label annotations | | `CommonOverlayIdentifierSocialReports` | User-submitted social reports | | `CommonOverlayIdentifierEVCharging` | EV charging stations | ##### Safety overlay[​](#safety-overlay "Direct link to Safety overlay") ![](/docs/ios/assets/images/ios_core_overlaysafety-ab8bb3ab09dcd890b01dfca42814de59.png) **Speed limit and red light control overlay items** Safety overlays represent speed limit cameras, red light controls, and similar items. Use `search(inPreviewDataSafetyCameraParameterType:)` with `SafetyCameraParameterType`: | Parameter | Description | | ------------------------------------ | ------------------------------------- | | `SafetyCameraParameterTypeSpeedUnit` | Speed limit unit (e.g. `km/h`, `mph`) | | `SafetyCameraParameterTypeSpeedVal` | Speed limit value | | `SafetyCameraParameterTypeTowards` | Direction angle | | `SafetyCameraParameterTypeDriveDir` | Driving direction flag | ```swift if let speedVal = item.search(inPreviewDataSafetyCameraParameterType: .speedVal) { print("Speed limit: \(speedVal)") } if let unit = item.search(inPreviewDataSafetyCameraParameterType: .speedUnit) { print("Unit: \(unit)") } ``` ##### Social reports overlay[​](#social-reports-overlay "Direct link to Social reports overlay") ![](/docs/ios/assets/images/ios_core_overlaysocial-344b1fdf3dad08fcdfdc9786a946230f.png) **Fixed Camera social report overlay item** This overlay displays user-reported events such as fixed cameras and construction sites. Use `search(inPreviewDataSocialReportParameterType:)` with `SocialReportParameterType`: | Parameter | Description | | ---------------------------------------- | ----------------------------------------- | | `SocialReportParameterTypeCategNameTTS` | Category TTS name | | `SocialReportParameterTypeCategValidity` | Validity in minutes | | `SocialReportParameterTypeDescription` | Report description | | `SocialReportParameterTypeOwnerId` | Report owner ID | | `SocialReportParameterTypeOwnerName` | Report owner name | | `SocialReportParameterTypeOwnReport` | Whether the current user owns this report | | `SocialReportParameterTypeScore` | Report score/rating | and other more advanced parameters. ##### Public transport overlay[​](#public-transport-overlay "Direct link to Public transport overlay") ![](/docs/ios/assets/images/ios_core_overlaypublictransport-486b7d7676e2ca955b208a759e2dab9c.png) **Bus Public transport overlay item** This overlay displays public transport stations. Two types of public transport stops exist: danger Two types of public transport stops exist: * Bus stations with schedule information — `OverlayItemObject` instances * Bus stations without schedule information — `LandmarkObject` instances #### Work with Overlays[​](#work-with-overlays "Direct link to Work with Overlays") ##### Select overlay items[​](#select-overlay-items "Direct link to Select overlay items") Overlay items are selectable. Identify specific items when users tap or long-press the map using `getCursorSelectionOverlayItems()` on `MapViewController` or using the dedicated overlay selection delegate methods. See the [Interact with map](/docs/ios/guides/maps/interact-with-map.md#select-map-elements) guide for details. ##### Search overlay items[​](#search-overlay-items "Direct link to Search overlay items") Overlays are searchable. Set the appropriate properties in `SearchPreferencesObject` when performing a search. See the [Get started with Search](/docs/ios/guides/search/get-started-search.md) guide for details. ##### Calculate routes[​](#calculate-routes "Direct link to Calculate routes") Overlay items are **not designed for route calculation** directly. Tip For routing, create a landmark from the overlay item's coordinates and a representative name. ```swift if let coords = overlayItem.getCoordinates() { let landmark = LandmarkObject.landmark(withName: overlayItem.getName(), location: coords) // Use landmark as a route waypoint } ``` ##### Display overlay item information[​](#display-overlay-item-information "Direct link to Display overlay item information") Access overlay item preview data using `search(inPreviewDataSafetyCameraParameterType:)` or `search(inPreviewDataSocialReportParameterType:)` for predefined overlays, or `getPreviewData(_:)` for the raw payload. Use `getPreviewUrl()` to open a URL in a web browser for more details about the item. danger The preview data is unavailable if the parent map tile is disposed. Retrieve preview data before further map interactions. ##### Proximity alarms[​](#proximity-alarms "Direct link to Proximity alarms") Configure alarms to notify users when approaching specific overlay items. See the [Landmarks and overlay alarms](/docs/ios/guides/alarms/landmark-and-overlay-alarms.md) guide for implementation details. ##### Download overlay data[​](#download-overlay-data "Direct link to Download overlay data") Some overlays can be downloaded for offline use. See the [Offline](/docs/ios/guides/offline/manage-content.md#download-content) section for more details. --- ### Positions Last updated: March 18, 2026 | 7 minutes read

This page covers position handling in iOS using `PositionObject` and `PositionContext`. Tip Do not confuse `CoordinatesObject` with `PositionObject`. `CoordinatesObject` describes a location only. `PositionObject` includes movement and quality metadata such as speed, course, provider, and map-matching context. #### Create and access positions[​](#create-and-access-positions "Direct link to Create and access positions") In most app flows, you receive positions from `PositionContext`. For testing and simulation-style pipelines, you can also create a `PositionObject` manually with `createPosition:latitude:longitude:altitude:course:speed:speedAccuracy:horizontalAccuracy:verticalAccuracy:courseAccuracy:`. ```swift let synthetic = PositionObject.createPosition( Date().timeIntervalSince1970 * 1000, latitude: 48.858844, longitude: 2.294351, altitude: 35.0, course: 90.0, speed: 13.5, speedAccuracy: 1.2, horizontalAccuracy: 5.0, verticalAccuracy: 8.0, courseAccuracy: 10.0 ) ``` ##### Start continuous updates[​](#start-continuous-updates "Direct link to Start continuous updates") Use `PositionContext` to receive continuous updates: ```swift final class PositionHandler: NSObject, PositionContextDelegate { private let positionContext: PositionContext init(dataSourceContext: DataSourceContext) { self.positionContext = PositionContext(context: dataSourceContext) super.init() self.positionContext.delegate = self } func startRawUpdates() { positionContext.startUpdatingPositionDelegate(.position) } func startImprovedUpdates() { positionContext.startUpdatingPositionDelegate(.improvedPosition) } func stopUpdates() { positionContext.stopUpdatingPositionDelegate() } func positionContext(_ positionContext: PositionContext, didUpdatePosition position: PositionObject) { guard position.hasCoordinates() else { return } let coordinates = position.getCoordinates() print("Position: \(coordinates.latitude), \(coordinates.longitude)") } } ``` ##### Read latest snapshots[​](#read-latest-snapshots "Direct link to Read latest snapshots") You can fetch on-demand snapshots for raw or improved position: ```swift if let raw = positionContext.getPosition(.position) { print(raw.getSatelliteTime()) } if let improved = positionContext.getPosition(.improvedPosition) { print(improved.getRoadSpeedLimit()) } ``` ##### PositionContext operations[​](#positioncontext-operations "Direct link to PositionContext operations") | Method / Property | Description | | ----------------------------------- | ----------------------------------------------------------------- | | `init(context:)` | Creates a context bound to a `DataSourceContext` | | `delegate` | Assign a `PositionContextDelegate` to receive updates | | `startUpdatingPositionDelegate(_:)` | Starts updates for `.position` or `.improvedPosition` | | `stopUpdatingPositionDelegate()` | Stops position updates | | `getPosition()` | Returns latest position from default stream | | `getPosition(_:)` | Returns latest position for a specific `PositionType` | | `getSourceType()` | Returns source stream type as `PositionDataType` | | `getPositionDataType()` | Returns position data type (`.live`, `.playback`, `.unavailable`) | | `setSpeedMultiplier(_:)` | Sets playback/simulation speed multiplier | | `clean()` | Performs context cleanup | #### Raw position data[​](#raw-position-data "Direct link to Raw position data") Raw position data corresponds to `PositionType.position`. It reflects source-level sensor/location data without map-matching enrichment. #### Map matched position data[​](#map-matched-position-data "Direct link to Map matched position data") Map-matched position data corresponds to `PositionType.improvedPosition`. It enriches raw position with road and terrain context (road modifiers, road speed limits, road info, terrain altitude/slope). #### Compare position types[​](#compare-position-types "Direct link to Compare position types") Map-matched positions provide additional data compared to raw positions: | Attribute | Raw | Map Matched | Available when | Description / API | | ------------------------ | --- | ----------- | ----------------------- | ------------------------------------------------------------------------- | | `satelliteTime` | ✅ | ✅ | always | Sensor timestamp in ms since 1970 (`getSatelliteTime`) | | `provider` | ✅ | ✅ | always | Position source (`getProvider`) | | `latitude` & `longitude` | ✅ | ✅ | `hasCoordinates` | Geographic coordinates (`getLatitude`, `getLongitude`) | | `coordinates` | ✅ | ✅ | `hasCoordinates` | Full `CoordinatesObject` (`getCoordinates`) | | `altitude` | ✅ | ✅ | `hasAltitude` | Altitude in meters (`getAltitude`) | | `speed` | ✅ | ✅ | `hasSpeed` | Speed in m/s (`getSpeed`) | | `speedAccuracy` | ✅ | ✅ | `hasSpeedAccuracy` | Speed accuracy in m/s (`getSpeedAccuracy`) | | `course` | ✅ | ✅ | `hasCourse` | Heading in degrees (`getCourse`) | | `courseAccuracy` | ✅ | ✅ | `hasCourseAccuracy` | Heading accuracy in degrees (`getCourseAccuracy`) | | `horizontalAccuracy` | ✅ | ✅ | `hasHorizontalAccuracy` | Horizontal accuracy in meters (`getHorizontalAccuracy`) | | `verticalAccuracy` | ✅ | ✅ | `hasVerticalAccuracy` | Vertical accuracy in meters (`getVerticalAccuracy`) | | `fixQuality` | ✅ | ✅ | always | Trustworthiness (`getFixQuality`) | | `roadModifiers` | ❌ | ✅ | `hasRoadLocalization` | Packed road flags (`getRoadModifier`) | | `speedLimit` | ❌ | ✅ | always | Road speed limit in m/s (`getRoadSpeedLimit`) | | `roadInfo` | ❌ | ✅ | `hasRoadLocalization` | Road metadata list (`getRoadInfo`) | | `roadAddress` | ❌ | ✅ | `hasRoadLocalization` | Address fields from matched road (`getRoadAddressFieldNameWithType`) | | `terrainAltitude` | ❌ | ✅ | `hasTerrainData` | Terrain altitude from map data (`getTerrainAltitude`) | | `terrainSlope` | ❌ | ✅ | `hasTerrainData` | Terrain slope in degrees (`getTerrainSlope`) | | `formattedSpeed` | ❌ | ✅ | always | Localized speed/value unit (`getFormattedSpeed`, `getFormattedSpeedUnit`) | info `getRoadSpeedLimit` can return `0` even for map-matched positions when speed-limit data is unavailable for the current road segment. #### PositionObject structure[​](#positionobject-structure "Direct link to PositionObject structure") `PositionObject` contains both sensor and map-aware metadata. ##### Core fields and checks[​](#core-fields-and-checks "Direct link to Core fields and checks") | Method | Description | | --------------------------------------------------- | ------------------------------------------------------------------------------------------ | | `isValid()` | Checks if position is valid | | `getSatelliteTime()` | Satellite timestamp in milliseconds | | `getProvider()` | Position source (`unknown`, `gps`, `network`, `sensorFusion`, `mapMatching`, `simulation`) | | `getLatitude()` / `getLongitude()` | Raw latitude/longitude values | | `getCoordinates()` | Position coordinates as `CoordinatesObject` | | `getAltitude()` | Altitude in meters | | `getSpeed()` | Current speed in m/s | | `getSpeedAccuracy()` | Current speed accuracy in m/s | | `getCourse()` | Heading in degrees (`0` north, `90` east) | | `getCourseAccuracy()` | Heading accuracy in degrees | | `getHorizontalAccuracy()` / `getVerticalAccuracy()` | Accuracy metrics in meters | | `getFixQuality()` | Fix confidence (`invalid`, `inertial`, `low`, `high`) | | `getType()` | Data object type (`DataType`) | | `getFormattedSpeed()` / `getFormattedSpeedUnit()` | Localized speed text and unit | Availability checks are exposed via: | Check | Description | | ------------------------- | -------------------------------------- | | `hasCoordinates()` | Latitude/longitude available and valid | | `hasAltitude()` | Altitude is available | | `hasSpeed()` | Speed is available | | `hasSpeedAccuracy()` | Speed accuracy is available | | `hasCourse()` | Course is available | | `hasCourseAccuracy()` | Course accuracy is available | | `hasHorizontalAccuracy()` | Horizontal accuracy is available | | `hasVerticalAccuracy()` | Vertical accuracy is available | #### Raw vs improved position data[​](#raw-vs-improved-position-data "Direct link to Raw vs improved position data") Use `PositionType` in `PositionContext` to choose the stream: * **Raw position**: source-level positioning data * **Improved position**: map-matched data enriched with road and terrain context Improved-only style fields are exposed through `PositionObject` when available: | Method | Description | | ------------------------------------- | -------------------------------------------------------------- | | `hasRoadLocalization()` | Indicates map matching localized the position on a road | | `getRoadModifier()` | Packed `RoadModifier` flags (`Tunnel`, `Bridge`, `Ramp`, etc.) | | `getRoadSpeedLimit()` | Current road speed limit in m/s (`0` if unavailable) | | `getRoadInfo()` | Road metadata list in ascending priority | | `getRoadCodeImage(_:)` | Renders road code image (`UIImage`) | | `getRoadCodeImage(_:scale:ppi:)` | Renders road code image with explicit scale/PPI | | `getRoadAddressFieldNameWithType(_:)` | Road-address fields from matched position | | `hasTerrainData()` | Indicates terrain metadata is available | | `getTerrainAltitude()` | Terrain altitude from map data | | `getTerrainSlope()` | Current slope in degrees (positive ascent, negative descent) | Decode road modifier flags from improved positions: ```swift if let improved = positionContext.getPosition(.improvedPosition), improved.hasRoadLocalization() { let flags = improved.getRoadModifier() // 0x1 is the Tunnel flag in RoadModifier let isTunnel = (flags & 0x1) != 0 print("isTunnel = \(isTunnel)") } ``` #### Provider and source context[​](#provider-and-source-context "Direct link to Provider and source context") Use `PositionProvider` and `PositionDataType` to reason about data origin: * Provider indicates where the position came from (`GPS`, `MapMatching`, `Simulation`, etc.) * Source type indicates whether data is live or playback (`PositionDataTypeLive`, `PositionDataTypePlayback`) This distinction helps when building simulation flows, diagnostics, and navigation UI behavior. | Enum | Values | | -------------------- | ------------------------------------------------------------------------ | | `PositionProvider` | `unknown`, `gps`, `network`, `sensorFusion`, `mapMatching`, `simulation` | | `PositionFixQuality` | `invalid`, `inertial`, `low`, `high` | | `PositionDataType` | `live`, `playback`, `unavailable` | Read context values from `PositionContext`: ```swift let sourceType = positionContext.getSourceType() let dataType = positionContext.getPositionDataType() if dataType == .playback { positionContext.setSpeedMultiplier(2.0) } ``` --- ### Routes Last updated: April 9, 2026 | 12 minutes read

A route represents a navigable path between two or more landmarks (waypoints), including distance, estimated time, and navigation instructions. Compute routes in different ways: * **Waypoint-based** - Based on 2 or more landmarks (navigable) * **Over-track** - Based on a predefined `path` from GPX files or other sources (navigable) * **Route ranges** - Not navigable, without segments or instructions Navigable routes consist of segments. Each segment represents the portion between consecutive waypoints with its own route instructions. #### Create Routes[​](#create-routes "Direct link to Create Routes") Routes cannot be instantiated directly. Compute them based on a list of landmarks. See [Get started with Routing](/docs/ios/guides/routing/get-started-routing.md) for details. danger Calculating a route does not automatically display it on the map. See [Display routes](/docs/ios/guides/maps/display-map-items/display-routes.md) for instructions. #### Route types[​](#route-types "Direct link to Route types") The iOS SDK supports standard, public transport, over-track, and EV route variants. | Route type | Type checks and conversion | Class | | ---------------- | ------------------------------- | --------------- | | Normal route | default `RouteObject` | `RouteObject` | | Public transport | `isPTRoute()` and `toPTRoute()` | `PTRouteObject` | | Over-track | `isOTRoute()` and `toOTRoute()` | `OTRouteObject` | | Electric vehicle | `isEVRoute()` and `toEVRoute()` | `EVRouteObject` | ```swift func inspectType(route: RouteObject) { if route.isPTRoute(), let ptRoute = route.toPTRoute() as? PTRouteObject { print("PT fare: \(ptRoute.getPTFare())") } else if route.isOTRoute(), let otRoute = route.toOTRoute() as? OTRouteObject { print("OT route has track: \(otRoute.getTrack() != nil)") } else if route.isEVRoute(), route.toEVRoute() is EVRouteObject { print("EV route detected") } else { print("Standard route") } } ``` #### RouteObject structure[​](#routeobject-structure "Direct link to RouteObject structure") `RouteObject` is the shared contract for all route types. ##### Core methods[​](#core-methods "Direct link to Core methods") | Method | Return type | Description | | ---------------------------------- | -------------------------------- | --------------------------------------------------------------- | | `getWaypoints()` | `[LandmarkObject]` | Route waypoints in order: departure, intermediates, destination | | `getWaypoints(_:)` | `[LandmarkObject]` | Waypoints filtered by `RouteWaypointsOption` | | `getTimeDistance()` | `TimeDistanceObject?` | Total route metrics | | `getTimeDistance(withActivePart:)` | `TimeDistanceObject?` | Metrics for active/remaining part or full route | | `getGeographicArea()` | `RectangleGeographicAreaObject?` | Bounding rectangle for the route | | `getSummary()` | `String` | Human-readable summary | | `getSegments()` | `[RouteSegmentObject]` | Segment list | | `getTrafficEvents()` | `[RouteTrafficEventObject]` | Traffic events affecting the route | | `getStatus()` | `RouteStatus` | Route lifecycle state | | `getIncursCosts()` | `Bool` | Cost-incurring flag | | `hasFerryConnections()` | `Bool` | Whether ferry segments are present | | `hasTollRoads()` | `Bool` | Whether toll roads are present | ```swift func summarize(route: RouteObject) { print("Summary: \(route.getSummary())") print("Waypoint count: \(route.getWaypoints().count)") if let td = route.getTimeDistance() { print("Distance (m): \(td.getTotalDistance())") print("Duration (s): \(td.getTotalTime())") } print("Has toll roads: \(route.hasTollRoads())") print("Has ferry connections: \(route.hasFerryConnections())") } ``` ##### Geometry, sampling, and export[​](#geometry-sampling-and-export "Direct link to Geometry, sampling, and export") | Method | Return type | Description | | ----------------------------------------------------- | --------------------------------- | -------------------------------------------- | | `getPath()` | `PathObject?` | Full route geometry | | `getPath(_:end:)` | `PathObject?` | Geometry subset between two distances | | `getCoordinateOnRoute(_:)` | `CoordinatesObject?` | Coordinate sampled at distance from start | | `getClosestSegment(_:)` | `Int32` | Closest segment index to a coordinate | | `getDistanceOnRoute(_:activePart:)` | `Int32` | Distance from departure to location | | `getTimeDistanceCoordinateOnRoute(_:)` | `TimeDistanceCoordinatesObject?` | Distance/time-aligned coordinate on route | | `getTimeDistanceCoordinates(from:end:step:stepType:)` | `[TimeDistanceCoordinatesObject]` | Distance or time-stepped route sampling | | `export(as:)` | `Data?` | Exports route geometry in a path file format | | `export(as:withCompresion:)` | `Data?` | Export with compression flag | | `getDominantRoads()` | `[String]` | Main roads covering the route | ##### Preferences and metadata[​](#preferences-and-metadata "Direct link to Preferences and metadata") | Method | Return type | Description | | ---------------------- | -------------------------------- | --------------------------------------------------- | | `getTerrainProfile()` | `RouteTerrainProfileObject?` | Elevation/slope analytics if enabled in preferences | | `getPreferences()` | `RoutePreferencesObject?` | Route preferences used for computation | | `getExtraInfo()` | `SearchableParameterListObject?` | User metadata bag | | `setExtraInfo(_:)` | `Void` | Sets custom metadata | | `isEqualWithRoute(_:)` | `Bool` | Equality check against another route | ##### RouteWaypointsOption[​](#routewaypointsoption "Direct link to RouteWaypointsOption") | Value | Description | | -------------------------------------- | ---------------------------------------------------------- | | `RouteWaypointsOptionInitial` | Initial waypoints used at first route calculation | | `RouteWaypointsOptionRemainingInitial` | Remaining initial waypoints (passed intermediates removed) | | `RouteWaypointsOptionRemaining` | Remaining waypoints including service-added entries | ##### RouteStatus[​](#routestatus "Direct link to RouteStatus") | Value | Description | | -------------------------------------- | ------------------------------ | | `RouteStatusUninitialized` | Route object has no ready data | | `RouteStatusCalculating` | Calculation in progress | | `RouteStatusWaitingInternetConnection` | Waiting for connectivity | | `RouteStatusReady` | Route data is ready | | `RouteStatusError` | Calculation failed | ##### Sample route geometry and export[​](#sample-route-geometry-and-export "Direct link to Sample route geometry and export") ```swift if let path = route.getPath() { print("Route points: \(path.getCoordinates().count)") } if let gpxData = route.export(as: .gpx) { print("Exported GPX size: \(gpxData.count) bytes") } if let point = route.getCoordinateOnRoute(2_000) { print("Coordinate at 2km: \(point.latitude), \(point.longitude)") } let samples = route.getTimeDistanceCoordinates(from: 0, end: 5_000, step: 500, stepType: true) for sample in samples { print("distance=\(sample.getDistance()) ts=\(sample.getTimestamp())") } ``` #### RouteSegmentObject structure[​](#routesegmentobject-structure "Direct link to RouteSegmentObject structure") A `RouteSegmentObject` represents the route portion between two consecutive waypoints. | Method | Return type | Description | | --------------------- | -------------------------------- | ------------------------------------------- | | `getWaypoints()` | `[LandmarkObject]` | Segment departure and destination waypoints | | `getTimeDistance()` | `TimeDistanceObject?` | Segment distance and travel time | | `getGeographicArea()` | `RectangleGeographicAreaObject?` | Segment bounding rectangle | | `getIncursCosts()` | `Bool` | Segment cost-incurring flag | | `getSummary()` | `String` | Segment summary text | | `getInstructions()` | `[RouteInstructionObject]` | Segment turn instructions | | `getPTInstructions()` | `[PTRouteInstructionObject]` | PT instruction list | | `isCommon()` | `Bool` | Segment mode matches parent route mode | ```swift for (index, segment) in route.getSegments().enumerated() { print("Segment \(index): \(segment.getSummary())") if let td = segment.getTimeDistance() { print(" \(td.getTotalDistance()) m / \(td.getTotalTime()) s") } for instruction in segment.getInstructions() { print(" - \(instruction.getTurnInstruction())") } } ``` #### RouteInstructionObject structure[​](#routeinstructionobject-structure "Direct link to RouteInstructionObject structure") `RouteInstructionObject` provides maneuver details, signposts, and road metadata. | Method | Return type | Description | | ------------------------------------------------ | ------------------------ | ---------------------------------- | | `getCoordinates()` | `CoordinatesObject` | Instruction location | | `getCountryCodeISO()` | `String` | ISO country code | | `getTurnInstruction()` | `String` | Maneuver text | | `getTurnDetails()` | `TurnDetailsObject?` | Turn detail metadata | | `getTurnImage(_:)` | `UIImage?` | Turn image at requested size | | `hasTurnInfo()` | `Bool` | Turn info availability | | `getFollowRoadInstruction()` | `String` | Follow-road text | | `hasFollowRoadInfo()` | `Bool` | Follow-road availability | | `getSignpostInstruction()` | `String` | Signpost text | | `getSignpostDetails()` | `SignpostDetailsObject?` | Signpost detail object | | `hasSignpostInfo()` | `Bool` | Signpost availability | | `getRoadInfo()` | `[RoadInfoObject]` | Road metadata list | | `getRoadInfoImage(_:)` | `UIImage?` | Road info rendered image | | `hasRoadInfo()` | `Bool` | Road info availability | | `getTimeDistanceToNextTurn()` | `TimeDistanceObject?` | Remaining metrics to next maneuver | | `getRemainingTravelTimeDistance()` | `TimeDistanceObject?` | Remaining metrics to destination | | `getRemainingTravelTimeDistanceToNextWaypoint()` | `TimeDistanceObject?` | Remaining metrics to next waypoint | | `getTraveledTimeDistance()` | `TimeDistanceObject?` | Traveled metrics | | `getExitDetails()` | `String` | Exit details text | | `isExit()` | `Bool` | Main-road exit flag | | `isFerry()` | `Bool` | Ferry instruction flag | | `isTollRoad()` | `Bool` | Toll-road instruction flag | | `isCommon()` | `Bool` | Shares parent route transport mode | | `isEV()` | `Bool` | Instruction belongs to EV route | info `RouteInstructionObject` is route-overview data. For live, position-dependent turn guidance during navigation, use `NavigationInstructionObject`. ```swift if let instruction = route.getSegments().first?.getInstructions().first { print(instruction.getTurnInstruction()) let turnImage = instruction.getTurnImage(CGSize(width: 64, height: 64)) print("Turn image available: \(turnImage != nil)") if instruction.hasRoadInfo() { print("Road info count: \(instruction.getRoadInfo().count)") } } ``` #### Public transport route extensions[​](#public-transport-route-extensions "Direct link to Public transport route extensions") ##### PTRouteObject[​](#ptrouteobject "Direct link to PTRouteObject") `PTRouteObject` adds transit-specific details on top of `RouteObject`. | Method | Return type | Description | | -------------------------------- | ------------ | ------------------------------------------- | | `getPTFare()` | `String` | Fare text | | `getPTFrequency()` | `Int` | Frequency value | | `getPTRespectsAllConditions()` | `Bool` | Whether all PT preferences are met | | `getCountBuyTicketInformation()` | `Int` | Ticket info entry count | | `getBuyTicketURL(_:)` | `String` | Ticket purchase URL for entry index | | `getSolutionPartIndexes(_:)` | `[NSNumber]` | Route-solution part indexes for ticket info | ```swift if route.isPTRoute(), let ptRoute = route.toPTRoute() as? PTRouteObject { print("Fare: \(ptRoute.getPTFare())") print("Frequency: \(ptRoute.getPTFrequency())") let ticketInfoCount = ptRoute.getCountBuyTicketInformation() for i in 0..` | Measurement object with unit support | | `getTotalDistanceFormatted()` / `getTotalDistanceUnitFormatted()` | `String` | Localized distance value/unit | | `getTotalTimeFormatted()` / `getTotalTimeUnitFormatted()` | `String` | Localized time value/unit | | `getFormattedDistance(_:)` / `getFormattedDistanceUnit(_:)` | `String` | Formatted value/unit for arbitrary meters | ##### TurnDetailsObject[​](#turndetailsobject "Direct link to TurnDetailsObject") | Method | Return type | Description | | ------------------------------------------------------------------------------------------ | ----------------------------------------------- | ---------------------------------- | | `getEvent()` | `TurnType` | Turn event enum | | `getAbstractGeometry()` | `AbstractGeometryObject?` | Vector geometry for turn | | `getAbstractGeometryImage()` | `AbstractGeometryImageObject?` | Renderable abstract geometry image | | `getRoundaboutExitNumber()` | `Int32` | Exit number or `-1` if unavailable | | `getTurnImage(_:colorActiveInner:colorActiveOuter:colorInactiveInner:colorInactiveOuter:)` | `UIImage?` | Color-customized turn image | | `getTurnImageId()` | `Int32` | Turn image ID | | `getTurnId32()` / `getTurnId64()` | `TurnSimplifiedType32` / `TurnSimplifiedType64` | Simplified maneuver IDs | ```swift let scale = UIScreen.main.scale let imgSize = CGSize.init(width: 40.0 * scale, height: 40.0 * scale) turnDetailsObject.getTurnImage(imgSize, colorActiveInner: UIColor.white, colorActiveOuter: UIColor.black, colorInactiveInner: UIColor.lightGray, colorInactiveOuter: UIColor.lightGray) turnDetailsObject.getTurnImage(imgSize, colorActiveInner: UIColor.red, colorActiveOuter: UIColor.green, colorInactiveInner: UIColor.blue, colorInactiveOuter: UIColor.yellow) ``` ![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAVyklEQVR4nO3deXAUVbvH8WcmCTsKBBEJGlBRpISLLIpUUHaIslxBsYSobIoUm6UCllVixD9A61VAEAt5Ay+vvCAaFgXZ7o0CsojAlX03BgUhgGFLWEzI3DoNQTLpyTLTPdNn5vupSkmlJ0nTDr/0OX3O84gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgM1cdv8A+C85Odnj1OuXnJzMewdB5w7+jwQA/xBYALRBYAHQBoEFQBsEFgBtEFgAtEFgAdAGgQVAGwQWAG0QWAC0QWAB0AaBBUAbBBYAbRBYALRBYAHQBoEFQBsEFgBtEFgAtEFgAdAGgQVAGwQWAG0QWAC0QWAB0AaBBUAbBBYAbRBYALRBYAHQBoEFQBsEFgBtEFgAtEFgAdAGgQVAGwQWAG0QWAC0QWAB0AaBBUAbBBYAbRBYALRBYAHQBoEFQBsEFgBtEFgAtEFgAdAGgQVAG9GhPgEgDHhKOO4K0nmEPe6wAHvDquA1pXkdSkBgAf4rawgRXAEisAALw6p69epStWpVv74WJSOwgLIrEjjdunWT9PR0ycrKkvPnz8vBgwfl/fffl/j4+OK+B8FVRgQWEKBWrVrJokWLpH79+jc+16BBAxkzZowcPnxYpk6dKjVq1PD15YRWGRBYQIDGjRsnMTExpseio6Nl+PDhcuDAAenVq5evb0FolRKBBQSgUqVK0qFDhxJfV7NmTVm4cKHMnDnT+BoThFYpEFhA2RQKloYNG0q5cuVK/cWDBw+WtWvXSlxcXInfG0URWEAAYmNjy/w1LVq0kJ9++sn4rwlCqxgEFhAAl8u/Rex16tSRNWvWSKdOncwOE1o+EFhAAC5evOj311auXFm++eYb6dmzp9lhQssEgQUE4NixYwFdvwoVKhiT8QMHDjQ7zFotLwQWUDaFxoBHjx6Vy5cvB3QNo6KijKeHI0aM8PUS7rauI7CAAOTm5sr27dsDvoZut1s+/vhjmThxoq+XeAL+IWGAwAICtGnTJsuu4dixY2XatGlGgJnwSIQjsAAHBZYybNgwmTNnjrFK3oRHIhiBBQRo48aNll/DpKQkWbBggZQvX97ssEciFIEFBDjxrp4U7tu3z/LrqPYerlixwle5Go9EIAILsIAKFju0a9dO0tLSfK2o90iEIbAAC6xcudK269iyZUtZt24d+w8JLMCaYaEKlOzsbNsuZ6NGjeSHH36Qe++9N6LvtLjDAixw5coVY2+gnerXr28EY+PGjSM2tAgswOHzWDe744475LvvvjOGiZEYWgQWoFFgFRQDVKHVvn17ibTQIrAAi+axfv31V6P5RDBUqVJFli5dKl26dImo0CKwAA3vshRValmVp/FRKz4sQ4vAAjRZ3mBGlWf+8ssv5cUXX4yI0CKwAAupJ4WBFPXztzzNrFmzjHrx4V5Ti8ACLJzHUrWxvv/++6BfU7fbLTNmzJCRI0f6eklYhBaBBVhMNVUNBbfbLVOmTAnrmloEFmCxxYsXy19//RWy6zp27NiwDS0CC7B4WHjmzBnbV72XJrSmT58edoUACSzABqqxRKgNHTo07AoBEliATfNYeXl5Ib+2SUlJMnfuXImJiQmL0CKwABuGhadPnzY2KjvBs88+a8yrqZZiuocWgQWE8bCwwJNPPmmswldbenQOLQILsDGwrl696pjr27ZtWyO0brnlFm1Di8ACbBoWZmZmyoYNGxx1fRMSEoxKD6rig46hRWABNkpNTXXc9W3evLmsXbtW6tSpo11oEViAjb766ivJz8933DVu1KiRsYXozjvv1Cq0CCzAxmHhiRMnLG+0apX77rtPuzrxBBYQQU8LvcXHxxuhpUudeAILiNBhYYHatWsbW4l0qBNPYAE2DwuPHj0qW7ZscfR1rlGjhqxevVpat27t6NAisIAIHxYWqFatmhFaHTt2FKeGFoEFBGl5g5OHhQUqV65s1IlPTEwUJ4aW6TZuhFeDT1WbSX2oPyOow0LPzR11fv75Z2MNlNNVrFhRlixZIn379jW7M/R4D3mDicAKI+fOnZOMjAw5cuSIsflWfdhYXzzkv211vMvSIbAKmlssWLBABg0aZJSocUpohSwpUbLk5OQSQ+Hs2bOyc+dO2bVrl5w6dYrL6mB33323/PLLL6KT/Px8efnllyUlJcUR+cEdlqZ+//13Y/3MoUOHxOPhZkcH6enpsn37dmnatKnowu12y2effSZVq1aVyZMnex/2BDu4CCzNqA21q1atMt780I+aE9IpsApCa9KkSUZovffee2IiaENEAksTatJcLe7bvHlzqUuWqNK49erVM3bmqzpIt956q68a3wgSJ5WbKavx48cbPRCTk5NDFlpGYPk9pHAVOr9yIvKwiDwuIg+qrUoioraDVxIRVYDnvIioGeA/ROSAiOwRkbUisllEcm98Fz/PxVX4XMKKmptSE7bq7qo4KpC6du0q7dq1M8qINGjQwJg8BazyzjvvSKVKlWTMmDEhCS3/7rD+Dgf167qTiDwvIv+tlnEU81W3XP+oLSLNbvp8jogsEZF/i8j/ist1bbEK8zIGNaG+dOlSyc39O9O9tW/f3pgY7dmzp68yuIBlRo8ebYTWiBEjzG527L/TUj+01B/XzijGI9LfI7Lfcy1arPpQ3+/F69+/TOcVjhITEz0ul6ug1XiRj86dO3s2bdrkAUIhJSXFExUV5ev9aZ8yBJX6eMwjstvioPL+OOAR6XTjZ0ZoYPkKqri4OE9qampI3qTAzebNm+eJjo4OWmi5SrveJzo3V7quWiXNt20LznDN5ZKtLVrIqi5dJM+8r1rYUhtlv/32W9Nj3bt3l9mzZ0tsbGzQzwsws2zZMnnmmWfk8uXLZoctHR6W6pFR9TNnZHBKijTfujV4c0sej7TYskUG/fOfUu3MGYkUe/fuleXLl5see/fdd40tE4QVnKRbt25GH0a1pceEJ6iBddvJkzJg9my5/cQJCYXaJ07IoJQU47/hLisry9h46j3EVUsRPv30Uxk3bhzLEuBIarO0GhXY3Uas2MC6PTNTBs6aJVXPqxUJoVMlO1te/Ne/jPMJ5/U5qtCb2W31hx9+KK+88kpIzgsoLbWcRnXkUbW17Aotd3HDwKS5c6WC+bg06NR59Js7V6qdPSvhaOPGjXL8+PEin3/rrbfk1VdfDck5AWWlqpaqmlo+pi08tgRWdF6e9PnyS6ly4YI4SdULF+S5+fONBwDhVmVB7Qs0a3ypVhcDOmnevLnxfrajjZhpYHVduVJqm/y2d4JamZnSefVqCSdpaWnG1pubqdvq+fPnG1shAN088MADxvCwbt26loZWkcC668iRa0sXHKzl1q1SLyNDwsGZM2dk9+7dRT4/YcIEozkAoKv7779f1q9fb2kbsUKB5b56VbotW+b8bTEejyQuXy5uDUrOlkT9D/UundusWTMZPHhwyM4JsLKNmGrYqsLLhCegwGqyc6fcpkkRuFonT8qDu3aF+jQCooaBZndXb7/9NssXEDbq1q0r69atkyZNmgQcWjcCy5WfLwkbNohO2qxfLy6n3w0WY9++fUXqrKuxf48ePUJ2ToAdatWqZcxp+SgR7SlzYN2dni6xp0+LTmqeOiX1f/1VdGV2dzVgwADurhCWYmNjjQdMgfQ+vBFY/7Vjh+ioiabnreatfvvtt0KfU08E+/XrF7JzAuymarapirlqkak/oWUEVtTVq9LwgKqpp5+G+/cbDwt0o7oBew8HH3roIV9rV4CwobbvqP2yPqY+ig0towxC3NGjEuO1Dsjwj3+I1KwpjqGGrG+8UehT5a9ckTp//CFH77xTdAssbz5+6wBhp0KFCsZWtLL2PjQCK/7IEfPv+vTT6rmkOIY6T6/AUtSaLN0CS/UM9NaqVauQnAsQCqp89xdffCEvvPCCsUi6NKHlLlgioLNamizFuNmff/5Z5HMNGzYMybkAoaIapXz++edGw9bSDA+NwKpp8o9HJ7o93VTOe1XAUE00VKNNINJERUUZvQ+HDRtWYmgZgeW0Tc7+bIrWjfeEuyrqTwMJRCq32y3Tpk0zFk0XF1pGYJUzm3DXSHkNz997s7NqUglEuvHjx8vEiRN9HjcCKyYvT3QWo2G5mTyva16+fPmQnQvgJPfcc48xt+UzsHI1b/KQGxMT6lMAYAH1tPC5554r8gu9UGD9pXl34Cuanz8AkZkzZ0pSUpLPsLoRWBc0nz+5cItqKA1AV9OnT5chQ4YUKbV0XaFW8/Knk1az++E0PfoAbb3//vvGkgYfDZELLR41AuvkbbeJzk5pfv5AJIfVm2++6etwkZXuxmz7kXr1zF+emuq8vYQmMurXD/qpAPCfGvqNHj1aPvroI18v8b2X8FhcnDHxXmQ9lsm+Pae5Ur68/EGFA0CrsFKt66ZOnVrm1vbGkPBqVJQc0HQf2/4HHpB8d4kNrAE4pGGw6lfgT1gpN/6l7zCvt+x4up43EGny8vKkf//+Mnv2bL/CqlBgpd9zj3ZPC9VkO/NXgB5b0fr06SNz5871O6wKBZbH5ZL1CQmik/Vt2hjnDcC5Ll68KN27d5fFixebHS7TP+BCkz87GzeWU7VqiQ4yb79ddj/4YKhPA0AxcnJyjLBabd6tvcx3G4UCKz8qSpY9+aQqziSO5nLJisREJtsBBzt79qx06tTJaO9lwq+QKfJ47bf4eNnaooU42U8tW/peOwYg5LKysqRz586yadMms8N+3xGZrgdY1aWLnLjjDnHqUPB/OnUK9WkA8CEzM1Patm0rW7ZsMTsc0PDNNLDyoqNlQZ8+ku2wTdFqk/Z8VXqCcjKAI6lem23atJFdu3aZHQ54rsnnisuz1avL3KQkuVyhgjiBOo//JCXJuWrVQn0qAExkZGQYreoOHTpkdtiSiXF3ScOvWQMHyvkQl29Rd3pz+vc3zgeA8+zfv18SEhIkPT3d7LBlT/GMb+SjrMP1VxgvUTPci1RzYgm+/xORXmqPthRznqrrjGYK/WXi4+ON31CAbvbu3SsdO3aU48ePmx229B9myZvwroWE+pfUWtXZKqmVtIXUz5l2/ecWG1YAQmPbtm3y2GOPBSWslNLtGr4WFpdFRDUOe1xETGfULLRTRB4TkRGqIANhBTjP+vXrpX379qZNge0IK6X0ZQ5UaF0Lrh9EpJmIvCAi+yw+n70i8ryINFfX46afCcBB1qxZI4mJiUUaAl9n2/xM2euyXAsQVSX+cxFRe2PUoqh/q1UHfp7D+etfr75PYxFRuyPzCCrAmZYvXy5PPPGEZGdnmx22dTK55En3Yr/a5V0M8OHrQ0YVZPeJSB3VWFpE1GPGc+qBn4iowe4BEdktImtFRK0u+7tNhp/nwqR76KnH2YsWqWcz8KVWrVoyYMAAbS9Qamqq9O3bV3LNe4Fq9+QLpee5+SM+Pt6ju8WLFxf6O/FR9BqMHDnSo6t58+Z5oqOjff1/DQpKdQJB9PTTT4djz0BXsM6DwAKCpHbt2tK6tVqlE549A4OBwAKCpHfv3hIVFRW2PQODweiaA9gokidiPd6BFc49A4OBwAKCEFbq6aBaEa6D/Px8eeONN2TSpEmO+yVEYAFB0KtXLy2Gg/n5+TJq1CiZNk3tinPeHTOBBQSBDsPBq1evyqBBg2TOnDmOHdoTWIDNw8HY2FijAqfT23D17dtXFi5c6NiwUggswGZPPfWUREdHO7oNV+/evWXlypWODivFuVcRCBNOXiyak5MjPXv2lLS0NMeHlUJgATYOB6tVq2aUDXZqG67ExET58ccftQgrhcACbB4OlitXznHX+OTJk0Ybrh07dmgTVgqBBUTY08Hjx48bDU737NmjVVgpbM0BbBwOdujQwVHXNyMjw1jAqmNYKQQWYJMePXpIBYe0ySvobKN6Bh4+fFh0DCuFwAIiYDi4Z88eo/760aNHtQ0rhcACbBgOVq1a1ZjUdoKtW7fK448/HrTONnYisAAbdO/e3RHDwXXr1hnzaMHsbGMnAgsI08WiK1askK5duwa9s42dCCzA4uFgpUqVpEuXLiG9rkuXLjUqRFy6dClswkohsAAbhoMqtEJl/vz5Rlhdvqx6H4dPWCkEFmAxFRahMmPGDEc0i7ALgQVYSFVlCNXTwU8++USGDh3qiGYRdiGwAAvnr1RXHLXCPRT114cPH+6YZhF2IbAAC6mncsE2btw4xzWLsAubnwELqXItwZKfny+vvfaaTJkyJSLCSiGwAP95vBulNmnSJGj111966SWZPXt2xISVQmABFg4H3W53UOqv9+vXT1JTUyMmqAoQWIBGw8GLGtVftwOBBVhA9Rzs2LGj7SWNu3XrJhs2bIjIsFIILMCC+atHHnlEatSoYdu1zMzMNIac27dvj9iwUggswOHDwSNHjhgljQ8dOhTRYaWwDgtw8Pqrffv2SUJCAmF1HYEFBKhmzZrSrFkzy6/jli1bjPrrulcJtRKBBViwHcfq5Qxr1641JvFPnz5tdtglEYrAAgL06KOPWnoNly1bZsyJhVPhPasQWICDAmvevHlhWXjPKgQWEOD6qxYtWlhyDadOnSrPP/+85Obmmh12WfJDNEdgAQGIi4uTypUrW1IeZuTIkWFdy8oKrMMCAphwV4EVCBVQr7/+ukyePNnXSwirmxBYQACqVKkSUMWFIUOGSEpKiq+XEFZeCCwgAP4uZ8jJyTEqLnz99de+XkJYmSCwgABkZWWV+WuOHTsmPXv2lG3btpkdJqiKwaQ7UDaFAuXgwYPG0K600tLSpHnz5oSVnwgsIADnzp2TzZs3l6ro3tixY42OOqryggnurEqBwAICNGHCBF/LEQyrV6+Wpk2bygcffMCyBWj9ePzGR3x8vEd3ixcvLvR38l4CEEaK/D1HjRrluXTp0o1rof68ZMkST7t27cyuSbhfH4QhAktvRcKnSpUqnrZt23oefvhhT8WKFUsKKsLKDzwlBPzj8g6d7OxsWbNmTWm/Fn5gDgvwnz/BQ1gFgMACAuMqw+sIqwAxJAQCVxBENw8RCScbEFiAdQgpmzEkBKANAguANggsANogsABog8ACoA0CC4A2CCwA2iCwAGiDwAKgDQILgDYILADaILAAaIPAAqANAguANggsANogsABog8ACoA0CC4A2CCwA2iCwAGiDwAKgDQILgDYILADaILAAaIPAAqANAguANggsANogsABog8ACoA0CC4A2CCwA2iCwAGiDwAKgDQILgDYILADaILAAaIPAAqANAguANggsANogsABog8ACoA0CC4A2CCwA2nCF+gQinKfgD9HR0XLXXXeJznJyciQzM9P707zHYBneTA4JrDDGewyWYUgIOxFWsBRvqNAL17ss3luwHG8qZwmH8OI9BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANju/wHH5cLKKAZklQAAAABJRU5ErkJggg==) **Turn Image with basic colors** ![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAWqklEQVR4nO3dCXRc1WGH8f8s0kjWYknel2IwtgGvrHYWs4YtYIMhxCc0JyQ5adPkJA1toYUSKKQhBQJZTsnJ3gYIaRoC2MbEwYGEHWIMBBtsbLzgfZVlC0n2aBlNz32yXI/0RpZm3pt5d+b7nWM41jJ6Fujzu+/dd29o3oI5AgAbhPN9AADQXwQLgDUIFgBrECwA1iBYAKxBsABYg2ABsAbBAmANggXAGgQLgDUIFgBrECwA1iBYAKxBsABYg2ABsAbBAmANggXAGgQLgDUIFgBrECwA1iBYAKxBsABYg2ABsAbBAmCNaL4PAOktmL0/efTvt7WM0DlLHsz5t+yFyz6rsRW7U9521Uu1oZwfCIoeZ1gArEGwAFiDYAGwBsECYA2CBcAaBAuANQgWAGsQLADWIFgArEGwAFiDYAGwBsECYA2CBcAaBAuANQgWAGsQLADWIFgArEGwAFiDYAGwBsECYA2CBcAaBAuANQgWAGsQLADWIFgArEGwAFiDYAGwBsECYA2CBcAaBAuANQgWAGsQLADWIFgArEGwAFiDYAGwBsECYA2CBcAaBAuANQgWAGsQLADWIFgArEGwAFiDYAGwRjTfB1CsOiz/1tt+/LBPlP/rAE8kj/H+EN9nbzAkBPyNVffH9OfjcAwEC8jcQCNEuLJEsAAPY1XR2qnytmN2jLOtDHHlFBi4XsE5fVNcn3uhUcM/SDi/31kT1fLxZXp6WoX2VkX6eg2ubw0AwQKyNHFXm25Ysl+Rzv/v2KgDHbrizWZd/laznplaod/OrFJzmeuAxnwS0eonhoRAlj6xvCklVkeLdEqXrGzR9x7eo5kb4ulegiFiPxEsIAulHUlN3dZ2zI+rinfqn37foC/+6YDzOS6IVj8QLGBgUsIyZn+Hoon+t+aC1Qd1x+P1qmtJHPO10RvBArJQGe8c8OeM39Oubz1S7/zbBdHqA8ECshDKMC+1LQndvqBe07a2ur2baKVBsIAstJVkfoMv1p7UvzzZoDM3ul6MJ1ouCBaQhYYK1zlW/VaSSDoX489796Dbu5kZ3wPBAgYm5ZRqX2VY7ZHsplGFk9Lf/emALl3Zku5DONs6jGABWUiEQ9o0rMSTa2Fmpvy1r36Q7kOSWX+RAkCwgCy9N7LUs+/hlW806/PPN6a7mJ9UkSNYQJbWjcz+DOtol7zdoq88Yx71cX13UkWMYAFZem+Ud2dY3WavPaTrlzY4F+VdJFWkCBYwcKGedwq313q/joB59vDmxQ3plqtJqggRLMADb40r8+X7OGVbq25dVO88i+ii6KJFsAAPvDUu5tv38cTd7bqd5w8dBAvwYFi4ZnSp4lnMej+WsQ0duuOxeo1o7CjqMy2CBXjATB5dPca/syxj+AcJ3fH4Ph23r3gfmiZYgAXDwqMfmr5twT5nmFiM0SJYQMAvvPdUFe/UbQvrnQvyxRYtggVkLuWi1Z7qiLP5RC6UtSd105MNmrGluKJFsADLhoXdzFLL//y7hnRrxRdktAgWYOGwsJtZnvkfnmrQuWvSLk9TUAgW4KHVY0rVFs3trl3hpPSlPx5w1osv9DW1CBaQnVDP6Q2rxsZyfxBJ6W+fPaBLVxT2mloEC/DYshNzOyxMWVPrxcJeU4tgAR4zW9R3ZLkKabZral1boNEiWED2UurUEgs717Ly6co3mvWFAlwIkGABPlh2Ynnev68XFeBCgAQL8MFrJ5YpEYCfrtlrD+mrT5toFcaaWgH4lgKFNyxsKgtrzejc3y108+F1h3Tjkv0FsXopwQIK7G6hm9M2xfWvTzQ4j/TYHC2CBfh4HaszfzcLe5m8vVU3P7HP6iWXCRbgnZQ8NQ4Ka60PG1Rk4+Sdbc5KD7YuuUywAB8tm5D/u4U9jd/TteSyWVvLtmgRLMBHf55QrmSAhoVHL7n8bwv2aUizXdEiWIC3UvJ0YFDY052hvTTqQNc68SMtWieeYAE5mJMVVMOaEk60bFknnmABPns1oMPCbjUHO53hoQ3rxBMswO+doSsj2jA8mMPCbpXxTn39iX2atKtNQY4WwQKKbBJpOoNaO/X1Rfs0bWtw14knWECOpjcEeVjYLdbetU78qZuDGa3cbPGBvAmH4gqHDykcalUkdCij19hyUIon+V9lgEJH/4CbHXXeH1bizIEKutKOpG5c0qD7L651OzM0f6a8pZf/CwtISWSfKkrf1aDStYpFdygW3alIuCnr171rrflnbeD+trXxUR0bgtW9ucX1TzXoJx+r0fMnD1JQokWwLFcSqVdN+csaXP6KEykE16sTy/taCTRwwoc3twh3Ss9ODka0CJalBpWu19DKJ1QVe4uTHUuYYeGmoSU6vt6OsyzDrFj6xWcPqLw9qSUzKpTmLDtn4SJYlimLbtXI6l+pIrYq34eCDCeR2hSs7mhd92Kjyto69fhZVXKRs7MtgmWJeCKkx7YnNX7YbQrJ9fmvXszSuMOaOlR1qNNZB2lQW1KhJJee8qkzbMGtwjTmL2tyhomPzsxftJxgLZi3OLPPDqUcn5kZN1PSuZKmSpokabQZvUiqlmQG72anR3OhxVzGNacIz5trkWY7tyOvkuEP1FUL56pQbT0Y0bfXVmtTS7LPWJl5NDO2tGrK9jadtKNVoxoTzsVTwCvXvNakWEdSv/qI+ZHOfbSiWYbKzOO6SNJnJM2T1GuQe5Tqw79GSjr9qLebnR8XSnpI0jMKhboW6uFMwPHc3ph+sK5KrX2sBDdlW6suXHVQZ26Mp1sGF/DM3DebVdqe1APnDna7VexrtKIZxqpE0qcl3SzppCyPoeLwa3368JnXXZL+R6FQe7FHa/GOcv1sY2Xa+QPTt7Tqk681aaL74xSAby55u8U50/rpBTVuq6r6Fq1oBmdV50j6ofmL3YfjMfF7QNItkr6qUOhp561FGq6fbqx0fXtdS0KffaFRszbEc35MQLfz3j3onNH/8MJatx2CfIlWaN6COVowe/+xi2CWVL31femhXbmZMmj+qJ8bKX3zBClWXE8QLdlZrh9tcI/VGe/HnbkxaZa4BXLuzePL9L2P16rdfbdrz6IVVUc/nyXcHJcuXSk9mKNYGebr/GJX19fdUjxnEi/Xx/TjNLH65LIm55EJYoUgOX1TXDcsaXAe6XHhaTGOHaw1B6W5b0urzLXxPHinRfr4213/LnA74xHdv76q139hMw/mb55r1CeWN6XbehzIK/Ow9E1P+r+NWN/BWt0izXlb2pnni7p72qR573QdT4Eyfznds6ZaLR29z6A/81KjLiyCYMNuU7a1OjvymLW1/IqWc9F9W8uI3u/Y0qJhn3xDEff1nnOvsUOJ+Wu097Hz1DG213NN1ntqd1Ibmnv/N533erMuW0GsYAezauktT+zTXVcMcXa/9vpCvBOsc5Y8mPLGWHtcj953oUbuCda1o8juuBrnb9VVNz2reEnwtk/KZpWFCcNucpaA6bnx5fxl9jwsCxhmRQqzTvyd84Zof0VEXkbLdUh426M3acrWFQqik3as0i2PfV2FZETVI71iZU6rv7Z0v/MoBGCbMfs7dNvCfarzeBuxXsE6a/0ruvalXyjIPv3iz/Wh915UISiN7FF1uXk6KZVZhsRsDgDYavT+Dn3jsXqN8HAbsZRgRRPtuvPX1wf+AVlzfHc8cqMinQG5vpaFoZVP9no+8IS97bpgtXnsErDbsKaEbl+wz4mXi2RWwZr32m80ceca2WDSjtW6Yvlv830YWTHDwMHlr/Z6O9MXUEjqmhO64/F6Hee+rE4yo2BFOhP60h++K5t8eel3FE7aO2yqLnvdWXO959jfzGYHCkn1oU7nmlaaJaKTAw7WR9c8q/G718kmE3at1YfXmhVq7OR2dmWez2JyKApRVbxTty7Kbu/DI8G6etmvZaOrlv2vbGSuW5nNIo5m7gjOXpvZzjaADcyabbcs2udMMs0kWk6wSjradOGKJ2Wji1csdm4W2Ka8dEOv4eDxe9tV29K/1UQBW5nHd25e3JDu0kfymBNHT930uga1udyVuu8+aehQBUZ9vXTjjSlvqow3afrmv+jN8WaxU3uUl6zv9bY0f+sABackkdQ/PrV/wHsfOsGauf5l91e95hpp3DgFxubNvYJlzFr3onXBMnsG9jRxNwvxoXhEE0l9bWmDs57Wy5PK+xWtcPcUAZtN2vmubOMWrDRzVYCCFemUvvL0fp3vPu8w6Ros2+4O9mTj8UcjDSm/N3+VjGjk+hWKT/jw3odm2eVjRcsJ1vDGXbLZ8AO9z1aCLtLjgrtZ1J8NJFCsQknp88836urlTX1GywlWRWuzbFbZ6vqHDLSedwjNzrpAsZu/rMl5jjadcPdyMjYra7Nv7pLZFOhonF0BXUY2JpxrW2mD1VrS67aiVeKlhbM2FlDMXp5Urv+82HUXHofz5paY+6YHtmiOuW6dDcAif5wySD+4KH2sDOdduwePks1214zO9yEAyMIfplXo5+fXKOm+FmnKVvN6f8RE2Wyj5ccPFLNFZ1Tqv923vTdSEuYE673Rp8hm60adnO9DAJBhrH794ep07w65PpqzbOJs9w9/9NHgPUvo4s+Tzsn5oQDInBn6PfzRav3u1LTXz9M/S7hi3Bk6GKvQoNYeM01dntsLmuayKq0cd1q+DwPAAGL14NmD9dT0Crd397mjjjMkbI+W6ukZc2SjpadeoUTY6S6AgOsMST+5oCajWBlHbiAumPkp2WjBLDuPGyg2ibCclRmeO8V1I+R+7VV4JFgvnXKBdXfbzMV2rl8BwdcRCen7l9bppZNcJ3n3e2PVI8HqDIX140tukE1+dMkNznEDCK62aEjfvrxOy8e7PlEzoF2gU37aF541X+tG2THFYc2YqVp85jX5PgwAfWgtCemeOXVaeVzM7d0D3rI+JVgdkRLdeu33lQwN+HVyyhzfN+bfy8V2IMAOxsL61pVDtGqsN7EynNtrL1z22ZQ3tuw5QZUPb1RQtVw3Xvde/zNJ5pedvvyXfB8B4J/msrDumjtEG0aUuL074zMiJ1hjK3anvvXuEdKK3ZL7CoD5NblClXcOV2VZj2O2zrB8HwDgi8ZBXWdWW4Z4GyvD/Yp1LCw9cLI0olSBMrJUevgUqYwL7UAQ1VdFdMfVQ32JlZH+J/+4MumRydLggEzKNMfxm8nSX7mOhwHk2d7qiP79qqHaWePaDE8ujPd9qjK5QnpymjQ6z2da5kxv4dSu4wEQODtqo7r9E0O1pzri9m7P7uJ1pXDoS+lf8GznXcdLelxSPh7ae1O7267W+W9tVjL9uudXLZwry7CIOwrCtrqoc81qf4W/sTKOfTGoKxKbJH1E0g9z+INmvs4PDn/dPmMFID82Di/RN64empNYGf27et0VC7NTxVcknSvpbflrpSSzZszfm7lnxAoInjWjSvXNeUPV5H4TzJfJnP2/3Wai1RWuFyWdLuk6SV5vuWy2oP6MpDPM441HfU0AAbJ6TEx3XzFEh0pdu+TbzPOBzw/oCojZU/2XkqZKukjSQ5Iy3Rzwg8Ofb15nmqSHndcnVEAg/WVcme6eW6d4SW5jZWQ2Z6E7JqGQ2T3smcO/viBp5uEhownZJElmdwizpKBZA7XRTICVZLZpXivpHUnPS1p+OICprw3rmNvZr51o95Zxfht8sFPnvXtQtlo2wWzDVaNEOPexOhKsjO+wLUhZ9M9E55XDvzJn390+HLa9LtrX+tyQdOmKAD49MoA9A816Vmm24crJA8gBmRUKFIcPbbBvl/LuPQN/ft6xt+HyG8ECcqTmYKcm7Wyzcs/AX/RzGy6/ESwgR2ZtOKRwsrC34fIbwYLfgr24mr9S8jRzg5nKaI9FAYuVQbCAHMSq+lCnTtneWtB7BuYCwQJyYKYlw8FkSHrg7MFa6r4NV97PmAkWkAOzLBgOdpo9Az9Wo+dPznwbLr8RLMB7KedSVfFOTQ74cLAjEtL9F9dqmfvE30DEyiBYgM/O2hhXxDwTEuBtuL5zWZ1WeLSzjZ8IFuCzWesPBXobrnsvr9M7Hu5s4yeCBfg4HBzU2qkp29sCuw3XXXPrtM7slWBBrAyCBfho5sa4oong3R78oLxrZ5vNQ/3ZLMIvBAsosruD+ysi+o8rh2hrnX+bRfiFYAE+Dgenbm0N3M42d145RLsH2xcrg2ABPjnz/bhKAjQc3FEbdWLVUJmb9df9QLCAIhgObq2LOsPAXG0W4ReCBXgj5VSqvC2p6VtaA7OzzV1XDMnpZhF+IViAD07fFIzh4LujS/XtObnfLMIvBAvwwYcCMFn0rXExfffjdc5M9kKIlUGwgOylnEqVdiQ1I8/DwTdOKNP3L61Ve6RwYmUQLMBjZ7wfd6JVzJtF+IVgAQV0d/CZqRX6L7P+ep43i/ALwQI8ZFZlmJ6nyaJLp1XogYBsFuEXggVkJ6UPk3a1OTPcc21RANdf9wPBAjw0Y3Puh4OPzKrS42dVFXysDIIFeOjUzbkbDiZD0kOzB+v3M4K5/rofCBbg0XDQbJQ6bl97ztZf/+kFNXruFNf11wsyVgbBAjwcDoaSOVp//aIaLZtQXjSh6kawAI+cloPhYJtF66/7gWABHjB7Dk7d1ur7ksb3zKnT2lH2LGnsNYIFZCZl8Ddhd5sq4/5NZ2gcFHZWXNhk2ZLGXiNYQMDvDu6t6lrSeGeNnauEeolgAR441af5V9tro85mETavEuolggVkyezsfMJe76czbBhRorvnFsbCe14hWEC2j+PsbPN8OsPqMaW69/LCWXjPKwQLyJJ5ftBLbx7ftZZVIS285xWCBWRpoofB6lrLqkaJMLFyQ7CALOdfnbjHm+tXT02v0INnF+5aVl4gWEAW6poTirVnfwGrWJaHyRbBAgYmpU51LYmsvn/mbOqXswdrSRGtuJANggVkIZuzK7Piws/Or9Gzk4trxYVsECwgy2tYmWgtMSsu1Or18WXpPoRYuSBYQBaaY+5b0/SloSKi+y6vc3ZkdkGo+kCwgIExQTlyXrWzJuIM7fp7pvXO2Jjuv7jWeZg5zWujDwQLyHLJl/UjSo85edQsumfWXl98WiXTFrIw8PNZACkWnpk2Qo6Vx8V006eG6YnTiVW2OMMCPHiUxmwG8devfKCSRNfY0GwRv2JcTL+fXqFVY11XB+3GMHAACBaQ5XUsw+xcY6YnjN/T5jwDuGVISbpnAXu+DgaAYAEeRSteEtLqMbH+fi4ywDUsIHOZhIdYZYFgAdkJDeDjiFWWGBIC2esO0dFDROLkA4IFeIdI+YwhIQBrECwA1iBYAKxBsABYg2ABsAbBAmANggXAGgQLgDUIFgBrECwA1iBYAKxBsABYg2ABsAbBAmANggXAGgQLgDUIFgBrECwA1iBYAKxBsABYg2ABsAbBAmANggXAGgQLgDUIFgBrECwA1iBYAKxBsABYg2ABsAbBAmANggXAGgQLgDUIFgBrECwA1iBYAKxBsABYg2ABsAbBAmANggXAGgQLgDUIFgBrECwA1iBYAKwRzfcBFLGQpGT3bxoqIvradSNks9ao+SMB/iFYAZEIS3uqI/k+DCDQGBLCT5xywVMEK78K+Qe6kP9syBOGhMH6wT5yTctihAq+IVh5ElWH25v5YQf6wJAQgDUIFgBrECwA1iBYAKxBsABYg2ABsAbBAmANggXAGgQLgDUIFgBrECwA1iBYAKxBsABYg2ABsAbBAmANggXAGgQLgDUIFgBrECwA1iBYAKxBsABYg2ABsAbBAmANggXAGgQLgGzxf9BVMv5f9CujAAAAAElFTkSuQmCC) **Turn Image with customized colors** ##### SignpostDetailsObject[​](#signpostdetailsobject "Direct link to SignpostDetailsObject") Signposts near roadways indicate intersections and directions. The SDK provides realistic image renderings with additional information. ![](/docs/ios/assets/images/signpost_image-eeff2259d472a997f59b32830e683611.png) **Signpost image captured during highway navigation** | Method | Return type | Description | | ----------------------------------------------- | ---------------------- | ----------------------------------- | | `getItems()` | `[SignpostItemObject]` | Signpost elements | | `hasBorderColor()` / `getBorderColor()` | `Bool` / `UIColor` | Border color availability/value | | `hasTextColor()` / `getTextColor()` | `Bool` / `UIColor` | Text color availability/value | | `hasBackgroundColor()` / `getBackgroundColor()` | `Bool` / `UIColor` | Background color availability/value | | `getImage(_:)` | `UIImage?` | Rendered signpost image | ##### RouteTerrainProfileObject[​](#routeterrainprofileobject "Direct link to RouteTerrainProfileObject") If route preferences enabled terrain profile generation, `getTerrainProfile()` returns this object. | Method | Return type | Description | | --------------------------------------------------------------- | -------------------------- | -------------------------------------- | | `getMinElevation()` / `getMaxElevation()` | `Float` | Min/max elevation | | `getMinElevationDistance()` / `getMaxElevationDistance()` | `Int32` | Distances for min/max elevation points | | `getTotalUp()` / `getTotalDown()` | `Float` | Total ascent/descent | | `getTotalUp(_:end:)` / `getTotalDown(_:end:)` | `Float` | Ascent/descent for route range | | `getElevationSamples(_:)` | `[NSNumber]` | Elevation sample list | | `getElevationSamples(_:distBegin:distEnd:)` | `[NSNumber]` | Elevation samples for route subsection | | `getElevation(_:)` | `Float` | Elevation at distance | | `getClimbSections()` | `[ClimbSectionObject]` | Climb sections | | `getSurfaceSections()` | `[SurfaceSectionObject]` | Surface type sections | | `getRoadTypeSections()` | `[RoadTypeSectionObject]` | Road type sections | | `getSteepSections(_:)` | `[RoadSteepSectionObject]` | Steepness-classified sections | | `getElevationChartMinValueY()` / `getElevationChartMaxValueY()` | `Float` | Chart range helpers | ##### RouteTrafficEventObject[​](#routetrafficeventobject "Direct link to RouteTrafficEventObject") `RouteTrafficEventObject` extends traffic-event data with route-relative context. | Method | Return type | Description | | ----------------------------------------------------------------- | -------------------- | -------------------------------------------------- | | `getDistanceToDestination()` | `Int32` | Distance from event to destination | | `getFrom()` / `getTo()` | `CoordinatesObject?` | Event start/end coordinates | | `getFromLandmark()` / `getToLandmark()` | `LandmarkObject?` | Event start/end landmarks | | `hasTrafficEventOnDistance(_:)` | `Bool` | Whether event affects the remaining route distance | | `isInsideTrafficEventOnDistance(_:)` | `Bool` | Whether the point is inside event impact zone | | `getDistanceFormatted()` / `getDistanceUnitFormatted()` | `String` | Formatted event distance | | `getDelayTimeFormatted()` / `getDelayTimeUnitFormatted()` | `String` | Formatted delay time | | `getDelayDistanceFormatted()` / `getDelayDistanceUnitFormatted()` | `String` | Formatted delay distance | *** Use `RouteObject` as the base route contract, then branch to `PTRouteObject`, `OTRouteObject`, or `EVRouteObject` only when route-type-specific behavior is needed. --- ### Traffic Events Last updated: March 18, 2026 | 4 minutes read

The Maps SDK for iOS provides real-time traffic information about delays, incidents, and road restrictions that can affect routing and navigation. **Event sources:** * **Magic Lane servers** - Provide up-to-date traffic data when online * **User-defined roadblocks** - Restrict specific road segments or areas **Impact zones:** * **Path-based** - Follows the shape of a road segment * **Area-based** - Covers a larger geographic area Core entities for traffic handling: * `TrafficEventObject` for generic traffic events * `RouteTrafficEventObject` for route-specific traffic details * `TrafficContext` for traffic configuration and roadblock management *** #### Enable traffic[​](#enable-traffic "Direct link to Enable traffic") Use `TrafficContext` to enable traffic for route calculation: ```swift // Create or access your TrafficContext let trafficContext = TrafficContext() // Enable online traffic trafficContext.setUseTraffic(.useOnline) // Or use offline-only traffic (local data) trafficContext.setUseTraffic(.useOffline) ``` info Traffic usage mode (none/online/offline) influences route calculation behavior when traffic-aware routing preferences are applied. Enable traffic before calculating routes to include traffic data in results. *** #### TrafficEventObject structure[​](#trafficeventobject-structure "Direct link to TrafficEventObject structure") The `TrafficEventObject` class contains traffic event information: | Method | Returns | Description | | ---------------------------- | -------------------------------- | -------------------------------------------- | | `isRoadblock()` | `Bool` | Whether event represents a roadblock | | `getDelay()` | `Int` | Estimated delay in seconds (`-1` if unknown) | | `getLength()` | `Int` | Length in meters of affected segment | | `getImpactZone()` | `TrafficEventImpactZone` | Path or Area impact zone | | `getReferencePoint()` | `CoordinatesObject?` | Central coordinate for the event | | `getBoundingBox()` | `RectangleGeographicAreaObject?` | Bounding rectangle | | `getArea()` | `GeographicAreaObject?` | Geographic area of the event | | `getDescription()` | `String` | Human-readable description | | `getEventClass()` | `TrafficEventClass` | Classification of the event | | `getEventSeverity()` | `TrafficEventSeverity` | Severity level | | `getImage(_:)` | `UIImage?` | Event icon/image at specified size | | `getPreviewURL()` | `URL?` | Preview URL (empty for user roadblocks) | | `isUserRoadblock()` | `Bool` | Whether event is user-defined | | `getAffectedTransportMode()` | `Int` | Affected transport modes bitset | | `getStartTime()` | `TimeObject?` | UTC start time | | `getEndTime()` | `TimeObject?` | UTC end time | | `isActive()` | `Bool` | Whether event is currently active | | `isExpired()` | `Bool` | Whether event has expired | | `hasOppositeSibling()` | `Bool` | Path event has opposite-direction sibling | *** #### RouteTrafficEventObject structure[​](#routetrafficeventobject-structure "Direct link to RouteTrafficEventObject structure") `RouteTrafficEventObject` extends `TrafficEventObject` with route-specific information: | Method | Returns | Description | | ------------------------------------ | -------------------- | -------------------------------------------------- | | `getDistanceToDestination()` | `Int` | Distance in meters from event to route destination | | `getFrom()` | `CoordinatesObject?` | Event start point on route | | `getTo()` | `CoordinatesObject?` | Event end point on route | | `getFromLandmark()` | `LandmarkObject?` | Start point as landmark | | `getToLandmark()` | `LandmarkObject?` | End point as landmark | | `hasTrafficEventOnDistance(_:)` | `Bool` | Whether delay exists within remaining distance | | `isInsideTrafficEventOnDistance(_:)` | `Bool` | Whether current position is inside event | | `getDistanceFormatted()` | `String` | Formatted distance to destination | | `getDelayTimeFormatted()` | `String` | Formatted delay duration | | `getDelayDistanceFormatted()` | `String` | Formatted delay distance | *** #### Event classifications[​](#event-classifications "Direct link to Event classifications") `TrafficEventClass` provides classification categories: * `trafficEventClassOther` - General information * `trafficEventClassLevelOfService` - Congestion level * `trafficEventClassAccidents` - Accident/incident * `trafficEventClassClosuresAndLaneRestrictions` - Closure or lane restriction * `trafficEventClassRoadworks` - Construction/roadwork * `trafficEventClassObstructionHazards` - Hazards on road * `trafficEventClassRoadConditions` - Road surface condition * `trafficEventClassDelays` - Traffic delay * `trafficEventClassTrafficRestrictions` - Access restriction * `trafficEventClassUserRoadblock` - User-defined roadblock `TrafficEventSeverity` indicates impact level: * `trafficEventSeverityStationary` - Stationary traffic * `trafficEventSeverityQueuing` - Queuing traffic * `trafficEventSeveritySlowTraffic` - Slow traffic flow * `trafficEventSeverityPossibleDelay` - Possible delay * `trafficEventSeverityUnknown` - Unknown severity * `trafficEventSeverityFree` - Free traffic flow *** warning Traffic events are only available on routes calculated with traffic enabled. Enable traffic in `TrafficContext` before route calculation for traffic-aware routing. *** Traffic events integrate seamlessly with routing and navigation, ensuring calculated routes account for real-time conditions and user-defined restrictions. --- ### Get started The Magic Lane SDKs enable developers to build advanced navigation and mapping applications with minimal setup. The guides below walk you through creating an API key, integrating the SDK of your choice, building your first application, and following best practices for optimal performance. #### [📄️Integrate the SDK](/docs/ios/guides/get-started/integrate-sdk.md) [The Magic Lane Maps SDK for iOS is distributed as a Swift Package hosted on GitHub, making it easy to integrate directly into your Xcode projects using Swift Package Manager (SPM).](/docs/ios/guides/get-started/integrate-sdk.md) #### [📄️Coding with AI](/docs/ios/guides/get-started/coding-with-ai.md) [Use LLM-friendly documentation files and AI coding assistants to accelerate development with the Magic Lane Maps SDK for iOS.](/docs/ios/guides/get-started/coding-with-ai.md) --- ### Coding with AI Last updated: April 24, 2026 | 3 minutes read

Magic Lane provides machine-readable documentation files that integrate with popular AI coding assistants. Use them to get accurate, context-aware answers about the Maps SDK for iOS while you code. #### Available resources[​](#available-resources "Direct link to Available resources") Two files are generated with every documentation build and published alongside the docs: | File | Contents | URL | | ----------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | **llms-full.txt** | Developer guides, explanations, and code examples | [developer.magiclane.com/docs/ios/llms-full.txt](https://developer.magiclane.com/docs/ios/llms-full.txt) | | **llms.txt** | Concise API overview and page index | [developer.magiclane.com/docs/ios/llms.txt](https://developer.magiclane.com/docs/ios/llms.txt) | These files follow the [llms.txt](https://llmstxt.org/) standard and contain detailed concept explanations, code examples, documentation references, and source file locations for verification. #### Using with AI coding tools[​](#using-with-ai-coding-tools "Direct link to Using with AI coding tools") Download both files and place them in your project. Each tool reads them differently - follow the setup for the one you use. ##### Cursor[​](#cursor "Direct link to Cursor") 1. Create a `.cursor` directory in your project root. 2. Place `llms-full.txt` and `llms.txt` inside it. 3. Open **Cursor Settings → Rules** and add a new rule with type **Apply Intelligently**. 4. Set the description to: *Use this rule whenever a question is asked about Magic Lane Maps SDK for iOS.* 5. Reference the files in the rule body. Cursor will automatically use them when relevant. ##### GitHub Copilot[​](#github-copilot "Direct link to GitHub Copilot") 1. Create a `.copilot` directory in your project root. 2. Place `llms-full.txt` and `llms.txt` inside it. 3. In Copilot Chat, reference the files by stating: ```text Please use the context from the llms.txt files in the .copilot directory when answering questions about the Magic Lane Maps SDK. ``` ##### JetBrains AI Assistant[​](#jetbrains-ai-assistant "Direct link to JetBrains AI Assistant") 1. Create a `.idea/ai` directory in your project root. 2. Place `llms-full.txt` and `llms.txt` inside it. 3. Reference the files in AI Assistant chat when asking about the SDK. ##### Windsurf[​](#windsurf "Direct link to Windsurf") 1. Create a `.windsurf` directory in your project root. 2. Place `llms-full.txt` and `llms.txt` inside it. 3. Reference the files in chat for context-aware responses. ##### Claude Desktop / Claude Code[​](#claude-desktop--claude-code "Direct link to Claude Desktop / Claude Code") Pass the `llms-full.txt` URL directly in your prompt: ``` Use https://developer.magiclane.com/docs/ios/llms-full.txt as context for the following question about the Magic Lane Maps SDK for iOS: [YOUR QUESTION HERE] ``` #### Tips[​](#tips "Direct link to Tips") * Experiment with different prompts and models to get the best results. * The `llms-full.txt` file is best for general questions and guides. Use `llms.txt` for a quick overview of available pages. * Always verify generated code against the official [API reference](/docs/ios/api-reference/). --- ### Integrate the SDK Last updated: January 27, 2026 | 2 minutes read

The Magic Lane Maps SDK for iOS is distributed as a Swift Package hosted on GitHub, making it easy to integrate directly into your Xcode projects using Swift Package Manager (SPM). #### Adding the SDK to Your Project[​](#adding-the-sdk-to-your-project "Direct link to Adding the SDK to Your Project") To integrate the Magic Lane Maps SDK into your iOS application: 1. **Open your project in Xcode** 2. **Add the package dependency** * Navigate to **File > Swift Packages > Add Package Dependency** 3. **Enter the package URL** * Paste the repository URL: `https://github.com/magiclane/magiclane-maps-sdk-ios` 4. **Select your version** * Choose your desired version, branch, or commit * We recommend using the latest stable release 5. **Complete the integration** * Click **Add Package** and Xcode will resolve dependencies * Select the SDK products you need for your target Once added, the SDK will be automatically managed by Xcode, and you can start importing and using the Magic Lane Maps SDK in your Swift code. **Note:** If you're migrating from the old package URL (`https://issuetracker.magiclane.com/magiclane/maps-sdk-ios-swift-package`), remove the old dependency first via **File > Swift Packages > Remove Package** before adding the new GitHub-hosted version. --- ### Maps SDK for iOS ![](/docs/ios/assets/images/maps_sdk_banner_ios-46cc793dc041dd8cb8f6641d8d87d3f3.png) Last updated: May 18, 2026 | 3 minutes read

Current version: **2.1.6** | [View Release Notes](/docs/ios/guides/introduction/CHANGELOG)

Welcome to the Developer Guide for the Magic Lane Maps SDK for iOS. This guide provides everything you need to build immersive, location-aware applications with seamless mapping and navigation capabilities for iOS devices. #### Why use the Maps SDK for iOS ?[​](#why-use-the-maps-sdk-for-ios- "Direct link to Why use the Maps SDK for iOS ?") Magic Lane delivers a comprehensive mapping and navigation solution that transforms how developers build location-based applications. By combining cutting-edge features with robust performance, Magic Lane brings advanced navigation capabilities to life. ##### Core Mapping Features[​](#core-mapping-features "Direct link to Core Mapping Features") * **Global OpenStreetMap Coverage**: High-quality, continuously updated map data spanning the entire globe * **3D Terrain Visualization**: Immersive topographical modeling that brings landscapes to life * **Customizable Map Styling**: Full control over visual elements, colors, and design to match your brand identity * **Advanced Search**: Powerful POI discovery, address lookup, and geocoding capabilities ##### Navigation Excellence[​](#navigation-excellence "Direct link to Navigation Excellence") * **Multi-Modal Routing**: Optimized turn-by-turn directions for driving, walking, and cycling * **Multilingual Voice Guidance**: Clear audio navigation support in multiple languages * **Smart Traffic Integration**: Real-time traffic data and incident reports that dynamically adjust routes for optimal travel times * **Flexible Route Customization**: Tailor routing logic to meet specific use case requirements ##### Offline Capabilities[​](#offline-capabilities "Direct link to Offline Capabilities") * **Full Offline Support**: Download and store maps for uninterrupted service without connectivity * **Complete Feature Parity**: Mapping, search, routing, and navigation work seamlessly offline ##### Developer Experience[​](#developer-experience "Direct link to Developer Experience") * **Cross-Platform Compatibility**: Native support for Android and iOS platforms * **Lightweight Architecture**: Optimized performance ensures smooth operation on resource-constrained devices * **Comprehensive Documentation**: Detailed guides, sample code, best practices, and complete API references * **Flexible Integration**: Well-designed APIs and SDKs for straightforward implementation ##### Advanced Features[​](#advanced-features "Direct link to Advanced Features") * **Real-Time Updates**: Live traffic conditions and incident information for accurate, up-to-date routing With Magic Lane, developers gain access to enterprise-grade mapping technology that's both powerful and accessible, enabling the creation of sophisticated location-aware applications across any platform. These guides enable you to get quickly started using Magic Lane - Maps SDK for iOS to render your first interactive map, then plot a route, simulate navigation along the route, and search for points of interest. The **Magic Lane Studio** can be used to make custom map styles, and render GeoJSON data on your maps. --- ### Maps This section covers map-first workflows in the Maps SDK for iOS: creating map views, camera and gesture control, and displaying landmarks, markers, overlays, routes, instructions, paths, and map styles. Use these guides to move from a basic map setup to a fully interactive and styled map experience. #### [📄️Get started with maps](/docs/ios/guides/maps/get-started.md) [Learn how to display a map view and work with MapViewController, the core iOS map runtime controller.](/docs/ios/guides/maps/get-started.md) #### [📄️Adjust map view](/docs/ios/guides/maps/adjust-map.md) [The iOS SDK lets you control viewport framing, camera orientation, zoom, perspective, and coordinate transforms through MapViewController and MapViewPreferencesContext.](/docs/ios/guides/maps/adjust-map.md) #### [📄️Interact with map](/docs/ios/guides/maps/interact-with-map.md) [Use MapViewControllerDelegate, cursor selection APIs, and map-view preferences to handle user interactions, gestures, selections, and map render updates.](/docs/ios/guides/maps/interact-with-map.md) #### [🗃Display map items](/docs/ios/guides/maps/display-map-items.md) [6 items](/docs/ios/guides/maps/display-map-items.md) #### [📄️Styling](/docs/ios/guides/maps/styling.md) [Learn how to customize map appearance using predefined styles or custom styles created in Magic Lane Map Studio.](/docs/ios/guides/maps/styling.md) --- ### Adjust map view Last updated: April 16, 2026 | 4 minutes read

The iOS SDK lets you control viewport framing, camera orientation, zoom, perspective, and coordinate transforms through `MapViewController` and `MapViewPreferencesContext`. info Most code snippets in this guide use UIKit syntax, but the same APIs are available in SwiftUI via the `proxy.mapViewController` provided by `MapReader`. Some are also available via modifiers or `MapBase` initializers for quick view updates, as seen in [Get started with maps](/docs/ios/guides/maps/get-started.md). #### Get viewport metrics[​](#get-viewport-metrics "Direct link to Get viewport metrics") The viewport is returned in screen pixels and is relative to the map view's parent. ```swift let viewport = mapViewController.getViewport() let scale = mapViewController.getScaleFactor() let ppi = mapViewController.getPpiFactor() print("viewport=\(viewport) scale=\(scale) ppi=\(ppi)") ``` info Use pixel values when providing screen positions to map APIs such as centering with a custom point or cursor selection. #### Center the map[​](#center-the-map "Direct link to Center the map") Use the centering APIs based on the target object. ##### Center on coordinates[​](#center-on-coordinates "Direct link to Center on coordinates") ```swift let location = CoordinatesObject.coordinates(withLatitude: 48.8566, longitude: 2.3522) mapViewController.center(onCoordinates: location, zoomLevel: 68, animationDuration: 900) ``` ##### Center on a geographic area[​](#center-on-a-geographic-area "Direct link to Center on a geographic area") ```swift let center = CoordinatesObject.coordinates(withLatitude: 44.93343, longitude: 25.09946) let area = RectangleGeographicAreaObject(location: center, horizontalRadius: 3000, verticalRadius: 2000) mapViewController.center(onArea: area, zoomLevel: -1, animationDuration: 700) ``` ##### Center on an area with padding[​](#center-on-an-area-with-padding "Direct link to Center on an area with padding") Use edge area insets to reserve screen space, then center on the area. ```swift let center = CoordinatesObject.coordinates(withLatitude: 44.93343, longitude: 25.09946) let area = RectangleGeographicAreaObject(location: center, horizontalRadius: 3000, verticalRadius: 2000) mapViewController.setEdgeAreaInsets(UIEdgeInsets(top: 150, left: 100, bottom: 150, right: 100)) mapViewController.center(onArea: area, zoomLevel: -1, animationDuration: 700) // Optional: reset when you no longer need extra padding. mapViewController.setEdgeAreaInsets(.zero) ``` ##### Center on routes and route parts[​](#center-on-routes-and-route-parts "Direct link to Center on routes and route parts") ```swift mapViewController.center(onRoutes: [mainRoute, alternativeRoute], displayMode: .full, animationDuration: 900) let screenRect = CGRect(x: 40, y: 180, width: 320, height: 420) mapViewController.center(onRoute: mainRoute, startDist: 0, endDist: 12000, rectangle: screenRect, animationDuration: 700) ``` #### Convert screen and WGS coordinates[​](#convert-screen-and-wgs-coordinates "Direct link to Convert screen and WGS coordinates") Convert a tapped screen point to WGS and back: ```swift let point = CGPoint(x: 200, y: 320) let wgs = mapViewController.transformScreenToWgs(point) let screenPoint = mapViewController.transformWgsToScreen(wgs) print("wgs=\(wgs.latitude),\(wgs.longitude) screen=\(screenPoint)") ``` You can also convert a screen rectangle to geographic areas using `transformScreenRectToWgs(_:)`. #### Adjust zoom, angle, rotation, and perspective[​](#adjust-zoom-angle-rotation-and-perspective "Direct link to Adjust zoom, angle, rotation, and perspective") * UIKit * SwiftUI ```swift mapViewController.setZoomLevel(65, animationDuration: 400) mapViewController.setViewAngle(45) mapViewController.setTiltAngle(20) mapViewController.setPerspective(.view3D, animationDuration: 600) { success in print("3D perspective set: \(success)") } let preferences = mapViewController.getPreferences() preferences.setRotationAngle(35) ``` ```swift MapReader { proxy in MapBase() .onAppear { guard let mapViewController = proxy.mapViewController else { return } let preferences = mapViewController.getPreferences() mapViewController.setZoomLevel(65, animationDuration: 400) mapViewController.setViewAngle(45) mapViewController.setTiltAngle(20) mapViewController.setPerspective(.view3D, animationDuration: 600) { success in print("3D perspective set: \(success)") } preferences.setRotationAngle(35) } } ``` Here are some videos demonstrating the rotation and tilt angle changes. **Tilt angle & view angle** **Rotation angle**
danger Do not confuse zoom level with map style detail quality. Zoom controls camera distance, while map detail quality is controlled separately with `setMapDetailsQualityLevel(_:)`. #### Store and restore camera state[​](#store-and-restore-camera-state "Direct link to Store and restore camera state") Use `MapCameraObject` when you need to persist camera position and orientation. ```swift if let camera = mapViewController.getMapCamera(), let savedState = camera.saveCameraState() { UserDefaults.standard.set(savedState, forKey: "savedMapCamera") } if let camera = mapViewController.getMapCamera(), let data = UserDefaults.standard.data(forKey: "savedMapCamera") { _ = camera.restoreCameraState(data) } ``` #### Customize follow position camera[​](#customize-follow-position-camera "Direct link to Customize follow position camera") ```swift let preferences = mapViewController.getPreferences() let follow = preferences.getFollowPositionPreferences() follow.setCameraFocus(CGPoint(x: 0.5, y: 0.5)) follow.setZoomLevel(70, animationDuration: 0) follow.setMapRotationMode(.positionHeading, angle: 0, objectFollowMap: true) // Keep the changes made by touch gestures while following the position, instead of resetting to the // default zoom and angle after exiting the following mode follow.setTouchHandlerModifyPersistent(true) ``` #### Relevant examples demonstrating map view adjustment related features[​](#relevant-examples-demonstrating-map-view-adjustment-related-features "Direct link to Relevant examples demonstrating map view adjustment related features") * [Map Perspective](/docs/ios/examples/maps-3dscene/map-perspective.md) * [Following Position](/docs/ios/examples/maps-3dscene/following-position.md) * [Projections](/docs/ios/examples/maps-3dscene/projections.md) * [Map Download](/docs/ios/examples/maps-3dscene/map-download.md) --- ### Display map items This section covers how to render and manage map entities in iOS: landmarks, markers, overlays, routes, route instructions, and paths. #### [📄️Display landmarks](/docs/ios/guides/maps/display-map-items/display-landmarks.md) [Learn how to filter, add, and highlight landmarks on the map.](/docs/ios/guides/maps/display-map-items/display-landmarks.md) #### [📄️Display markers](/docs/ios/guides/maps/display-map-items/display-markers.md) [Learn how to add and style point, polyline, and polygon markers, including labeling and clustering behavior.](/docs/ios/guides/maps/display-map-items/display-markers.md) #### [📄️Display overlays](/docs/ios/guides/maps/display-map-items/display-overlays.md) [Overlays provide enhanced, layered information on top of your base map. They offer dynamic, interactive, and customized data that adds contextual value to map elements.](/docs/ios/guides/maps/display-map-items/display-overlays.md) #### [📄️Display routes](/docs/ios/guides/maps/display-map-items/display-routes.md) [Learn how to display routes on the map, assign the main route, style route rendering, and manage route labels.](/docs/ios/guides/maps/display-map-items/display-routes.md) #### [📄️Display route instructions](/docs/ios/guides/maps/display-map-items/display-route-instructions.md) [Learn how to focus the map on route instructions and show maneuver arrows during route visualization.](/docs/ios/guides/maps/display-map-items/display-route-instructions.md) #### [📄️Display paths](/docs/ios/guides/maps/display-map-items/display-paths.md) [Learn how to render PathObject instances on the map using PathCollectionObject.](/docs/ios/guides/maps/display-map-items/display-paths.md) --- ### Display landmarks Last updated: April 16, 2026 | 4 minutes read

Learn how to filter, add, and highlight landmarks on the map. #### Filter landmarks by category[​](#filter-landmarks-by-category "Direct link to Filter landmarks by category") Use `LandmarkStoreContextCollection` from `MapViewPreferencesContext` to control which store/category combinations are visible. ```swift guard let stores = mapViewController.getPreferences().getLandmarkStoreCollection() else { return } // Remove all currently displayed generic POI categories. let genericCategories = GenericCategoriesContext() let genericStoreId = genericCategories.getLandmarkStoreId() _ = stores.removeAllStoreCategories(genericStoreId) // Display only gas stations from generic categories. _ = stores.addStoreCategoryId(genericStoreId, categoryId: Int32(GenericCategoryType.gasStation.rawValue)) ``` #### Add custom landmarks[​](#add-custom-landmarks "Direct link to Add custom landmarks") Create a custom landmark, add it to a landmark store, then make that store visible. ```swift let customStore = LandmarkStoreContext(name: "custom-landmarks") let landmark = LandmarkObject.landmark( withName: "Charging stop", location: CoordinatesObject.coordinates(withLatitude: 52.3780, longitude: 4.9008) ) landmark.setLandmarkDescription("Fast charging station") _ = customStore.addLandmark(landmark) if let stores = mapViewController.getPreferences().getLandmarkStoreCollection() { _ = stores.addAllStoreCategories(customStore.getId()) } ``` info Initializing a `LandmarkStoreContext` will create a file inside the app's sandboxed file system if it doesn't already exist. ![](/docs/ios/assets/images/ios_maps_customlandmark-929435e3b5a5a00c4fa7ad78579e509d.png) **Displayed Custom Landmark** Registering a landmark store from an external file is also possible with `LandmarkStoreContextService`: ```swift func registerFromURL(url: URL) { let name = "LandmarkStoreFromURL" let landmarkStoreService = LandmarkStoreContextService() let identifier = landmarkStoreService.registerLandmarkStoreContext(name, path: url.relativePath) if identifier > 0 { let landmarkStore = landmarkStoreService.getLandmarkStoreContext(withIdentifier: Int32(identifier)) // Use landmarkStore as needed, for example to add landmarks or retrieve data } } ``` #### Highlight landmarks[​](#highlight-landmarks "Direct link to Highlight landmarks") Highlights allow you to customize landmarks, making them more visible and providing render settings options. By default, highlighted landmarks are not selectable but can be made selectable if necessary. Highlighting a landmark allows you to: * Customize its appearance * Temporarily isolate it from standard interactions (default behavior, can be modified) Tip Landmarks retrieved through search can be highlighted to enhance their prominence and customize their appearance. Custom landmarks can also be highlighted, but must be added to a `LandmarkStore` first. ```swift let settings = HighlightRenderSettings() settings.showPin = true settings.imageSize = 7 settings.options = Int32( HighlightOption.showLandmark.rawValue | HighlightOption.overlap.rawValue | HighlightOption.noFading.rawValue ) mapViewController.presentHighlights([landmark], settings: settings, highlightId: 2) ``` ##### Highlight options[​](#highlight-options "Direct link to Highlight options") The `HighlightOption` enum provides options to customize highlighted landmark behavior: | Option | iOS enum case | Description | | -------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `showLandmark` | `HighlightOption.showLandmark` | Shows the landmark icon and text. Enabled by default. | | `showContour` | `HighlightOption.showContour` | Shows the landmark contour area when available. Enabled by default. | | `group` | `HighlightOption.group` | Groups landmarks in close proximity. Available only with `showLandmark`. Disabled by default. | | `overlap` | `HighlightOption.overlap` | Overlaps highlight over existing map data. Available only with `showLandmark`. Disabled by default. | | `noFading` | `HighlightOption.noFading` | Disables highlight fading in/out. Available only with `showLandmark`. Disabled by default. | | `bubble` | `HighlightOption.bubble` | Displays highlights in a bubble with custom icon placement. Available only with `showLandmark`. Automatically invalidates `group`. Disabled by default. | | `selectable` | `HighlightOption.selectable` | Makes highlights selectable from cursor/touch selection flow. Available only with `showLandmark`. Disabled by default. | danger When showing bubble highlights, if the whole bubble does not fit on the screen, it will not be displayed at all. Make sure to truncate the text if the text length is very long. danger For a landmark contour to be displayed, the landmark must have a valid contour area. Landmarks with a polygon representation on OpenStreetMap will have a contour area. Make sure the landmarks you want to highlight with contours have valid contour data with `isContourGeograficAreaEmpty()` ##### Remove highlights[​](#remove-highlights "Direct link to Remove highlights") ```swift // remove highlights with a specific ID mapViewController.removeHighlight(2) // remove all highlights mapViewController.removeHighlights() ``` #### Remove landmark categories from map[​](#remove-landmark-categories-from-map "Direct link to Remove landmark categories from map") ```swift let genericStoreId = GenericCategoriesContext().getLandmarkStoreId() _ = mapViewController.getPreferences() .getLandmarkStoreCollection()? .removeAllStoreCategories(genericStoreId) ``` #### Relevant examples demonstrating landmarks related features[​](#relevant-examples-demonstrating-landmarks-related-features "Direct link to Relevant examples demonstrating landmarks related features") * [Map Selection](/docs/ios/examples/maps-3dscene/map-selection.md) * [Search On Map](/docs/ios/examples/places-search/search-on-map.md) --- ### Display markers Last updated: April 16, 2026 | 4 minutes read

Learn how to add and style point, polyline, and polygon markers, including labeling and clustering behavior. #### Overview[​](#overview "Direct link to Overview") `MarkerObject` stores coordinates grouped into marker parts. Coordinates in the same part are connected for polyline/polygon marker collections, while different parts stay independent. You can append coordinates in order or insert at a specific index. **Code used for creating a marker with coordinates added to the same part:** ```swift let marker = MarkerObject(coordinates: [ CoordinatesObject.coordinates(withLatitude: 52.1459, longitude: 1.0613), CoordinatesObject.coordinates(withLatitude: 52.14569, longitude: 1.0615), CoordinatesObject.coordinates(withLatitude: 52.14585, longitude: 1.06186), CoordinatesObject.coordinates(withLatitude: 52.14611, longitude: 1.06215) ]) ``` **Code used for creating a marker with coordinates separated into different parts:** ```swift let marker = MarkerObject(coordinates: [ CoordinatesObject.coordinates(withLatitude: 52.1459, longitude: 1.0613), CoordinatesObject.coordinates(withLatitude: 52.14569, longitude: 1.0615) ]) marker.add( CoordinatesObject.coordinates(withLatitude: 52.14585, longitude: 1.06186), index: -1, part: 1 ) marker.add( CoordinatesObject.coordinates(withLatitude: 52.14611, longitude: 1.06215), index: -1, part: 1 ) ``` ![](/docs/ios/assets/images/ios_maps_markers1-42ec431370779e1398f7651a688eb74d.png) **Polyline Markers with one part** ![](/docs/ios/assets/images/ios_maps_markers2-8553cdd5c0105571f56e8e5194f67b15.png) **Polyline Markers with coordinates added to separate parts** #### Add markers to the map[​](#add-markers-to-the-map "Direct link to Add markers to the map") Create `MarkerObject` instances and group them in a `MarkerCollectionObject`, then add the collection to the map. ```swift let marker = MarkerObject(coordinates: [ CoordinatesObject.coordinates(withLatitude: 52.14590, longitude: 1.06130), CoordinatesObject.coordinates(withLatitude: 52.14569, longitude: 1.06150), CoordinatesObject.coordinates(withLatitude: 52.14579, longitude: 1.06180), ]) let collection = MarkerCollectionObject(name: "points", type: .point) collection.addMarker(marker) mapViewController.addMarker(collection) ``` ![](/docs/ios/assets/images/ios_maps_markers3-7f1c50dd4738b7e1f306c287cff3630a.png) **Point Markers** For `MarkerCollectionObject`, choose a marker type that matches your geometry: `.point`, `.polyline`, `.polygon`, or `.area`. #### Marker types[​](#marker-types "Direct link to Marker types") * `MarkerCollectionTypePoint` for point markers * `MarkerCollectionTypePolyline` for line markers * `MarkerCollectionTypePolygon` for filled polygons * `MarkerCollectionTypeArea` for top-level area overlays ##### Point markers[​](#point-markers "Direct link to Point markers") Point markers are rendered as icons and can be labeled or grouped at configured zoom levels. ##### Polyline markers[​](#polyline-markers "Direct link to Polyline markers") Polyline markers render connected segments for coordinates in the same part. ##### Polygon markers[​](#polygon-markers "Direct link to Polygon markers") Polygon markers render filled areas when each part has at least three coordinates. warning If a polygon part has fewer than three coordinates, it is rendered as an open polyline. ##### Polyline and polygon example[​](#polyline-and-polygon-example "Direct link to Polyline and polygon example") ```swift let line = MarkerObject(coordinates: [ CoordinatesObject.coordinates(withLatitude: 52.360495, longitude: 4.936882), CoordinatesObject.coordinates(withLatitude: 52.360495, longitude: 4.836882), CoordinatesObject.coordinates(withLatitude: 52.410495, longitude: 4.886882), ]) // change type to .polyline for open polyline rendering let lines = MarkerCollectionObject(name: "polyExample", type: .polygon) lines.addMarker(line) lines.setInnerColor(.systemRed) lines.setOuterColor(.black) lines.setInnerSize(1.0) lines.setOuterSize(1.4) mapViewController.addMarker(lines) ``` ![](/docs/ios/assets/images/ios_maps_markers4-bd741b393036724a9994db080ada0fdb.png) **Rendering Markers as a Polygon** #### Customize marker appearance[​](#customize-marker-appearance "Direct link to Customize marker appearance") You can style a collection directly or pass `MarkerCollectionRenderSettingsObject` when adding. `MarkerCollectionRenderSettingsObject` supports type-specific customization: * **Point markers**: `pointImage`, `imageSize`, `labelTextSize`, `labelTextColor`, `labelingMode` * **Polyline markers**: `polylineInnerColor`, `polylineOuterColor`, `polylineInnerSize`, `polylineOuterSize` * **Polygon markers**: `polygonFillColor` All size values are in millimeters. info Properties not applicable to the collection type are ignored. ```swift let settings = MarkerCollectionRenderSettingsObject() settings.pointImage = UIImage(systemName: "mappin.circle.fill") settings.imageSize = 4.0 settings.labelTextSize = 2.6 settings.labelingMode = NSNumber( value: MarkerLabelingMode.item.rawValue | MarkerLabelingMode.textAbove.rawValue ) settings.pointsGroupingZoomLevel = 70 mapViewController.addMarker(collection, renderSettingsObject: settings) ``` ##### Labeling and clustering[​](#labeling-and-clustering "Direct link to Labeling and clustering") ```swift let clusteredCollection = MarkerCollectionObject(name: "poi-clustered", type: .point) for index in 0..<40 { let latitude = 52.3676 + Double(index % 8) * 0.0012 let longitude = 4.9041 + Double(index / 8) * 0.0012 let point = MarkerObject(coordinates: [ CoordinatesObject.coordinates(withLatitude: latitude, longitude: longitude) ]) clusteredCollection.addMarker(point) } let clusteringSettings = MarkerCollectionRenderSettingsObject() clusteringSettings.imageSize = 3.8 clusteringSettings.labelTextSize = 2.4 clusteringSettings.labelGroupTextSize = 2.6 clusteringSettings.labelingMode = NSNumber( value: MarkerLabelingMode.item.rawValue | MarkerLabelingMode.group.rawValue | MarkerLabelingMode.textAbove.rawValue ) clusteringSettings.pointsGroupingZoomLevel = 70 mapViewController.addMarker(clusteredCollection, renderSettingsObject: clusteringSettings) // Compare behavior around the grouping threshold. let focus = CoordinatesObject.coordinates(withLatitude: 52.3690, longitude: 4.9060) mapViewController.center(onCoordinates: focus, zoomLevel: 72, animationDuration: 0) ``` warning Set `pointsGroupingZoomLevel` to `0` to disable grouping for that collection. Be careful though, as a large number of visible point markers may impact performance. #### Remove markers[​](#remove-markers "Direct link to Remove markers") ```swift mapViewController.removeMarker(collection) mapViewController.removeAllMarkers() let current = mapViewController.getAvailableMarkers() print("marker collections on map: \(current.count)") ``` #### Relevant examples demonstrating markers related features[​](#relevant-examples-demonstrating-markers-related-features "Direct link to Relevant examples demonstrating markers related features") * [Shapes](/docs/ios/examples/maps-3dscene/shapes.md) --- ### Display overlays Last updated: April 17, 2026 | 2 minutes read

Overlays provide enhanced, layered information on top of your base map. They offer dynamic, interactive, and customized data that adds contextual value to map elements. #### Get available overlays[​](#get-available-overlays "Direct link to Get available overlays") Use `OverlayServiceContext` to retrieve the current overlay catalog. ```swift let overlayService = OverlayServiceContext() if let collection = overlayService.getAvailableOverlays() { for overlay in collection.getOverlays() { print("overlay id=\(overlay.getUid()) name=\(overlay.getName())") } } ``` Disable overlays by applying a custom map style from [Magic Lane Map Studio](https://developer.magiclane.com/docs/guides/map-studio/create-a-map-style/) with certain overlays disabled, or by using the `disableOverlay` method: #### Disable and enable overlays[​](#disable-and-enable-overlays "Direct link to Disable and enable overlays") Disable or enable whole overlays by UID. ```swift let overlayService = OverlayServiceContext() let publicTransportOverlayId = Int32(CommonOverlayIdentifier.publicTransport.rawValue) let disableResult = overlayService.disableOverlay(publicTransportOverlayId) print("disable result: \(disableResult)") let enableResult = overlayService.enableOverlay(publicTransportOverlayId) print("enable result: \(enableResult)") ``` Disable a specific category by providing both overlay and category IDs: ```swift if let overlays = overlayService.getAvailableOverlays(), let overlay = overlays.getOverlayAt(0), let category = overlay.getCategories().first { _ = overlayService.disableOverlay(overlay.getUid(), category: category.getUid()) } ``` danger To disable or enable an overlay the SDK must be online. If the SDK is offline, the methods will return `SDKErrorCodeNotFound`. Check with `isOnlineConnection()` from `GEMSdk` before calling these methods. #### Check overlay state[​](#check-overlay-state "Direct link to Check overlay state") ```swift let enabled = overlayService.isOverlayEnabled(publicTransportOverlayId) print("overlay enabled: \(enabled)") ``` #### Select overlay items on map[​](#select-overlay-items-on-map "Direct link to Select overlay items on map") Use cursor-based selection from `MapViewController`: ```swift let selectedOverlays = mapViewController.getCursorSelectionOverlayItems() for item in selectedOverlays { print("item id=\(item.getUid()) name=\(item.getName())") } ``` You can also handle overlay selection in `MapViewControllerDelegate` using `didSelectOverlays` callbacks. #### Relevant examples demonstrating overlays related features[​](#relevant-examples-demonstrating-overlays-related-features "Direct link to Relevant examples demonstrating overlays related features") * [Map Selection](/docs/ios/examples/maps-3dscene/map-selection.md) --- ### Display paths Last updated: April 16, 2026 | 2 minutes read

Learn how to render `PathObject` instances on the map using `PathCollectionObject`. #### Add paths to the map[​](#add-paths-to-the-map "Direct link to Add paths to the map") Display [Path](/docs/ios/guides/core/base-entities.md#path) objects by adding them to the map path collection available from `MapViewPreferencesContext`. `PathCollectionObject` is an iterable collection with utility methods such as `size`, `add`, `remove`, `removeAt`, `getPathAt`, and `getPathByName`. ```swift guard let paths = mapViewController.getPreferences().getPaths() else { return } let path = PathObject(coordinates: [ CoordinatesObject.coordinates(withLatitude: 52.3676, longitude: 4.9041), CoordinatesObject.coordinates(withLatitude: 52.3610, longitude: 4.9156), CoordinatesObject.coordinates(withLatitude: 52.3540, longitude: 4.9230) ]) path.setName("Recorded track") _ = paths.add( path, colorBorder: .black, colorInner: .red, szBorder: 1.2, szInner: 0.7 ) ``` ![](/docs/ios/assets/images/ios_maps_paths-8133ef9a410d29359dde16076854c644.png) **Simple example path from coordinates** ##### Customize path appearance[​](#customize-path-appearance "Direct link to Customize path appearance") When adding a path, you can configure border/inner colors and sizes using `colorBorder`, `colorInner`, `szBorder`, and `szInner`. #### Center on path[​](#center-on-path "Direct link to Center on path") Center the map on a path by reading its geographic area and passing it to `center(onArea:zoomLevel:animationDuration:)`. ```swift if let area = path.getArea() { mapViewController.center(onArea: area, zoomLevel: -1, animationDuration: 700) } ``` #### Access paths in collection[​](#access-paths-in-collection "Direct link to Access paths in collection") Read paths from the collection by index or name, and use `size()` to inspect how many paths are currently displayed. ```swift guard let paths = mapViewController.getPreferences().getPaths() else { return } let count = paths.size() let first = paths.getPathAt(0) let byName = paths.getPathByName("Recorded track") print("count=\(count) first=\(first != nil) byName=\(byName != nil)") ``` #### Remove paths[​](#remove-paths "Direct link to Remove paths") Use `remove(_:)` to delete a specific path instance, `remove(at:)` to delete by index, or `clear()` to remove all paths. ```swift guard let paths = mapViewController.getPreferences().getPaths() else { return } _ = paths.remove(path) _ = paths.remove(at: 0) paths.clear() ``` #### Relevant examples demonstrating paths related features[​](#relevant-examples-demonstrating-paths-related-features "Direct link to Relevant examples demonstrating paths related features") * [Finger Draw Route](/docs/ios/examples/routing-navigation/finger-drawn-route-navigation.md) --- ### Display route instructions Last updated: April 16, 2026 | 1 minute read

Learn how to focus the map on route instructions and show maneuver arrows during route visualization. #### Center on route instructions[​](#center-on-route-instructions "Direct link to Center on route instructions") Get instructions from route segments, then center on the desired instruction. A simplified example is shown below. For more details on obtaining a route's instructions see the [Get the route segments and instructions](/docs/ios/guides/routing/get-started-routing.md#retrieve-route-instructions) section. ```swift if let firstSegment = route.getSegments().first { if let instruction = firstSegment.getInstructions().first { mapViewController.center(onRouteInstruction: instruction, zoomLevel: 75, animationDuration: 700) } } ``` #### Remove instruction[​](#remove-instruction "Direct link to Remove instruction") The route instruction arrow is automatically cleared when a new route instruction is centered on or when the route is cleared. If you want to clear the instruction manually, center on an empty instruction object, like this: ```swift mapViewController.center(onRouteInstruction: RouteInstructionObject.init(), zoomLevel: -1, animationDuration: 0) ``` #### Relevant examples demonstrating route instruction related features[​](#relevant-examples-demonstrating-route-instruction-related-features "Direct link to Relevant examples demonstrating route instruction related features") * [Route Instructions](/docs/ios/examples/routing-navigation/route-instructions.md) * [Public Transit Route Instructions](/docs/ios/examples/routing-navigation/public-transit-route-instructions.md) --- ### Display routes Last updated: April 16, 2026 | 3 minutes read

Learn how to display routes on the map, assign the main route, style route rendering, and manage route labels. info This page focuses on rendering already computed routes. For route calculation APIs, see the routing guides. #### Add routes to the map[​](#add-routes-to-the-map "Direct link to Add routes to the map") Add routes with methods called directly on `MapViewController`: ```swift mapViewController.presentRoutes([mainRoute, alternativeRoute], withTraffic: nil, showSummary: true, animationDuration: 500) // To set the main route explicitly: mapViewController.setMainRoute(alternativeRoute) ``` ![](/docs/ios/assets/images/ios_maps_routes1-26892523a3a6b376113791640009495c.png) **Presenting routes through MapViewController with default summary** info When presenting the routes through `MapViewController`, the first route will be set as the main route by default and the routes will be centered on. Or add routes through `MapViewPreferencesContext`. ```swift let preferences = mapViewController.getPreferences() _ = preferences.addRoute(mainRoute, isMainRoute: true, label: "Fastest", images: nil) _ = preferences.addRoute(alternativeRoute, isMainRoute: false, label: "Alternative", images: nil) // To set the main route explicitly: preferences.setMainRoute(alternativeRoute) let currentMain = preferences.getMainRoute() print("main route updated: \(currentMain != nil)") mapViewController.center(onRoutes: [mainRoute, alternativeRoute], displayMode: .full, animationDuration: 500) ``` ![](/docs/ios/assets/images/ios_maps_routes2-fa40611fff7f01f1153be376e7969c3a.png) **Presenting routes through MapViewPreferencesContext with custom labels** warning Only one route can be marked as main at a time. #### Customize route appearance[​](#customize-route-appearance "Direct link to Customize route appearance") Customize route rendering with `MapViewRouteRenderSettings`. ```swift let preferences = mapViewController.getPreferences() let settings = preferences.getRenderSettings(mainRoute) settings.options = Int32( MapViewRouteRenderOption.main.rawValue | MapViewRouteRenderOption.showTraffic.rawValue ) settings.contourInnerColor = .red settings.contourOuterColor = .white settings.contourInnerSize = 1.2 settings.contourOuterSize = 2.0 preferences.setRenderSettings(settings, route: mainRoute) ``` All route dimensional values in `MapViewRouteRenderSettings` are measured in millimeters. ![](/docs/ios/assets/images/ios_maps_routes3-6e97fa8b4737f5c218065b81fdcf4e99.png) **Customizing route rendering with MapViewRouteRenderSettings** #### Add and update route labels[​](#add-and-update-route-labels "Direct link to Add and update route labels") ```swift let preferences = mapViewController.getPreferences() preferences.setRouteLabel(mainRoute, label: "ETA 12 min %%0%%") preferences.setRouteImages(mainRoute, images: [UIImage(systemName: "clock.fill")!.withTintColor(.white)]) let label = preferences.getRouteLabel(mainRoute) print("route label: \(label ?? "")") ``` Hide a label: ```swift preferences.hideRouteLabel(mainRoute) ``` ![](/docs/ios/assets/images/ios_maps_routes4-ad061b252433c59dc9d76609e8942177.png) **Setting a custom label with an image** #### Check visible route interval[​](#check-visible-route-interval "Direct link to Check visible route interval") Retrieve the visible portion of a route - defined by its start and end distances in meters - using the `getVisibleRouteInterval` method from `MapViewController`: ```swift let interval = mapViewController.getVisibleRouteInterval(mainRoute, rect: .zero) if interval.count == 2 { let startMeters = interval[0].intValue let endMeters = interval[1].intValue print("visible route meters: \(startMeters)-\(endMeters)") } ``` #### Remove routes[​](#remove-routes "Direct link to Remove routes") ```swift // Using MapViewController mapViewController.removeRoutes([alternativeRoute]) mapViewController.removeAllRoutes() // Using preferences let preferences = mapViewController.getPreferences() preferences.removeRoute(alternativeRoute) preferences.clearRoutes() ``` #### Relevant examples demonstrating route rendering related features[​](#relevant-examples-demonstrating-route-rendering-related-features "Direct link to Relevant examples demonstrating route rendering related features") * [Finger Draw Route](/docs/ios/examples/routing-navigation/finger-drawn-route-navigation.md) * [Range](/docs/ios/examples/maps-3dscene/range.md) --- ### Get started with maps Last updated: April 16, 2026 | 2 minutes read

Learn how to display a map view and work with `MapViewController`, the core iOS map runtime controller. #### Display a map[​](#display-a-map "Direct link to Display a map") `MapViewController` is the UIKit map controller. In SwiftUI, use `MapBase` inside `MapReader`. * UIKit * SwiftUI ```swift final class MapHostViewController: UIViewController, MapViewControllerDelegate { private var mapViewController: MapViewController? override func viewDidLoad() { super.viewDidLoad() let controller = MapViewController() controller.delegate = self controller.view.backgroundColor = .systemBackground mapViewController = controller addChild(controller) view.addSubview(controller.view) controller.didMove(toParent: self) controller.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ controller.view.topAnchor.constraint(equalTo: view.topAnchor), controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), controller.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), controller.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), ]) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) mapViewController?.startRender() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) mapViewController?.stopRender() } deinit { mapViewController?.destroy() } func mapViewControllerReady(_ mapViewController: MapViewController) { // Map is ready for interaction } } ``` ```swift import SwiftUI struct MapHostView: View { @Environment(\.scenePhase) private var scenePhase @State private var isAppActive = true var body: some View { MapReader { proxy in MapBase() .mapRender(isAppActive) .ignoresSafeArea() } .onChange(of: scenePhase) { newPhase in switch newPhase { case .active: isAppActive = true case .background: isAppActive = false default: break } } } } ``` ![](/docs/ios/assets/images/ios_maps_displaymap-ecc0b905fdc88d7f1ba641205ac8a8af.png) **Default Map View** #### Use map preferences[​](#use-map-preferences "Direct link to Use map preferences") Use `getPreferences()` for style, gestures, cursor, route rendering, landmark visibility, and other view-level settings. * UIKit * SwiftUI ```swift guard let mapViewController, let preferences = mapViewController.getPreferences() else { return } preferences.enableCursor(true) preferences.enableCursorRender(true) mapViewController.setBuildingsVisibility(.visibilityHide) ``` ```swift MapReader { proxy in MapBase() .mapBuildings(.visibilityDefault) .mapTraffic(false) .onAppear { let preferences = proxy.mapViewController?.getPreferences() preferences?.enableCursor(true) preferences?.enableCursorRender(true) } } ``` Tip You can instantiate multiple `MapViewController` instances or multiple `MapBase` views in one app. Some SDK-level settings are still global (for example language and unit system), while map view preferences are per-controller. #### Relevant examples demonstrating map related features[​](#relevant-examples-demonstrating-map-related-features "Direct link to Relevant examples demonstrating map related features") * [Hello Map](/docs/ios/examples/maps-3dscene/hello-map.md) * [Map Compass](/docs/ios/examples/maps-3dscene/map-compass.md) * [Map Perspective](/docs/ios/examples/maps-3dscene/map-perspective.md) * [Multi Map](/docs/ios/examples/maps-3dscene/multi-map.md) * [Map Render](/docs/ios/examples/maps-3dscene/map-render.md) --- ### Interact with map Last updated: March 25, 2026 | 3 minutes read

Use `MapViewControllerDelegate`, cursor selection APIs, and map-view preferences to handle user interactions, gestures, selections, and map render updates. #### Understand gesture and event handling[​](#understand-gesture-and-event-handling "Direct link to Understand gesture and event handling") The map handles common gestures (tap, double-tap, pan, pinch, rotate, shove) natively. To react to user interaction, implement callbacks in `MapViewControllerDelegate`. | Interaction | Delegate callback (`MapViewControllerDelegate`) | | -------------- | ----------------------------------------------------------------- | | Tap | `mapViewController:onTouchPoint:` | | Long press | `mapViewController:onLongTouchPoint:` | | Double tap | `mapViewController:onDoubleTouch:` | | Two-finger tap | `mapViewController:onTwoTouches:` | | Pan | `mapViewController:onMovePoint:toPoint:` | | Pinch | `mapViewController:onPinch:startPoint2:toPoint1:toPoint2:center:` | | Shove | `mapViewController:onShove:initial:start:end:` | Gesture behavior explained: * **Tap**: Tap the screen with one finger. This gesture does not have a predefined map action. * **Long press**: Press and hold one finger on the screen. This gesture does not have a predefined map action. * **Double tap**: Zoom the map in by a fixed amount by tapping twice with one finger. * **2 finger tap**: Align the map to north by tapping the screen with two fingers. * **Pan**: Move the map by pressing and holding one finger, then dragging in any direction. The map continues with slight momentum after release. * **Pinch**: Zoom in or out continuously by changing the distance between two fingers. You can also rotate continuously by changing the angle between two fingers. * **Shove**: With two fingers on the map, move both fingers together in parallel (typically up or down) to change the camera tilt angle while keeping the map center stable. info Gesture enable/disable is controlled per map using `MapViewPreferencesContext`. #### Enable and disable gestures[​](#enable-and-disable-gestures "Direct link to Enable and disable gestures") ```swift let preferences = mapViewController.getPreferences() _ = preferences.enableTouchGesture(.onMove, enable: false) _ = preferences.enableTouchGesture(.onPinch, enable: true) let enabledGestures = MapViewTouchGestures.onRotate.rawValue | MapViewTouchGestures.onShove.rawValue _ = preferences.enableTouchGestures(Int32(enabledGestures), enable: true) let rotateEnabled = preferences.isTouchGestureEnabled(.onRotate) print("Rotate gesture enabled: \(rotateEnabled)") ``` #### Select map elements[​](#select-map-elements "Direct link to Select map elements") Use cursor-based selection when you already have a screen point (for example from your own gesture recognizer). ```swift func selectElements(at point: CGPoint, mapViewController: MapViewController) { mapViewController.setCursorPosition(point) let landmarks = mapViewController.getCursorSelectionLandmarks() let streets = mapViewController.getCursorSelectionStreets() let overlays = mapViewController.getCursorSelectionOverlayItems() let routes = mapViewController.getCursorSelectionRoutes() let trafficEvents = mapViewController.getCursorSelectionTrafficEvents() print("landmarks=\(landmarks.count) streets=\(streets.count) overlays=\(overlays.count)") print("routes=\(routes.count) traffic=\(trafficEvents.count)") } ``` You can also receive selection directly from delegate callbacks such as: | Selection type | Delegate callback (`MapViewControllerDelegate`) | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Landmarks | `mapViewController:didSelectLandmarks:onTouchPoint:` and `mapViewController:didSelectLandmarks:onLongTouchPoint:` | | Streets | `mapViewController:didSelectStreets:onTouchPoint:` and `mapViewController:didSelectStreets:onLongTouchPoint:` | | Routes | `mapViewController:didSelectRoutes:onTouchPoint:` | | Overlays | `mapViewController:didSelectOverlays:`, `mapViewController:didSelectOverlays:onTouchPoint:`, and `mapViewController:didSelectOverlays:onLongTouchPoint:` | | Traffic events | `mapViewController:didSelectTrafficEvents:`, `mapViewController:didSelectTrafficEvents:onTouchPoint:`, and `mapViewController:didSelectTrafficEvents:onLongTouchPoint:` | danger Avoid heavy work inside touch or render callbacks. Long-running operations can reduce map responsiveness. #### Observe render and camera movement[​](#observe-render-and-camera-movement "Direct link to Observe render and camera movement") Use render callbacks to react to loading completion or camera movement status. ```swift mapViewController.setOnMapViewRendered { dataStatus, cameraStatus in if dataStatus == .complete && cameraStatus == .stationary { print("Map is fully rendered and camera is stationary") } } ``` You can also use delegate methods: * `mapViewController:willRender:` * `mapViewController:didRender:cameraTransitionStatus:` #### Capture the map as an image[​](#capture-the-map-as-an-image "Direct link to Capture the map as an image") ```swift let targetSize = CGSize(width: 1080, height: 1920) // Use CGRect.zero to capture the entire map view let image = mapViewController.snapshotImage(withSize: targetSize, captureRect: .zero) if image == nil { print("Could not capture map image") } ``` danger Capturing the map view may not work correctly when map rendering is disabled. #### Relevant examples demonstrating map interaction related features[​](#relevant-examples-demonstrating-map-interaction-related-features "Direct link to Relevant examples demonstrating map interaction related features") * [Map Selection](/docs/ios/examples/maps-3dscene/map-selection.md) * [Projections](/docs/ios/examples/maps-3dscene/projections.md) * [Map Render](/docs/ios/examples/maps-3dscene/map-render.md) * [Map Update](/docs/ios/examples/maps-3dscene/map_update.md) --- ### Styling Last updated: April 16, 2026 | 2 minutes read

Learn how to customize map appearance using predefined styles or custom styles created in [Magic Lane Map Studio](https://developer.magiclane.com/docs/guides/map-studio/create-a-map-style/). #### Apply predefined styles[​](#apply-predefined-styles "Direct link to Apply predefined styles") ##### Retrieve available styles[​](#retrieve-available-styles "Direct link to Retrieve available styles") Use `MapStyleContext` to fetch online style entries and inspect local styles. ```swift let styleContext = MapStyleContext() styleContext.getOnlineList { styles in for style in styles { print("id=\(style.getIdentifier()) name=\(style.getName()) type=\(style.getType())") } } let styles = styleContext.getLocalList() for style in styles { print("id=\(style.getIdentifier()) name=\(style.getName()) type=\(style.getType())") } ``` ##### Download a style[​](#download-a-style "Direct link to Download a style") ```swift styleContext.downloadStyle(withIdentifier: styleId, allowCellularNetwork: true) { success in print("style download success: \(success)") } ``` ##### Apply the downloaded style[​](#apply-the-downloaded-style "Direct link to Apply the downloaded style") Apply by identifier with either `MapViewController` or preferences: ```swift mapViewController.applyStyle(withStyleIdentifier: styleId, smoothTransition: true) ``` info Two predefined online style categories are available in content store APIs: * `ContentStoreOnlineTypeViewStyleHighRes` * `ContentStoreOnlineTypeViewStyleLowRes` #### Apply custom styles[​](#apply-custom-styles "Direct link to Apply custom styles") Load a `.style` file from your app bundle and apply it using style data. ```swift guard let url = Bundle.main.url(forResource: "CustomMapStyle", withExtension: "style"), let data = try? Data(contentsOf: url) else { return } mapViewController.applyStyle(withStyleBuffer: data, smoothTransition: true) ``` You can also apply by file path: ```swift if let path = Bundle.main.path(forResource: "CustomMapStyle", ofType: "style") { mapViewController.applyStyle(withFilePath: path, smoothTransition: true) } ``` ![](/docs/ios/assets/images/ios_maps_displaymap-ecc0b905fdc88d7f1ba641205ac8a8af.png) **Default Map Style** ![](/docs/ios/assets/images/ios_maps_customstyle-89ba891acfa5be1f0b1f30745f934887.png) **Custom Map Style** #### Observe style changes[​](#observe-style-changes "Direct link to Observe style changes") `MapViewControllerDelegate` provides a callback when style changes are applied. ```swift func mapViewController(_ mapViewController: MapViewController, onMapStyleChanged identifier: Int) { print("Style changed to id=\(identifier)") } ``` #### ContentStoreObject quick reference[​](#contentstoreobject-quick-reference "Direct link to ContentStoreObject quick reference") | Method | Description | | -------------------------------------------- | ------------------------------------------------ | | `getIdentifier()` | Unique content ID | | `getName()` | Display name | | `getType()` | Content type (`view style`, `road map`, `voice`) | | `getFileName()` | Local content path when available | | `getTotalSize()` / `getTotalSizeFormatted()` | Full item size | | `isCompleted()` | Download completion flag | | `getStatus()` | Current download state | | `downloadWithAllowCellularNetwork` | Start or resume download | | `pauseDownload()` / `cancelDownload()` | Pause/cancel transfer | | `getDownloadProgress()` | Current progress percent | | `canDeleteContent()` / `deleteContent()` | Remove downloaded content | | `isUpdatable()` / `getUpdateVersion()` | Update information | #### Relevant examples demonstrating map styling related features[​](#relevant-examples-demonstrating-map-styling-related-features "Direct link to Relevant examples demonstrating map styling related features") * [Map Custom Style](/docs/ios/examples/maps-3dscene/map-style.md) * [Map Style Following Theme](/docs/ios/examples/maps-3dscene/map-style-following-theme.md) --- ### Navigation The Magic Lane iOS SDK offers developers a comprehensive solution for building turn-by-turn navigation systems within mobile applications. The SDK features critical tools like live guidance, offline support, and warning alerts (such as speed limits and traffic conditions). Real-time navigation adapts dynamically to the user's progress, notifying them of any route deviations and recalculating the path as needed. The SDK also supports a location simulator for testing navigation functionalities during app development. #### [📄️Get started with Navigation](/docs/ios/guides/navigation/get-started-navigation.md) [This guide shows you how to implement turn-by-turn navigation in your iOS app using NavigationContext and NavigationContextDelegate.](/docs/ios/guides/navigation/get-started-navigation.md) #### [📄️Add voice guidance](/docs/ios/guides/navigation/voice-guidance.md) [Enhance navigation experiences with spoken instructions. This guide covers enabling the built-in SoundContext, managing voice settings, switching voices and languages, and integrating custom TTS playback using AVSpeechSynthesizer.](/docs/ios/guides/navigation/voice-guidance.md) #### [📄️Better route detection](/docs/ios/guides/navigation/better-route-detection.md) [Monitor traffic conditions and automatically evaluate alternative routes for optimal navigation. This feature provides real-time route adjustments, reducing travel time and improving efficiency in dynamic traffic environments.](/docs/ios/guides/navigation/better-route-detection.md) #### [📄️Roadblocks](/docs/ios/guides/navigation/roadblocks.md) [This guide explains how to add, manage, and remove roadblocks to customize route planning and navigation.](/docs/ios/guides/navigation/roadblocks.md) --- ### Better route detection Last updated: April 7, 2026 | 3 minutes read

Monitor traffic conditions and automatically evaluate alternative routes for optimal navigation. This feature provides real-time route adjustments, reducing travel time and improving efficiency in dynamic traffic environments. #### What you need[​](#what-you-need "Direct link to What you need") info **Prerequisites:** * Route computed with specific `RoutePreferencesObject` settings * Active traffic data on the route * Significant time gain (over 5 minutes) for alternative routes #### Step 1: Configure route preferences[​](#step-1-configure-route-preferences "Direct link to Step 1: Configure route preferences") Configure the `RoutePreferencesObject` with the following required settings: * `setTransportMode` — `RouteTransportModecar` or `RouteTransportModeLorry` * `setAvoidTraffic` — `TrafficAvoidanceAll` or `TrafficAvoidanceRoadBlocks` * `setRouteType` — `RouteTypeFastest` ```swift let preferences = RoutePreferencesObject() preferences.setRouteType(.fastest) preferences.setAvoidTraffic(.all) preferences.setTransportMode(.car) let navigationContext = NavigationContext(preferences: preferences) navigationContext.delegate = self ``` Add additional settings to `RoutePreferencesObject` during route calculation, provided they don't conflict with the required preferences above that are needed to test for better route detection. ##### Additional requirements[​](#additional-requirements "Direct link to Additional requirements") **Traffic data:**
Traffic must be present on the active navigation route for callbacks to trigger. **Time savings threshold:**
Alternative routes must offer time savings exceeding five minutes to be considered. This ensures meaningful route adjustments. warning ❌ Better route detection will not function if the required conditions above are not met. #### Step 2: Register notification callbacks[​](#step-2-register-notification-callbacks "Direct link to Step 2: Register notification callbacks") Implement the following `NavigationContextDelegate` methods to receive better route notifications: * **`navigationContext(_:onBetterRouteDetected:branchingCoords:travelTime:delay:timeGain:)`** - Triggered when a better route is identified. Provides the new route, the branching coordinates, total travel time, traffic-induced delay, and time savings compared to the current route. A `timeGain` value of `-1` means the original route has roadblocks and time gain cannot be calculated. * **`navigationContext(_:onBetterRouteInvalidated:)`** - Triggered when a previously detected better route is no longer valid. Occurs if the user deviates from the shared trunk, a new better alternative appears, or traffic conditions change. info You must manually manage and switch to the recommended route. The navigation context does not automatically switch routes. ```swift func navigationContext(_ navigationContext: NavigationContext, onBetterRouteDetected route: RouteObject, branchingCoords: CoordinatesObject, travelTime: Int, delay: Int, timeGain: Int) { print("Better route detected — travel time: \(travelTime) s, delay: \(delay) s, time gain: \(timeGain) s") // Store the better route and present it to the user for acceptance } func navigationContext(_ navigationContext: NavigationContext, onBetterRouteInvalidated state: Bool) { print("Previously found better route is no longer valid") } ``` #### Step 3: Switch to the better route (optional)[​](#step-3-switch-to-the-better-route-optional "Direct link to Step 3: Switch to the better route (optional)") To switch navigation to the detected better route, stop the current navigation and restart it with the new route: ```swift func acceptBetterRoute(_ betterRoute: RouteObject) { navigationContext?.cancelNavigateRoute() navigationContext?.navigate(withRoute: betterRoute) { success in print("Switched to better route: \(success)") } } ``` #### Relevant examples demonstrating better route detection related features[​](#relevant-examples-demonstrating-better-route-detection-related-features "Direct link to Relevant examples demonstrating better route detection related features") * [Better Route Notification](/docs/ios/examples/routing-navigation/better-route-notification.md) --- ### Get started with Navigation Last updated: April 7, 2026 | 7 minutes read

This guide shows you how to implement turn-by-turn navigation in your iOS app using `NavigationContext` and `NavigationContextDelegate`. info **What you need** * A computed route (non-navigable routes like range routes are not supported) * Proper location permissions for real GPS navigation * Map data downloaded for offline functionality **Key features:** * **Turn-by-Turn Directions** - Detailed route instructions based on current location * **Live Guidance** - Text and voice instructions via `SoundContext` integration * **Warning Alerts** - Speed limits, traffic reports, and route events * **Offline Support** - Works offline with pre-downloaded map data The navigation system tracks your device location, speed, and heading, matching them against the route to generate accurate guidance. Instructions update dynamically as you progress. When you deviate from the route, the system notifies you and offers recalculation options. It can also adjust routes based on real-time traffic for faster alternatives. You can test navigation features using the built-in location simulator during development. #### How navigation works[​](#how-navigation-works "Direct link to How navigation works") The SDK offers two navigation methods: * **Navigation** - Uses position data from `GEMSdk` position services to guide users along the route * **Simulation** - Simulates navigation instructions without real position data for testing Navigation mode uses the SDK's position service with: * **Real GPS Data** - Requires location permissions on iOS. See [Get started with positioning](/docs/ios/guides/positioning/get-started-positioning.md) * **Custom Position Data** - Configure a custom data source for position updates. No permissions required. See [Custom positioning](/docs/ios/guides/positioning/custom-positioning.md) warning Only one navigation or simulation can be active at a time, regardless of map count. warning These are simplified code snippets, make sure the `MapViewController` object is properly set up as shown in previous guides. #### Start navigation[​](#start-navigation "Direct link to Start navigation") Once you have a computed route, start navigation using `navigateWithRoute(_:completionHandler:)` on a `NavigationContext` instance. Implement `NavigationContextDelegate` to receive navigation events: ```swift // Retain these as properties to keep navigation alive var navigationContext: NavigationContext? var mapView: MapViewController? func startNavigation(route: RouteObject) { let preferences = RoutePreferencesObject() navigationContext = NavigationContext(preferences: preferences) navigationContext?.delegate = self navigationContext?.navigate(withRoute: route) { [weak self] success in if success { self?.mapView?.presentRoutes([route], withTraffic: nil, showSummary: false, animationDuration: 0) self?.mapView?.startFollowingPosition(withAnimationDuration: 0,zoomLevel: -1) { _ in } } else { print("Navigation failed to start") } } } // MARK: - NavigationContextDelegate func navigationContext(_ navigationContext: NavigationContext, navigationStartedForRoute route: RouteObject) { print("Navigation started") } func navigationContext(_ navigationContext: NavigationContext, navigationInstructionUpdatedForRoute route: RouteObject, updatedEvents events: Int32) { guard let instruction = navigationContext.getNavigationInstruction() else { return } if events & Int32(NavigationInstructionUpdateEvent.nextTurnUpdated.rawValue) > 0 { let text = instruction.getNextTurnInstruction() print("Turn: \(text)") } if events & Int32(NavigationInstructionUpdateEvent.nextTurnImageUpdated.rawValue) > 0 { let image = instruction.getNextTurnImage(CGSize(width: 48, height: 48)) // update turn image in UI } if events & Int32(NavigationInstructionUpdateEvent.laneInfoUpdated.rawValue) > 0 { // update lane guidance UI } } func navigationContext(_ navigationContext: NavigationContext, route: RouteObject, navigationDestinationReached waypoint: LandmarkObject) { print("Destination reached: \(waypoint.getLandmarkName())") } func navigationContext(_ navigationContext: NavigationContext, route: RouteObject?, navigationError code: Int) { print("Navigation error: \(code)") } ``` info `NavigationContext` must be kept alive for the duration of navigation. Store it as a property on your view controller or data manager. Deallocating it will stop navigation. The `NavigationContextDelegate` delivers the following navigation events: | Delegate method | Significance | | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `navigationContext(_:navigationStartedForRoute:)` | Called when navigation begins, signaling the start of route guidance. | | `navigationContext(_:navigationInstructionUpdatedForRoute:updatedEvents:)` | Triggered when a new navigation instruction is available. The `updatedEvents` bitmask indicates what changed using `NavigationInstructionUpdateEvent` flags. | | `navigationContext(_:route:navigationStatusChanged:)` | Called when the navigation status changes. See `NavigationStatus` values below. | | `navigationContext(_:route:navigationWaypointReached:)` | Invoked when a waypoint in the route is reached, including details of the waypoint. | | `navigationContext(_:route:navigationDestinationReached:)` | Called upon reaching the final destination, with information about the destination landmark. | | `navigationContext(_:navigationRouteUpdated:)` | Fired when the current route is updated, providing the new route details. | | `navigationContext(_:route:navigationError:)` | Called when a navigation error occurs. See error codes below. | | `navigationContext(_:onBetterRouteDetected:branchingCoords:travelTime:delay:timeGain:)` | Triggered when a better alternative route is detected. See the [Better route detection guide](/docs/ios/guides/navigation/better-route-detection.md) for more details. | | `navigationContext(_:onBetterRouteInvalidated:)` | Indicates that a previously suggested better route is no longer valid. | | `navigationContext(_:onSkipNextIntermediateDestinationDetected:)` | Indicates the user is moving away from the next intermediate waypoint. Consider calling `skipNextIntermediateDestination()` on the navigation context. | | `navigationContext(_:canPlayNavigationSoundForRoute:)` | Returns whether the SDK is allowed to play a navigation sound. Return `true` to allow built-in audio playback. | | `navigationContext(_:route:navigationSound:)` | Provides a `SoundObject` when a sound needs to be played. See [Add voice guidance](/docs/ios/guides/navigation/voice-guidance.md). | **`NavigationStatus` values:** | Value | Meaning | | -------------------------------------- | ------------------------------------------------------------- | | `NavigationStatusRunning` | Normal running state. | | `NavigationStatusWaitingRoute` | Paused, waiting for route update (re-routing enabled). | | `NavigationStatusWaitingGPS` | Paused, waiting for GPS location to recover. | | `NavigationStatusWaitingReturnToRoute` | Paused, waiting to return to the route (re-routing disabled). | **`NavigationInstructionUpdateEvent` flags:** | Flag | Meaning | | ------------------------------------------------------ | ---------------------------------- | | `NavigationInstructionUpdateEventNextTurnUpdated` | The next turn instruction changed. | | `NavigationInstructionUpdateEventNextTurnImageUpdated` | The turn arrow image changed. | | `NavigationInstructionUpdateEventLaneInfoUpdated` | Lane guidance information changed. | Tip Present the route and start following position inside the navigation/simulation completion handler to ensure the map updates only after the session has started successfully. See [Show your location on the map](/docs/ios/guides/positioning/show-your-location-on-the-map.md) for camera customization options. Display the route on the map for better navigation clarity. Turn-by-turn navigation arrows disappear once passed. Learn more in [Display routes](/docs/ios/guides/maps/display-map-items/display-routes.md). The traveled portion of the route changes color using the `traveledInnerColor` parameter of `RouteRenderSettings`. #### Start simulation[​](#start-simulation "Direct link to Start simulation") Start a simulation using `simulateWithRoute(_:speedMultiplier:completionHandler:)`. The delegate methods work identically to real navigation: ```swift func startSimulation(route: RouteObject) { let preferences = RoutePreferencesObject() navigationContext = NavigationContext(preferences: preferences) navigationContext?.delegate = self navigationContext?.simulate(withRoute: route, speedMultiplier: 2.0) { [weak self] success in if success { self?.mapView?.presentRoutes([route], withTraffic: nil, showSummary: false, animationDuration: 0) self?.mapView?.startFollowingPositionWithAnimationDuration(0, zoomLevel: -1) { _ in } } else { print("Simulation failed to start") } } } ``` The `speedMultiplier` sets simulation speed (default is 1.0, matching the maximum speed limit for each road segment). Use `getSimulationMinSpeedMultiplier()` and `getSimulationMaxSpeedMultiplier()` to query the allowed range. #### Read navigation progress[​](#read-navigation-progress "Direct link to Read navigation progress") At any point during navigation or simulation, read the current instruction via `getNavigationInstruction()` on the `NavigationContext`: ```swift if let instruction = navigationContext?.getNavigationInstruction() { // Next turn let turnText = instruction.getNextTurnInstruction() let turnImage = instruction.getNextTurnImage(CGSize(width: 48, height: 48)) // Distances and times let toNextTurn = instruction.getTimeDistanceToNextTurn() let remaining = instruction.getRemainingTravelTimeDistance() // Current street let streetName = instruction.getCurrentStreetName() let speedLimit = instruction.getCurrentStreetSpeedLimit() // m/s } ``` Retrieve ETA and remaining distance as formatted strings directly from `NavigationContext`: ```swift let eta = navigationContext?.getEstimateTimeOfArrivalFormatted() ?? "" let etaUnit = navigationContext?.getEstimateTimeOfArrivalUnitFormatted() ?? "" let timeLeft = navigationContext?.getRemainingTravelTimeFormatted() ?? "" let distLeft = navigationContext?.getRemainingTravelDistanceFormatted() ?? "" ``` #### Skip intermediate destination[​](#skip-intermediate-destination "Direct link to Skip intermediate destination") When `onSkipNextIntermediateDestinationDetected` fires, call `skipNextIntermediateDestination()` to drop the current waypoint and continue to the next one: ```swift func navigationContext(_ navigationContext: NavigationContext, onSkipNextIntermediateDestinationDetected state: Bool) { if state { navigationContext.skipNextIntermediateDestination() } } ``` info If there are no more intermediate waypoints on the route, `skipNextIntermediateDestination()` returns `.kNotFound`. #### Stop navigation or simulation[​](#stop-navigation-or-simulation "Direct link to Stop navigation or simulation") Call `cancelNavigateRoute()` or `cancelSimulateRoute()` to stop an active session: ```swift // Stop navigation navigationContext?.cancelNavigateRoute() // Stop simulation navigationContext?.cancelSimulateRoute() ``` info After stopping simulation, the position service reverts to the previous data source if one exists. #### Run navigation in background[​](#run-navigation-in-background "Direct link to Run navigation in background") To use navigation while your app is in the background, additional setup is required. See the [Background location guide](/docs/ios/guides/positioning/background-location.md) for configuration instructions. #### Relevant examples demonstrating navigation related features[​](#relevant-examples-demonstrating-navigation-related-features "Direct link to Relevant examples demonstrating navigation related features") * [Navigate Route](/docs/ios/examples/routing-navigation/navigate-route.md) * [Simulate Route](/docs/ios/examples/routing-navigation/simulate-route.md) * [Finger Draw Route](/docs/ios/examples/routing-navigation/finger-drawn-route-navigation.md) * [No Map Just Routing](/docs/ios/examples/routing-navigation/no-map-just-routing.md) * [Custom Turns ID](/docs/ios/examples/routing-navigation/custom-turns-id.md) * [Follow Position Preferences](/docs/ios/examples/routing-navigation/follow-position-preferences.md) --- ### Roadblocks Last updated: April 7, 2026 | 5 minutes read

This guide explains how to add, manage, and remove roadblocks to customize route planning and navigation. A **roadblock** is a user-defined restriction applied to a specific road segment or geographic area. It reflects traffic disruptions such as construction, closures, or areas to avoid, and influences route planning by marking certain paths or zones as unavailable for navigation. Roadblocks can be **path-based** (defined by a sequence of coordinates) or **area-based** (covering a geographic region). They may be **temporary** (active only during navigation) or **persistent** (survive SDK uninitialization), depending on their intended duration. The `TrafficEventObject` class represents roadblocks. Roadblocks are managed through the `TrafficContext` class. While some roadblocks are provided in real time by online data from Magic Lane servers, you can also define your own **user roadblocks** to customize routing behavior. If the applied style includes traffic data and traffic display is enabled, a visual indication of the blocked portion will appear on the map, highlighted in red. #### Configure traffic usage[​](#configure-traffic-usage "Direct link to Configure traffic usage") Traffic behavior can be customized through the `TrafficContext` class. The `TrafficUsage` enum offers the following options: | Value | Description | | ------------------------ | ----------------------------------------------------------------- | | `TrafficUsageUseNone` | Disables all traffic data usage | | `TrafficUsageUseOnline` | Uses both online and offline traffic data (default) | | `TrafficUsageUseOffline` | Uses only offline traffic data, including user-defined roadblocks | #### Add temporary roadblock during navigation[​](#add-temporary-roadblock-during-navigation "Direct link to Add temporary roadblock during navigation") You can add a roadblock to bypass a portion of the current route for a specified distance. The route is recalculated automatically and the updated route is returned via the `navigationContext(_:navigationRouteUpdated:)` delegate method. To add a 100-meter roadblock starting 10 meters ahead: ```swift // Using NavigationContext navigationContext?.setRoadBlockWithLength(100, starting: 10) ``` Roadblocks added through `setRoadBlockWithLength(_:starting:)` only affect the ongoing navigation session. #### Check traffic information availability[​](#check-traffic-information-availability "Direct link to Check traffic information availability") Use `getOnlineServiceRestrictions(_:)` on `TrafficContext` to check if traffic events are available for a geographic position. It takes a `CoordinatesObject` and returns a `TrafficOnlineRestrictions` value: ```swift let trafficContext = TrafficContext() let coords = CoordinatesObject.coordinates(withLatitude: 50.108, longitude: 8.783) let restriction = trafficContext.getOnlineServiceRestrictions(coords) print("Traffic restriction: \(restriction.rawValue)") ``` The `TrafficOnlineRestrictions` enum provides the following values: * `TrafficOnlineRestrictionsNone` — No restrictions; online traffic is available * `TrafficOnlineRestrictionsSettings` — Online traffic is disabled in preferences * `TrafficOnlineRestrictionsConnection` — No internet connection is available * `TrafficOnlineRestrictionsNetworkType` — Not allowed on extra charged networks (e.g., roaming) * `TrafficOnlineRestrictionsProviderData` — Required provider data is missing * `TrafficOnlineRestrictionsWorldMapVersion` — The world map version is outdated and incompatible. Update the road map * `TrafficOnlineRestrictionsDiskSpace` — Insufficient disk space to store traffic data * `TrafficOnlineRestrictionsInitFail` — Failed to initialize the traffic service #### Add area-based persistent roadblock[​](#add-area-based-persistent-roadblock "Direct link to Add area-based persistent roadblock") Use `addPersistentRoadblock(_:startTime:expireTime:transportMode:identifier:)` to add **area-based** user roadblocks. It accepts a `CircleGeographicAreaObject` representing the area to be avoided. Construct a `TimeObject` from a Unix timestamp in milliseconds using `fromInt(_:)`: ```swift let trafficContext = TrafficContext() let center = CoordinatesObject.coordinates(withLatitude: 46.764942, longitude: 7.122563) let area = CircleGeographicAreaObject(center: center, radius: 300) // 300 m radius let startTime = TimeObject() startTime.fromInt(Int(Date().timeIntervalSince1970 * 1000)) let expireTime = TimeObject() expireTime.fromInt(Int(Date().addingTimeInterval(3600).timeIntervalSince1970 * 1000)) if let event = trafficContext.addPersistentRoadblock(area, startTime: startTime, expireTime: expireTime, transportMode: .car, identifier: "test_area_id") { print("Area roadblock added: \(event.getDescription())") } else { print("Failed to add area roadblock") } ``` warning The following conditions apply when adding a persistent roadblock: * If a roadblock already exists at the same location, the addition will fail and return `nil` * If the input parameters are invalid (e.g., `expireTime` is earlier than `startTime`, missing `identifier`, or invalid area object), the addition will fail #### Add path-based persistent roadblock[​](#add-path-based-persistent-roadblock "Direct link to Add path-based persistent roadblock") Use `addPersistentRoadblock(_:startTime:expireTime:transportMode:)` (the array overload) to add **path-based** user roadblocks. It accepts an array of `CoordinatesObject` instances: * **Single coordinate** — Defines a **point-based** roadblock. This may result in two roadblocks created — one for each travel direction. * **Multiple coordinates** — Defines a **path-based** roadblock from the first to the last coordinate, restricting access along a specific road segment. ```swift let coords = [CoordinatesObject.coordinates(withLatitude: 45.64695, longitude: 25.62070)] let startTime = TimeObject() startTime.fromInt(Int(Date().timeIntervalSince1970 * 1000)) let expireTime = TimeObject() expireTime.fromInt(Int(Date().addingTimeInterval(3600).timeIntervalSince1970 * 1000)) if let event = trafficContext.addPersistentRoadblock(coords, startTime: startTime, expireTime: expireTime, transportMode: .car) { print("Path roadblock added: \(event.getDescription())") } else { print("Failed to add path roadblock — check coordinates and road data availability") } ``` warning The path-based roadblock may also fail if: * **No suitable road found** — No valid road can be identified at the specified coordinates, or no road data (online or offline) is available for the given location. * **Route computation failed** — If multiple coordinates are provided but a valid route cannot be computed between them. #### Get all persistent roadblocks[​](#get-all-persistent-roadblocks "Direct link to Get all persistent roadblocks") Use `getPersistentRoadblocks()` to retrieve the list of all active or scheduled persistent roadblocks. Expired roadblocks are automatically removed: ```swift let roadblocks = trafficContext.getPersistentRoadblocks() for roadblock in roadblocks { print(roadblock.getDescription()) } ``` #### Remove roadblocks[​](#remove-roadblocks "Direct link to Remove roadblocks") ##### Remove persistent roadblock by coordinates[​](#remove-persistent-roadblock-by-coordinates "Direct link to Remove persistent roadblock by coordinates") Use `removePersistentRoadblock(_:)` to remove a path-based roadblock by providing the **first** coordinate used to create it: ```swift let startCoord = CoordinatesObject.coordinates(withLatitude: 45.64695, longitude: 25.62070) trafficContext.removePersistentRoadblock(startCoord) ``` ##### Remove roadblock using TrafficEventObject[​](#remove-roadblock-using-trafficeventobject "Direct link to Remove roadblock using TrafficEventObject") If the `TrafficEventObject` instance is available, remove it using `removeUserRoadblock(_:)`: ```swift if let event = trafficContext.getPersistentRoadblocks().first { trafficContext.removeUserRoadblock(event) } ``` Tip `removeUserRoadblock(_:)` can be used for both persistent and non-persistent roadblocks. ##### Remove all persistent roadblocks[​](#remove-all-persistent-roadblocks "Direct link to Remove all persistent roadblocks") Use `removeAllPersistentRoadblocks()` to delete all existing user-defined roadblocks: ```swift trafficContext.removeAllPersistentRoadblocks() ``` #### Monitor roadblock lifecycle events[​](#monitor-roadblock-lifecycle-events "Direct link to Monitor roadblock lifecycle events") Implement `PersistentRoadblockDelegate` on `TrafficContext` to receive notifications when roadblocks expire or become active: ```swift trafficContext.persistentRoadblockDelegate = self trafficContext.startPersistentRoadblockNotification() // MARK: - PersistentRoadblockDelegate func trafficContext(_ trafficContext: TrafficContext, onRoadblocksExpired events: [TrafficEventObject]) { for event in events { print("Roadblock expired: \(event.getDescription())") } } func trafficContext(_ trafficContext: TrafficContext, onRoadblocksActivated events: [TrafficEventObject]) { for event in events { print("Roadblock activated: \(event.getDescription())") } } ``` Call `stopPersistentRoadblockNotification()` when notifications are no longer needed. #### Relevant examples demonstrating roadblocks related features[​](#relevant-examples-demonstrating-roadblocks-related-features "Direct link to Relevant examples demonstrating roadblocks related features") * [Road Block](/docs/ios/examples/routing-navigation/road-block.md) --- ### Add voice guidance Last updated: April 7, 2026 | 6 minutes read

Enhance navigation experiences with spoken instructions. This guide covers enabling the built-in `SoundContext`, managing voice settings, switching voices and languages, and integrating custom TTS playback using `AVSpeechSynthesizer`. #### What you need[​](#what-you-need "Direct link to What you need") The Maps SDK for iOS provides two options for instruction playback: * **Built-in solutions** - Playback using human voice recordings or device Text-to-Speech via `SoundContext` * **External integration** - Delivery of `SoundObject` via `NavigationContextDelegate` for use with third-party audio frameworks The built-in solution provides automatic audio session management via `AVFoundation`, ducking other playbacks (such as music) while instructions play. #### Step 1: Create and configure SoundContext[​](#step-1-create-and-configure-soundcontext "Direct link to Step 1: Create and configure SoundContext") Create a `SoundContext` instance and store it as a property to keep it alive: ```swift var soundContext: SoundContext? func configureSoundContext() { soundContext = SoundContext() soundContext?.setVolume(7) // 0-10 range } ``` Use `setVolume(_:)` and `getVolume()` to check or change the playback volume. warning Ensure a valid TTS voice is configured, volume is set to a positive value, and `canPlayNavigationSoundForRoute` returns `true` in the delegate to automatically play voice instructions. Tip By default, device TTS uses the current locale via `AVSpeechSynthesisVoice`. Call `setUseTtsWithCompletionHandler(_:)` to activate it automatically. **Limitations:**
Customizing the timing of TTS instructions is not supported. Filtering TTS instructions based on custom logic is not available. #### Step 2: Configure the SoundContext[​](#step-2-configure-the-soundcontext "Direct link to Step 2: Configure the SoundContext") The `SoundContext` manages voice playback with the following features: | Method / Property | Description | | -------------------------------------------------------- | -------------------------------------------------------------------------------------- | | `setUseTtsWithCompletionHandler(_:)` | Activates TTS using the device's current locale (async) | | `setUseTtsWithLanguage(_:completionHandler:)` | Activates TTS with a specific BCP 47 language code, e.g. `"en-US"` (async) | | `setUseTtsWithLanguage(_:identifier:completionHandler:)` | Activates TTS with language and a specific `AVSpeechSynthesisVoice` identifier (async) | | `setUseHumanVoiceWithIdentifier(_:completionHandler:)` | Activates a downloaded human voice by identifier (async) | | `playText(_:)` | Plays a given TTS text string (computer voices only) | | `playSound(_:)` | Plays a `SoundObject` delivered by the navigation delegate | | `setVolume(_:)` / `getVolume()` | Gets or sets volume level (0-10) | | `isPlaying` | Returns whether a sound is currently playing | | `cancel()` | Cancels ongoing sound playback | | `getTtsVoiceName()` | Returns the TTS voice name based on device locale | | `updateSession(withAudioCategory:)` | Updates the AVAudioSession category | | `updateSession(withAudioOutput:)` | Updates the audio output route | #### Step 3: Select and configure voices[​](#step-3-select-and-configure-voices "Direct link to Step 3: Select and configure voices") The SDK provides voices for each supported language. Download and activate voices to deliver navigation prompts such as turn instructions, warnings, and announcements. ##### Voice types[​](#voice-types "Direct link to Voice types") The SDK offers two types of voice guidance: * **Human voices** - Pre-recorded human voices delivering instructions in a natural tone. Supports basic instruction types only; **does not** include road or settlement names. Activate with `setUseHumanVoiceWithIdentifier(_:completionHandler:)`. * **Computer TTS** - Device `AVSpeechSynthesizer` providing detailed, flexible guidance. **Fully supports** street and place names. Quality depends on device capabilities. Activate with `setUseTtsWithCompletionHandler(_:)`. danger ⚠️ **Do not confuse voice and language concepts** * **Language** defines **what** is said — words, phrasing, and localization * **Voice** defines **how** it is said — accent, tone, and gender Ensure the selected voice is compatible with the chosen language. Mismatched combinations may result in unnatural or incorrect pronunciation. ##### Get available human voices[​](#get-available-human-voices "Direct link to Get available human voices") Retrieve the available human voices list using `getLocalList()` from `HumanVoiceContext`: ```swift let humanVoiceContext = HumanVoiceContext() func getVoices() { // getOnlineList(completionHandler:) also available to check for and download additional voices let voices = humanVoiceContext.getLocalList() for item in voices { let name = item.getName() let gender = humanVoiceContext.getHumanVoiceGender(with: item) let nativeLanguage = humanVoiceContext.getNativeLanguage(item) print("\(name) — \(nativeLanguage)") } } ``` `HumanVoiceGender` has three values: `HumanVoiceGenderMale`, `HumanVoiceGenderFemale`, `HumanVoiceGenderComputer`. Tip See the [Manage Content Guide](/docs/ios/guides/offline/manage-content.md) for downloading, deleting, and managing voices. ##### Activate a human voice[​](#activate-a-human-voice "Direct link to Activate a human voice") Provide the voice identifier from `ContentStoreObject`: ```swift let voiceId: Int = voices.first?.getIdentifier() ?? 0 soundContext?.setUseHumanVoiceWithIdentifier(voiceId) { success in if success { print("Human voice activated") } } ``` ##### Activate a TTS voice by language[​](#activate-a-tts-voice-by-language "Direct link to Activate a TTS voice by language") Activate computer voices using `setUseTtsWithLanguage(_:completionHandler:)`: ```swift soundContext?.setUseTtsWithLanguage("en-GB") { success in if success { print("TTS voice activated for en-GB") } } ``` To use a specific `AVSpeechSynthesisVoice` identifier: ```swift let voices = AVSpeechSynthesisVoice.speechVoices() if let voice = voices.first(where: { $0.language == "en-GB" }) { soundContext?.setUseTtsWithLanguage("en-GB", identifier: voice.identifier) { success in print("TTS voice set: \(success)") } } ``` warning Selecting a TTS voice in an unsupported language may cause a mismatch between spoken voice and instruction content. Exact behavior depends on device TTS capabilities. #### Step 4: Connect SoundContext to navigation[​](#step-4-connect-soundcontext-to-navigation "Direct link to Step 4: Connect SoundContext to navigation") Implement the `NavigationContextDelegate` sound methods to route audio through `SoundContext`: ```swift // Return true to allow the SDK's built-in sound playback func navigationContext(_ navigationContext: NavigationContext, canPlayNavigationSoundForRoute route: RouteObject) -> Bool { return true } // Play the sound object delivered by the navigation engine func navigationContext(_ navigationContext: NavigationContext, route: RouteObject, navigationSound sound: SoundObject) { soundContext?.playSound(sound) } ``` #### Step 5: Integrate external TTS (optional)[​](#step-5-integrate-external-tts-optional "Direct link to Step 5: Integrate external TTS (optional)") The `NavigationContextDelegate` delivers `SoundObject` instances for each maneuver. Use `AVSpeechSynthesizer` or any third-party TTS engine to speak the instruction text: ```swift let synthesizer = AVSpeechSynthesizer() func navigationContext(_ navigationContext: NavigationContext, canPlayNavigationSoundForRoute route: RouteObject) -> Bool { // Return false to suppress built-in playback and handle audio yourself return false } func navigationContext(_ navigationContext: NavigationContext, route: RouteObject, navigationSound sound: SoundObject) { // Use the text from the current navigation instruction instead if let instruction = navigationContext.getNavigationInstruction() { let text = instruction.getNextTurnInstruction() let utterance = AVSpeechUtterance(string: text) utterance.voice = AVSpeechSynthesisVoice(language: "en-US") synthesizer.speak(utterance) } } ``` Tip **Disable internal playback:**
Return `false` from `canPlayNavigationSoundForRoute`. Instructions still arrive via `navigationSound`, but no built-in audio plays. #### Step 6: Adjust audio output[​](#step-6-adjust-audio-output "Direct link to Step 6: Adjust audio output") Select the audio route with `updateSession(withAudioOutput:)` on `SoundContext`: * **`AudioOutputAutomatic`** - Uses Bluetooth A2DP when available, otherwise speaker * **`AudioOutputSpeakerOnly`** - Forces speaker output (requires `AudioCategoryPlaybackAndRecording`) * **`AudioOutputBluetoothAsPhoneCall`** - Routes audio as a phone call ⚠️ Update the audio session category with `updateSession(withAudioCategory:)`: * **`AudioCategoryPlayback`** - For audio playback only * **`AudioCategoryPlaybackAndRecording`** - For recording and playback (required for Bluetooth phone-call and speaker-only output) ```swift soundContext?.updateSession(with: .playbackAndRecording) soundContext?.updateSession(with: .bluetoothAsPhoneCall) ``` For Bluetooth phone-call routing, set the call start delay in milliseconds: ```swift soundContext?.setDelay(500) ``` #### Step 7: Monitor audio session interruptions[​](#step-7-monitor-audio-session-interruptions "Direct link to Step 7: Monitor audio session interruptions") Implement `SoundContextDelegate` to react to audio session interruptions (e.g., incoming calls): ```swift soundContext?.delegate = self // MARK: - SoundContextDelegate func soundContextNotifyInterruptionBegin(_ soundContext: SoundContext) { print("Audio session interrupted — pausing navigation audio") } func soundContextNotifyInterruptionEnded(_ soundContext: SoundContext) { print("Audio session resumed") } ``` #### Step 8: Play custom instructions[​](#step-8-play-custom-instructions "Direct link to Step 8: Play custom instructions") Use `playText(_:)` on `SoundContext` to play custom instructions (e.g., road warnings or alerts). This uses the currently configured computer TTS voice and is **not available for human voices**. ```swift soundContext?.playText("Traffic incident ahead. Consider an alternative route.") ``` #### Relevant examples demonstrating voice guidance related features[​](#relevant-examples-demonstrating-voice-guidance-related-features "Direct link to Relevant examples demonstrating voice guidance related features") * [Human Voices](/docs/ios/examples/routing-navigation/human-voices.md) * [Navigate Route](/docs/ios/examples/routing-navigation/navigate-route.md) --- ### Offline The Maps SDK for iOS provides extensive offline functionality through its map download capabilities. Users can search for landmarks, calculate and navigate routes, and explore the map without requiring an active internet connection inside downloaded regions. The SDK also provides the ability to download content (maps, styles, or voices) and to manage downloaded content. #### [📄️Introduction](/docs/ios/guides/offline/introduction.md) [The Maps SDK for iOS provides offline functionality through map download capabilities.](/docs/ios/guides/offline/introduction.md) #### [📄️Manage content](/docs/ios/guides/offline/manage-content.md) [Manage offline content through the Maps SDK for iOS.](/docs/ios/guides/offline/manage-content.md) #### [📄️Update content](/docs/ios/guides/offline/update-content.md) [The Maps SDK for iOS allows updating downloaded content to stay synchronized with the latest map data.](/docs/ios/guides/offline/update-content.md) --- ### Introduction Last updated: April 3, 2026 | 3 minutes read

The Maps SDK for iOS provides offline functionality through map download capabilities. Download maps for entire countries or specific regions to enable offline access. Users can search for landmarks, calculate routes, navigate, and explore maps without an internet connection. warning Overlays, live traffic information, and other online-dependent services are unavailable in offline mode. The iOS SDK does not provide an automatic update mechanism for the map if using offline maps. Map updates must be triggered manually using the `MapsContext` update API. Automatic map updates are only available when no regions are downloaded, allowing the SDK to fetch the latest map version from the Magic Lane servers. Check the relevant example for more details: [Map Update](/docs/ios/examples/maps-3dscene/map_update.md). New map versions are released every few weeks globally, providing regular enhancements and improvements. #### Offline feature availability[​](#offline-feature-availability "Direct link to Offline feature availability") ##### Core entities[​](#core-entities "Direct link to Core entities") | Entity | Offline availability | | ---------- | ------------------------------------------------------------------------------------------------------------------------ | | Landmarks | ✓ Fully available | | Markers | ✓ Fully available | | Position | ⚠️ Partial - Raw position data is always accessible, but map-matched position data requires downloaded or cached regions | | Overlays | ✗ Not available | | Routes | ⚠️ Partial - traffic events are unavailable without internet connection | | Navigation | ✓ Fully available if navigation starts on an offline-calculated route | info Map tiles are automatically cached based on your location, camera position, and calculated routes to enhance performance and offline accessibility. ##### Map controller[​](#map-controller "Direct link to Map controller") `MapViewController` methods function as expected in offline mode. Methods that request data from regions not covered or cached return empty results. **Example:** Calling `getNearestLocations(_:)` with coordinates outside downloaded areas returns an empty array. ##### Map styling[​](#map-styling "Direct link to Map styling") You can set a new map style in offline mode if the style has been downloaded beforehand using `MapStyleContext` or if using a custom style. warning Styles containing extensive data, such as satellite or weather styles, may not display meaningful information when offline. ##### Services[​](#services "Direct link to Services") The following contexts are available offline within downloaded map regions: * `NavigationContext` * `SearchContext` * `Routing` * `LandmarkStoreContext` * `PositionContext` The following are **not supported** in offline mode: * Overlay services ##### SDK settings[​](#sdk-settings "Direct link to SDK settings") Most SDK features function independently of internet connection status, except authorization-related functionalities. danger SDK authorization requires an active internet connection. You cannot authorize the Maps SDK for iOS without being online. #### Relevant examples demonstrating offline related features[​](#relevant-examples-demonstrating-offline-related-features "Direct link to Relevant examples demonstrating offline related features") * [Map Download](/docs/ios/examples/maps-3dscene/map-download.md) * [Map Update](/docs/ios/examples/maps-3dscene/map_update.md) --- ### Manage Content Last updated: April 3, 2026 | 5 minutes read

Manage offline content through the Maps SDK for iOS. #### Content types[​](#content-types "Direct link to Content types") The SDK provides dedicated context classes for each type of downloadable content: * **`MapsContext`** - Downloads road maps covering countries and regions for search, routing, and navigation * **`MapStyleContext`** - Downloads map styles (high-res and low-res) that can be applied offline * **`HumanVoiceContext`** - Downloads pre-recorded human voice files for spoken navigation instructions Each context class exposes a consistent API for listing, downloading, and managing its respective content type. Tip Use `MapsContext` for most offline use cases — road map data is required for offline search, routing, and navigation. #### ContentStoreObject overview[​](#contentstoreobject-overview "Direct link to ContentStoreObject overview") Each downloadable item is represented by a `ContentStoreObject`, which provides details such as name, identifier, type, version, and size. You download and delete content through the item object itself. danger Ensure your API token is set and valid before performing content store operations. danger Modifying downloaded maps (download, delete, update) may interrupt ongoing operations such as search, route calculation, or navigation. #### List available maps[​](#list-available-maps "Direct link to List available maps") ##### List local content[​](#list-local-content "Direct link to List local content") Retrieve locally available maps using `getLocalList()` on a `MapsContext` instance: ```swift let mapsContext = MapsContext() let localMaps = mapsContext.getLocalList() for item in localMaps { print("\(item.getName()) — \(item.getTotalSizeFormatted())") } ``` ##### List online content[​](#list-online-content "Direct link to List online content") Retrieve the list of available maps from the Magic Lane servers using `getOnlineList(_:)`: ```swift mapsContext.getOnlineList { items in for item in items { print("\(item.getName()) — status: \(item.getStatus().rawValue)") } } ``` info Call `getOnlineList(_:)` only when an active internet connection is available. Use `getLocalList()` when offline. The same pattern applies for styles and voices: ```swift let styleContext = MapStyleContext() styleContext.getOnlineList { items in // handle style items } let voiceContext = HumanVoiceContext() voiceContext.getOnlineList { items in // handle voice items } ``` #### ContentStoreObject fields[​](#contentstoreobject-fields "Direct link to ContentStoreObject fields") ##### General information[​](#general-information "Direct link to General information") | Method | Description | | --------------------------- | ------------------------------------------------------------------ | | `getName()` | The name of the content item, automatically translated | | `getIdentifier()` | The unique identifier of the item | | `getType()` | The content type as a `ContentStoreOnlineType` value | | `getFileName()` | The full path to the content data file | | `getTotalSize()` | The total size of the content in bytes | | `getTotalSizeFormatted()` | The total size as a formatted string | | `getAvailableSize()` | The downloaded size of the content in bytes | | `getStatus()` | The current status as a `ContentStoreObjectStatus` value | | `isImagePreviewAvailable()` | Returns whether an image preview is available | | `getImagePreview(_:)` | Returns the preview image at the specified width | | `getChapterName()` | The chapter name for large countries divided into multiple items | | `getCountryCodes()` | Array of ISO 3166-1 alpha-3 country codes associated with the item | Tip Check a `ContentStoreObject`'s current state using `getStatus()`: * `ContentStoreObjectStatusUnavailable` — Not downloaded; cannot be used * `ContentStoreObjectStatusCompleted` — Downloaded and ready to use * `ContentStoreObjectStatusPaused` — Download paused by the user * `ContentStoreObjectStatusDownloadQueued` — Download queued, waiting for resources * `ContentStoreObjectStatusDownloadWaiting` — Waiting for a network connection * `ContentStoreObjectStatusDownloadWaitingFreeNetwork` — Waiting for a non-metered network * `ContentStoreObjectStatusDownloadRunning` — Download actively in progress * `ContentStoreObjectStatusUpdateWaiting` — Waiting for an update operation to finish danger Content items of type `roadMap` do not have an image preview. Use `getCountryFlagWithIsoCode(_:size:)` on `MapsContext` to retrieve a country flag image instead. ##### Download and update information[​](#download-and-update-information "Direct link to Download and update information") | Method | Description | | -------------------------- | --------------------------------------------------------- | | `getClientVersion()` | The current client version of the content | | `getDownloadProgress()` | The current download progress (0–100) | | `isCompleted()` | Returns `true` if the item is fully downloaded | | `isUpdatable()` | Returns `true` if a newer version is available | | `getUpdateSize()` | The update size in bytes, if an update is available | | `getUpdateSizeFormatted()` | The update size as a formatted string | | `getUpdateVersion()` | The newer version string, if available | | `getUpdateItem()` | The corresponding update item if an update is in progress | | `canDeleteContent()` | Returns `true` if the content can be deleted | #### Download content[​](#download-content "Direct link to Download content") Download a content item by calling `download(withAllowCellularNetwork:completionHandler:)` on the `ContentStoreObject`. Set `allowCellularNetwork` to `true` to permit downloads over cellular: ```swift item.download(withAllowCellularNetwork: false) { success in print("Download completed: \(success)") } ``` Track progress using the variant with a `progressHandler`: ```swift item.download(withAllowCellularNetwork: false, progressHandler: { progress in print("Download progress: \(progress)/100") }) { success in print("Download completed: \(success)") } ``` Alternatively, use `MapsContext` to download a map by identifier: ```swift let mapId = localMaps.first?.getIdentifier() ?? 0 mapsContext.downloadMap(withIdentifier: mapId, allowCellularNetwork: false) { success in print("Map download completed: \(success)") } ``` ##### Pause and resume download[​](#pause-and-resume-download "Direct link to Pause and resume download") Pause an active download using `pauseDownload()`. Resume it by calling `downloadWithAllowCellularNetwork(_:completionHandler:)` again: ```swift item.pauseDownload() ``` danger Do not perform further operations on the `ContentStoreObject` until the pause operation has completed. ##### Cancel download[​](#cancel-download "Direct link to Cancel download") Cancel and discard partially downloaded content using `cancelDownload()`: ```swift item.cancelDownload() ``` #### Monitor download progress via delegate[​](#monitor-download-progress-via-delegate "Direct link to Monitor download progress via delegate") Implement `ContentStoreObjectDelegate` on the item to receive status and progress callbacks: ```swift item.delegate = self // MARK: - ContentStoreObjectDelegate func contentStoreObject(_ object: ContentStoreObject, notifyStart hasProgress: Bool) { print("Download started, has progress: \(hasProgress)") } func contentStoreObject(_ object: ContentStoreObject, notifyProgress progress: Int32) { print("Download progress: \(progress)/100") } func contentStoreObject(_ object: ContentStoreObject, notifyComplete success: Bool) { print("Download completed: \(success)") } func contentStoreObject(_ object: ContentStoreObject, notifyStatusChanged status: ContentStoreObjectStatus) { print("Status changed: \(status.rawValue)") } ``` #### Delete downloaded content[​](#delete-downloaded-content "Direct link to Delete downloaded content") Check if an item can be removed, then call `deleteContent()`: ```swift if item.canDeleteContent() { item.deleteContent() print("Content deleted") } else { print("Content cannot be deleted") } ``` #### Relevant examples demonstrating manage content related features[​](#relevant-examples-demonstrating-manage-content-related-features "Direct link to Relevant examples demonstrating manage content related features") * [Map Download](/docs/ios/examples/maps-3dscene/map-download.md) --- ### Update Content Last updated: April 3, 2026 | 4 minutes read

The Maps SDK for iOS allows updating downloaded content to stay synchronized with the latest map data. New map versions are released every few weeks. The update operation is supported via `MapsContext` for road maps and via `MapStyleContext` for map styles. The SDK requires all road map content items to share the same version. Partial updates of individual items are not supported. danger The update process invalidates all routes currently in use. Ensure there are no active navigation sessions, route calculations, or similar operations running when applying an update. If a navigation session or route calculation is in progress at the time of the update, it will fail. Interacting with objects created before the update — such as route or navigation instruction objects — may lead to undefined behavior. Cancel all ongoing operations and discard related objects before applying the update. info The iOS SDK does not provide an automatic update mechanism for offline content. All updates for the downloaded content must be manually triggered via the `MapsContext` or `MapStyleContext` API. #### Content online support status[​](#content-online-support-status "Direct link to Content online support status") Based on the client's content version relative to the newest available release, it can be in one of three states, defined by `ContentStoreOnlineSupportStatus`: | Status | Description | | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ContentStoreOnlineSupportStatusUpToDate` | The client version is current. Features work online when connected and offline using downloaded regions. No updates are available. | | `ContentStoreOnlineSupportStatusOldData` | The client version is outdated but still supports online operations. An update is recommended. | | `ContentStoreOnlineSupportStatusExpiredData` | The client version is significantly outdated and no longer supports online operations. All features function exclusively on downloaded regions. An update is **mandatory** to restore online functionality. | #### Check for updates[​](#check-for-updates "Direct link to Check for updates") Call `checkForUpdate(_:)` on `MapsContext` to check whether a new version is available: ```swift let mapsContext = MapsContext() mapsContext.checkForUpdate { status in switch status { case .upToDate: print("Map is up to date") case .oldData: print("A new map version is available — update recommended") case .expiredData: print("Map version has expired — update required") @unknown default: break } } ``` #### Start the update[​](#start-the-update "Direct link to Start the update") Call `update(withAllowCellularNetwork:completionHandler:)` to download and automatically apply the update once all items are downloaded: ```swift mapsContext.update(withAllowCellularNetwork: false) { success in if success { print("Update completed. New version: \(mapsContext.getWorldMapVersion())") } else { print("Update failed") } } ``` Set `allowCellularNetwork` to `true` to permit downloading over cellular data. ##### Defer applying the update[​](#defer-applying-the-update "Direct link to Defer applying the update") Use the `canUpdateBeAppliedHandler` variant to control when the update is applied — for example, to wait until active navigation ends: ```swift mapsContext.update(withAllowCellularNetwork: false, canUpdateBeAppliedHandler: { [weak self] in guard let strongSelf = self else { return false } // Return true only when safe to apply (e.g. no active navigation) return strongSelf.navigationContext.isNavigationActive() }) { success in print("Update result: \(success)") } ``` #### Monitor update progress via delegate[​](#monitor-update-progress-via-delegate "Direct link to Monitor update progress via delegate") Assign a `ContentUpdateDelegate` to `delegateUpdate` on `MapsContext` to receive status and progress callbacks throughout the update process: ```swift mapsContext.delegateUpdate = self // MARK: - ContentUpdateDelegate func contextUpdate(_ context: NSObject, notifyStart hasProgress: Bool) { print("Update started") } func contextUpdate(_ context: NSObject, notifyProgress progress: Int32) { print("Update progress: \(progress)/100") } func contextUpdate(_ context: NSObject, notifyComplete success: Bool) { print("Update completed: \(success)") } func contextUpdate(_ context: NSObject, notifyStatusChanged status: ContentUpdateStatus) { print("Update status: \(status.rawValue)") } ``` **`ContentUpdateStatus` values:** | Value | Description | | --------------------------------------------- | ---------------------------------------------- | | `ContentUpdateStatusIdle` | Not started | | `ContentUpdateStatusWaitConnection` | Waiting for internet connection | | `ContentUpdateStatusWaitWIFIConnection` | Waiting for Wi-Fi connection | | `ContentUpdateStatusCheckForUpdate` | Checking for updated items | | `ContentUpdateStatusDownload` | Downloading updated content | | `ContentUpdateStatusFullyReady` | Update fully downloaded and ready to apply | | `ContentUpdateStatusPartiallyReady` | Update partially downloaded and ready to apply | | `ContentUpdateStatusDownloadRemainingContent` | Downloading remaining content after appliance | | `ContentUpdateStatusDownloadPendingContent` | Downloading pending content | | `ContentUpdateStatusComplete` | Finished with success | | `ContentUpdateStatusError` | Finished with error | #### Cancel the update[​](#cancel-the-update "Direct link to Cancel the update") Call `cancelUpdate()` to abort an in-progress update: ```swift mapsContext.cancelUpdate() ``` #### Check update state[​](#check-update-state "Direct link to Check update state") Query the current update state at any time: ```swift let isStarted = mapsContext.isUpdateStarted() let progress = mapsContext.getUpdateProgress() // 0–100 let status = mapsContext.getUpdateStatus() let canApply = mapsContext.canApplyUpdate() let updateItems = mapsContext.getUpdateItems() ``` Call `applyUpdate()` manually if you want to trigger the application of a fully downloaded update: ```swift if mapsContext.canApplyUpdate() { mapsContext.applyUpdate() } ``` #### Update map styles[​](#update-map-styles "Direct link to Update map styles") The same update API is available via `MapStyleContext`: ```swift let styleContext = MapStyleContext() styleContext.checkForUpdate { status in if status != .upToDate { styleContext.update(withAllowCellularNetwork: false) { success in print("Style update result: \(success)") } } } ``` #### Relevant examples demonstrating content update related features[​](#relevant-examples-demonstrating-content-update-related-features "Direct link to Relevant examples demonstrating content update related features") * [Map Update](/docs/ios/examples/maps-3dscene/map_update.md) --- ### Positioning & Sensors This section covers positioning workflows in the Maps SDK for iOS: data sources, live and custom positions, follow-position behavior, recorder setup, projection conversion, and camera-feed integration. Use these guides to start from device location updates and move toward advanced testing and playback scenarios. #### [📄️Sensors and data sources](/docs/ios/guides/positioning/sensors-and-data-sources.md) [Learn how DataSourceContext provides live, playback, simulation, and external sensor streams for positioning workflows.](/docs/ios/guides/positioning/sensors-and-data-sources.md) #### [📄️Get started with positioning](/docs/ios/guides/positioning/get-started-positioning.md) [Learn how to configure permissions and start receiving live position updates in iOS.](/docs/ios/guides/positioning/get-started-positioning.md) #### [📄️Show location on map](/docs/ios/guides/positioning/show-your-location-on-the-map.md) [Learn how to follow the current position, tune follow-position behavior, and customize the position tracker on the map.](/docs/ios/guides/positioning/show-your-location-on-the-map.md) #### [📄️Custom positioning](/docs/ios/guides/positioning/custom-positioning.md) [Learn how to push custom positions into an external DataSourceContext and consume them through PositionContext.](/docs/ios/guides/positioning/custom-positioning.md) #### [📄️Recorder](/docs/ios/guides/positioning/recorder.md) [The Recorder module manages sensor data recording with configurable parameters through RecorderConfigurationObject. Key features include:](/docs/ios/guides/positioning/recorder.md) #### [📄️Projections](/docs/ios/guides/positioning/projections.md) [Learn how to create projection objects and convert between coordinate systems using ProjectionContext.](/docs/ios/guides/positioning/projections.md) #### [📄️Background location](/docs/ios/guides/positioning/background-location.md) [Learn how to enable location updates while your app is in background on iOS.](/docs/ios/guides/positioning/background-location.md) #### [📄️Camera feed](/docs/ios/guides/positioning/camera-feed.md) [The SDK DataSourceContext provides access to camera frames for both live camera feeds and recorded logs.](/docs/ios/guides/positioning/camera-feed.md) --- ### Background location Last updated: March 31, 2026 | 1 minute read

Learn how to enable location updates while your app is in background on iOS. #### Step 1: Configure Info.plist[​](#step-1-configure-infoplist "Direct link to Step 1: Configure Info.plist") Add permission and background modes: ```xml NSLocationAlwaysAndWhenInUseUsageDescription Location is needed for map localization and navigation. UIBackgroundModes location processing ``` #### Step 2: Enable background updates in data source configuration[​](#step-2-enable-background-updates-in-data-source-configuration "Direct link to Step 2: Enable background updates in data source configuration") ```swift let source = DataSourceContext() let config = DataSourceConfigurationObject() config.setAllowBackgroundLocationUpdates(true) _ = source.setConfiguration(config, for: .position) _ = source.start() ``` warning Make sure `setAllowBackgroundLocationUpdates(true)` is called only if the `Info.plist` is properly configured. #### Step 3: Continue consuming updates[​](#step-3-continue-consuming-updates "Direct link to Step 3: Continue consuming updates") Use your existing `PositionContext` flow in the same way as foreground mode: ```swift let positionContext = PositionContext(context: source) positionContext.startUpdatingPositionDelegate(.position) ``` --- ### Camera feed Last updated: March 31, 2026 | 3 minutes read

The SDK `DataSourceContext` provides access to camera frames for both live camera feeds and recorded logs. Learn how to render those frames using `CameraRenderViewController`. Tip Recorded logs that include camera data are stored as `.mp4` files and can be played with standard iOS media players (for example `AVPlayer`). For simple log playback scenarios, a system player is often the best option. #### Create a camera render view[​](#create-a-camera-render-view "Direct link to Create a camera render view") `CameraRenderViewController` is the UI component that renders camera frames on screen. Initialize it with a data source context: ```swift let source = DataSourceContext() let cameraViewController = CameraRenderViewController(context: source) ``` You can also use `init(context:ppi:scale:)` when you need explicit control over rendering parameters tied to the device display. danger The `DataSourceContext` used by `CameraRenderViewController` must provide camera data. If camera data is missing, rendering cannot start. danger For live camera feed, ensure a video recording session is active for the same data source context. Add it like a standard child view controller: ```swift addChild(cameraViewController) view.addSubview(cameraViewController.view) cameraViewController.didMove(toParent: self) ``` #### Start and stop camera feed[​](#start-and-stop-camera-feed "Direct link to Start and stop camera feed") Use `startCamera()` and `startRender()` to begin camera acquisition and frame rendering. Stop both when the feed is no longer needed. ```swift _ = cameraViewController.startCamera() cameraViewController.startRender() // ... cameraViewController.stopRender() _ = cameraViewController.stopCamera() ``` #### Properties[​](#properties "Direct link to Properties") Use these values and methods as the runtime state surface for `CameraRenderViewController`: * `DataSourceContext` (constructor input) - Source of live or recorded camera frames used by the renderer * `view` - The `UIView` surface where frames are displayed * `getFrameFit()` - Current `FrameFit` used to scale camera frames in the view * `isRenderActive()` - Indicates whether rendering is currently active * `startCamera()` / `stopCamera()` return value - Boolean result showing whether camera start/stop succeeded #### Control frame fitting[​](#control-frame-fitting "Direct link to Control frame fitting") `setFrameFit(_:)` controls how camera frames are scaled in the view. Read the current value with `getFrameFit()`. ```swift cameraViewController.setFrameFit(.inside) let fit = cameraViewController.getFrameFit() print("frame fit = \(fit)") ``` ##### Observe playback progress for logs[​](#observe-playback-progress-for-logs "Direct link to Observe playback progress for logs") When rendering camera frames from recorded logs, use the progress callback to receive playback time updates in milliseconds. ```swift cameraViewController.setOnLogProgressCallback { timeMs in print("camera log progress: \(timeMs) ms") } ``` #### Lifecycle management[​](#lifecycle-management "Direct link to Lifecycle management") Manage `CameraRenderViewController` explicitly to avoid camera/render resource leaks. Recommended flow: 1. Create and attach the controller as a child view controller. 2. Start camera and render when the feed is needed. 3. Stop render and camera when the feed is no longer visible. 4. Call `destroy()` before disposal. ##### Cleanup[​](#cleanup "Direct link to Cleanup") Destroy the camera render controller when it is no longer needed to release rendering and camera resources. ```swift cameraViewController.destroy() ``` --- ### Custom positioning Last updated: March 31, 2026 | 3 minutes read

Learn how to push custom positions into an external `DataSourceContext` and consume them through `PositionContext`. Custom positioning is useful when your app needs to replay known trajectories, inject telemetry from an external system, or test navigation behavior without relying on device GPS. Tip For simulation and testing flows, custom and playback data sources let you validate behavior consistently across runs. #### Create an external data source[​](#create-an-external-data-source "Direct link to Create an external data source") Create an external source that accepts `DataType.position` values: ```swift guard let externalSource = DataSourceContext(externalDataTypes: [NSNumber(value: DataType.position.rawValue)]) else { return } _ = externalSource.start() ``` This source will accept `DataObject` samples pushed by your app. If creation fails, the source is `nil`, so keep the `guard` check. #### Push custom position samples[​](#push-custom-position-samples "Direct link to Push custom position samples") Create positions with `PositionObject.createPosition(...)`, then push their underlying `DataObject`. ```swift let nowMs = Int(Date().timeIntervalSince1970 * 1000) let custom = PositionObject.createPosition( nowMs, latitude: 48.85682, longitude: 2.34375, altitude: 0, course: 0, speed: 0, speedAccuracy: 1, horizontalAccuracy: 5, verticalAccuracy: 5, courseAccuracy: 5 ) if let dataObject = custom?.getDataObject() { _ = externalSource.pushData(dataObject) } ``` For more stable map-matching results, provide realistic values for: * `course` - movement heading in degrees * `speed` - speed in meters per second * `horizontalAccuracy` / `verticalAccuracy` - confidence of the sample ##### Push updates continuously[​](#push-updates-continuously "Direct link to Push updates continuously") In real flows, push multiple samples over time instead of a single point: ```swift let timer = DispatchSource.makeTimerSource(queue: .main) timer.schedule(deadline: .now(), repeating: .milliseconds(200)) timer.setEventHandler { let nowMs = Int(Date().timeIntervalSince1970 * 1000) let sample = PositionObject.createPosition( nowMs, latitude: 48.85682, longitude: 2.34375, altitude: 0, course: 45, speed: 8, speedAccuracy: 1, horizontalAccuracy: 4, verticalAccuracy: 6, courseAccuracy: 5 ) if let data = sample?.getDataObject() { _ = externalSource.pushData(data) } } timer.resume() ``` #### Read custom data through PositionContext[​](#read-custom-data-through-positioncontext "Direct link to Read custom data through PositionContext") ```swift let positionContext = PositionContext(context: externalSource) positionContext.startUpdatingPositionDelegate(.position) if let position = positionContext.getPosition(.position) { print(position.getCoordinates()) } ``` If you need road-aware values (such as road modifiers or road speed limits), start updates with `.improvedPosition` instead of `.position`. #### Remove custom data source[​](#remove-custom-data-source "Direct link to Remove custom data source") Stop updates and clean resources when done: ```swift positionContext.stopUpdatingPositionDelegate() _ = externalSource.stop() ``` warning Always stop external data sources when no longer needed to avoid conflicting updates in other flows. If you started a timer or any producer loop, stop it before stopping the source. #### Playback and simulation alternatives[​](#playback-and-simulation-alternatives "Direct link to Playback and simulation alternatives") Use these source initializers when you need repeatable test runs: ```swift let simulationSource = DataSourceContext(route: route) let logSource = DataSourceContext(filePath: "/path/to/recording.gpx") guard let simulationSource, let logSource else { return } ``` * `DataSourceContext(route:)` is useful for deterministic route simulation. * `DataSourceContext(filePath:)` is useful for replaying recorded tracks. #### Relevant examples demonstrating custom positioning related features[​](#relevant-examples-demonstrating-custom-positioning-related-features "Direct link to Relevant examples demonstrating custom positioning related features") * [Following Position](/docs/ios/examples/maps-3dscene/following-position.md) --- ### Get started with positioning Last updated: March 31, 2026 | 2 minutes read

Learn how to configure permissions and start receiving live position updates in iOS. #### Step 1: Add location permission[​](#step-1-add-location-permission "Direct link to Step 1: Add location permission") Add the following key to your `Info.plist`: ```xml NSLocationWhenInUseUsageDescription Location is needed for map localization and navigation. ``` #### Step 2: Ask for user consent[​](#step-2-ask-for-user-consent "Direct link to Step 2: Ask for user consent") After declaring the usage description, request location authorization at runtime. iOS shows the system permission dialog only after you call the Core Location authorization API. ```swift import CoreLocation final class PositionPermissionHandler: NSObject, CLLocationManagerDelegate { private let locationManager = CLLocationManager() func requestPermission() { locationManager.delegate = self locationManager.requestWhenInUseAuthorization() } func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { switch manager.authorizationStatus { case .authorizedWhenInUse, .authorizedAlways: print("Location permission granted") case .denied, .restricted: print("Location permission denied") case .notDetermined: break @unknown default: break } } } let permissionHandler = PositionPermissionHandler() permissionHandler.requestPermission() ``` If the user denies the request, iOS does not show the permission popup again automatically. In that case, direct the user to the app's Settings screen to enable location access manually. #### Step 3: Configure data source and position context[​](#step-3-configure-data-source-and-position-context "Direct link to Step 3: Configure data source and position context") Create a live `DataSourceContext`, configure it for position updates, then bind `PositionContext` to it. ```swift let source = DataSourceContext() let config = DataSourceConfigurationObject() config.setPositionAccuracy(.whenMoving) config.setPositionActivity(.automotive) config.setPositionDistanceFilter(0) _ = source.setConfiguration(config, for: .position) _ = source.start() let positionContext = PositionContext(context: source) ``` #### Step 4: Receive updates[​](#step-4-receive-updates "Direct link to Step 4: Receive updates") Use `PositionContextDelegate` for continuous updates: ```swift final class PositionHandler: NSObject, PositionContextDelegate { func positionContext(_ positionContext: PositionContext, didUpdatePosition position: PositionObject) { guard position.hasCoordinates() else { return } let c = position.getCoordinates() print("lat=\(c.latitude) lon=\(c.longitude)") } } let handler = PositionHandler() positionContext.delegate = handler positionContext.startUpdatingPositionDelegate(.improvedPosition) ``` #### Read current snapshot[​](#read-current-snapshot "Direct link to Read current snapshot") ```swift if let latest = positionContext.getPosition(.improvedPosition) { print("speed m/s = \(latest.getSpeed())") } ``` #### Relevant examples demonstrating positioning related features[​](#relevant-examples-demonstrating-positioning-related-features "Direct link to Relevant examples demonstrating positioning related features") * [Position Tracker](/docs/ios/examples/maps-3dscene/position-tracker.md) * [Following Position](/docs/ios/examples/maps-3dscene/following-position.md) --- ### Projections Last updated: March 31, 2026 | 4 minutes read

Learn how to create projection objects and convert between coordinate systems using `ProjectionContext`. #### Supported projection types[​](#supported-projection-types "Direct link to Supported projection types") * `WGS84` - World Geodetic System 1984 * `GK` - Gauss-Kruger * `UTM` - Universal Transverse Mercator * `LAM` - Lambert * `BNG` - British National Grid * `MGRS` - Military Grid Reference System * `W3W` - What three words #### WGS84 projection[​](#wgs84-projection "Direct link to WGS84 projection") The **WGS84** projection is a widely used geodetic datum that serves as the foundation for GPS and other mapping systems. Create a `WGS84` projection using coordinates: ```swift let wgs = ProjectionWGS84Object( coordinates: CoordinatesObject.coordinates(withLatitude: 51.5074, longitude: -0.1278) ) ``` Access and modify coordinates using the getter and setter: ```swift if let coords = wgs.getCoordinates() { print("latitude=\(coords.getLatitude()) longitude=\(coords.getLongitude())") } wgs.setCoordinates( CoordinatesObject.coordinates(withLatitude: 10.0, longitude: 10.0) ) ``` #### GK projection[​](#gk-projection "Direct link to GK projection") The **Gauss-Kruger** projection is a cylindrical map projection commonly used for large-scale mapping in regions with a north-south orientation. It divides the Earth into zones, each with its own coordinate system. Create a `Gauss-Kruger` projection: ```swift let gk = ProjectionGKObject(x: 6325113.72, y: 5082540.66, zone: 1) ``` Access and modify values using getter and setter methods: ```swift let easting = gk.getEasting() let northing = gk.getNorthing() let zone = gk.getZone() print("easting=\(easting) northing=\(northing) zone=\(zone)") gk.setX(1, setY: 1, zone: 2) ``` danger The `Gauss-Kruger` projection is currently supported only for countries that use the **Bessel ellipsoid**. Converting to and from `Gauss-Kruger` projection for other countries will result in an `SDKErrorCode` error. #### BNG projection[​](#bng-projection "Direct link to BNG projection") The **BNG** (British National Grid) projection is a coordinate system used in Great Britain for mapping and navigation. It provides a grid reference system for precise location identification. Create a `BNG` projection using easting and northing: ```swift let bng = ProjectionBNGObject(easting: 500000, northing: 4649776) ``` Or create from a grid reference string: ```swift let bng2 = ProjectionBNGObject(gridReference: "TL56") ``` Access and modify values: ```swift let easting = bng.getEasting() let northing = bng.getNorthing() let gridRef = bng.getGridReference() print("easting=\(easting) northing=\(northing) grid=\(gridRef)") bng.setEasting(1, northing: 1) bng.setGridReference("SJ23") ``` #### MGRS projection[​](#mgrs-projection "Direct link to MGRS projection") The **MGRS** (Military Grid Reference System) projection is a coordinate system used by the military for precise location identification. It combines the UTM and UPS coordinate systems. Create a `MGRS` projection: ```swift let mgrs = ProjectionMGRSObject(easting: 99316, northing: 10163, zone: "30U", letters: "XC") ``` Access and modify values: ```swift let easting = mgrs.getEasting() let northing = mgrs.getNorthing() let zone = mgrs.getZone() let sq100k = mgrs.getSq100kIdentifier() print("easting=\(easting) northing=\(northing) zone=\(zone) sq100k=\(sq100k)") mgrs.setEasting(1, northing: 1, zone: "B", letters: "AB") ``` #### W3W projection[​](#w3w-projection "Direct link to W3W projection") The **W3W** (What three words) projection is a geocoding system that divides the world into a grid of 3m x 3m squares, each identified by a unique combination of three words. Create a `W3W` projection with an API token: ```swift let w3w = ProjectionW3WObject(token: "your-api-token") ``` Access and modify token and words: ```swift let token = w3w.getToken() let words = w3w.getWords() w3w.setToken("new-token") w3w.setWords("///hello.world.test") ``` #### LAM projection[​](#lam-projection "Direct link to LAM projection") The **LAM** (Lambert) projection is a conic map projection commonly used for large-scale mapping in regions with an east-west orientation. Create a `LAM` projection: ```swift let lam = ProjectionLAMObject(x: 6325113.72, y: 5082540.66) ``` Access and modify coordinates: ```swift let x = lam.getX() let y = lam.getY() print("x=\(x) y=\(y)") lam.setX(1, setY: 1) ``` #### UTM projection[​](#utm-projection "Direct link to UTM projection") The **UTM** (Universal Transverse Mercator) projection is a global map projection that divides the world into a series of zones, each with its own coordinate system. Create a `UTM` projection: ```swift let utm = ProjectionUTMObject(x: 6325113.72, y: 5082540.66, zone: 1, hemisphere: .north) ``` Access and modify values: ```swift let x = utm.getX() let y = utm.getY() let zone = utm.getZone() let hemisphere = utm.getHemisphere() print("x=\(x) y=\(y) zone=\(zone) hemisphere=\(hemisphere)") utm.setX(1, setY: 1, zone: 2, hemisphere: .south) ``` #### Convert between projections[​](#convert-between-projections "Direct link to Convert between projections") `ProjectionContext.convert(_:to:completionHandler:)` converts from one projection object to another. ```swift let context = ProjectionContext() let from = ProjectionWGS84Object( coordinates: CoordinatesObject.coordinates(withLatitude: 51.5074, longitude: -0.1278) ) let to = ProjectionMGRSObject(easting: 0, northing: 0, zone: "", letters: "") let code = context.convert(from, to: to) { result in print("conversion result = \(result)") print("zone=\(to.getZone()) easting=\(to.getEasting()) northing=\(to.getNorthing())") } print("operation started with code = \(code)") ``` danger `ProjectionContext.convert` works with `ProjectionW3WObject` only if the object has a **valid** token that can be obtained from [what3words.com](https://developer.what3words.com/public-api). If the token is not set or invalid, the conversion will fail and return `SDKErrorCodeKNotSupported`. #### Relevant examples demonstrating projections related features[​](#relevant-examples-demonstrating-projections-related-features "Direct link to Relevant examples demonstrating projections related features") * [Projections](/docs/ios/examples/maps-3dscene/projections.md) --- ### Recorder Last updated: March 31, 2026 | 9 minutes read

The Recorder module manages sensor data recording with configurable parameters through `RecorderConfigurationObject`. Key features include: * **Customizable storage options** - Define log directories, manage disk space, and specify recording duration * **Data type selection** - Specify which data types (video, audio, or sensor data) to record * **Video and audio options** - Set video resolution, enable/disable audio recording, and manage chunk durations. Record sensor data only, sensor data + video, sensor data + audio, or all three Control the recording lifecycle: * Start, stop, pause, and resume recordings * Automatic restarts for continuous recording with chunked durations The Recorder supports various transportation modes (car, pedestrian, bike), enabling detailed analysis and classification based on context. Set disk space limits to prevent overwhelming device storage - logs are automatically managed based on retention thresholds. #### Create recorder configuration[​](#create-recorder-configuration "Direct link to Create recorder configuration") Create a recorder configuration with a logs folder and a data source context. ```swift let source = DataSourceContext() let config = RecorderConfigurationObject(folderPath: tracksPath, context: source) config.setDataType(.position) config.setMinDurationSeconds(10) config.setChunkDurationSeconds(config.kInfiniteRecording) config.setContinuousRecording(false) ``` `RecorderConfigurationObject` controls what is recorded, how long chunks are, and how storage limits are applied. ##### Common configuration options[​](#common-configuration-options "Direct link to Common configuration options") | API | Description | | ------------------------------- | ----------------------------------------------------------------- | | `setDataType(_:)` | Adds a data type to be recorded (for example position, camera). | | `setMinDurationSeconds(_:)` | Minimum duration required for saving a recording. | | `setChunkDurationSeconds(_:)` | Chunk length in seconds (`kInfiniteRecording` disables chunking). | | `setContinuousRecording(_:)` | Restarts recording automatically at chunk boundaries. | | `setEnableAudio(_:)` | Enables audio recording (when permitted). | | `setVideoQuality(_:)` | Sets camera recording resolution (`ResolutionFormat`). | | `setMaxDiskSpaceUsed(_:)` | Caps storage used by all logs. | | `setKeepMinSeconds(_:)` | Minimum retained recording duration before cleanup is allowed. | | `setDeleteOlderThanKeepMin(_:)` | Deletes older logs once retention threshold is exceeded. | | `setTransportMode(_:)` | Sets transport mode metadata for the recording. | You can also enable audio: ```swift config.setEnableAudio(true) ``` Set video quality with one of the `ResolutionFormat` values (for example `ResolutionFormatHD_720p`) when recording camera frames. warning If the recording duration is shorter than the configured `setMinDurationSeconds(_:)`, the `stopRecording()` method does not save the recording and returns `SDKErrorCodeRecordedLogTooShort`. The `SDKErrorCodeRecordedLogTooShort` error may also occur if an insufficient number of positions were emitted, even when the duration between `startRecording()` and `stopRecording()` exceeds `setMinDurationSeconds(_:)`. To test recording functionality, create a custom external `DataSourceContext` and push custom positions. Refer to the [custom positioning guide](/docs/ios/guides/positioning/custom-positioning.md) for details. The `SDKErrorCodeKGeneral` result might be returned if the application has been sent to the background without the required configuration. See the Recording lifecycle section below for information about proper recording management. If `setChunkDurationSeconds(_:)` is set too high, it may cause `SDKErrorCodeKNoDiskSpace` since the SDK determines how much space is required for the entire chunk. Ensure that the `DataType` values passed via `setDataType(_:)` are supported by the target platform. Using unsupported data types may cause the `startRecording()` method to return `SDKErrorCodeKInvalidInput`. ##### Resolution formats[​](#resolution-formats "Direct link to Resolution formats") | Enum value | Resolution | | ------------------------------ | ----------- | | `ResolutionFormatSD_480p` | 640 × 480 | | `ResolutionFormatHD_720p` | 1280 × 720 | | `ResolutionFormatFullHD_1080p` | 1920 × 1080 | | `ResolutionFormatWQHD_1440p` | 2560 × 1440 | | `ResolutionFormatUHD_4K_2160p` | 3840 × 2160 | | `ResolutionFormatUHD_8K_4320p` | 7680 × 4320 | #### Recorder permissions (iOS)[​](#recorder-permissions-ios "Direct link to Recorder permissions (iOS)") Add the required usage descriptions in `Info.plist` depending on what you record: ```xml NSLocationWhenInUseUsageDescription Location is needed for recording location-tagged logs. NSCameraUsageDescription Camera is needed for video recording. NSMicrophoneUsageDescription Microphone is needed for audio recording. ``` Location is required for position-based recording. Add camera/microphone keys when recording camera or audio streams. #### Start and stop recording[​](#start-and-stop-recording "Direct link to Start and stop recording") ```swift let recorder = RecorderContext(configuration: config) let startCode = recorder.startRecording() print("start = \(startCode)") // ... let stopCode = recorder.stopRecording() print("stop = \(stopCode)") ``` `startRecording()` and `stopRecording()` return `SDKErrorCode`, which should always be checked in production flows. #### Pause and resume[​](#pause-and-resume "Direct link to Pause and resume") ```swift _ = recorder.pauseRecording() _ = recorder.resumeRecording() ``` Pause/resume is useful when the session should remain open but data capture is temporarily suspended. #### Recording lifecycle[​](#recording-lifecycle "Direct link to Recording lifecycle") Typical lifecycle: 1. Create `RecorderConfigurationObject`. 2. Create `RecorderContext`. 3. Start recording. 4. Optionally pause/resume or toggle audio. 5. Stop recording and inspect resulting path / status. Recorder states are exposed through `RecorderStatus`: | Status | Meaning | | ------------------------------------------------ | ----------------------------------------------- | | `RecorderStatusStopped` | Recorder is idle. | | `RecorderStatusStarting` | Start is in progress. | | `RecorderStatusRecording` | Recording is active. | | `RecorderStatusPausing` / `RecorderStatusPaused` | Pause transition / paused state. | | `RecorderStatusResuming` | Resume transition in progress. | | `RecorderStatusStopping` | Stop is in progress. | | `RecorderStatusRestarting` | Restart due to configuration or chunk behavior. | #### Track status via delegate[​](#track-status-via-delegate "Direct link to Track status via delegate") ```swift final class RecorderHandler: NSObject, RecorderContextDelegate { func recorderContext(_ recorderContext: RecorderContext, recordingStatusChanged status: RecorderStatus) { print("status=\(status)") } func recorderContext(_ recorderContext: RecorderContext, recordingCompleted path: String, code: SDKErrorCode) { print("saved at \(path), code=\(code)") } } let handler = RecorderHandler() recorder.delegate = handler ``` Use the delegate to drive UI state, progress labels, and post-recording workflows. #### Record audio and camera streams[​](#record-audio-and-camera-streams "Direct link to Record audio and camera streams") Enable optional streams through configuration and recorder controls: ```swift config.setDataType(.camera) config.setVideoQuality(.hd_720p) config.setEnableAudio(true) let recorder = RecorderContext(configuration: config) _ = recorder.startRecording() recorder.startAudioRecording() // ... recorder.stopAudioRecording() _ = recorder.stopRecording() ``` #### Recorder bookmarks and metadata[​](#recorder-bookmarks-and-metadata "Direct link to Recorder bookmarks and metadata") The SDK uses the proprietary `.gm` format for recordings and provides APIs to inspect, convert, and enrich recorded logs. Use `LogBookmarksContext` for log management tasks: * **Export and import logs** - Convert logs to and from formats such as GPX, NMEA, KML, GeoJSON, CSV, FIT, and TCX * **Log metadata access** - Read timestamps, coordinates, routes, transport mode, and size * **Custom metadata and files** - Attach or read additional binary metadata and activity-related files ```swift let bookmarks = LogBookmarksContext(folderPath: tracksPath) let logs = bookmarks.getLogs() ``` ##### Export logs[​](#export-logs "Direct link to Export logs") ```swift let bookmarks = LogBookmarksContext(folderPath: yourPath) let logs = bookmarks.getLogs() if let logPath = logs.last { let code = bookmarks.exportLog( logPath, to: .gpx, exportedFileName: "My_File_Name", positionDistance: 0 ) print("export = \(code)") } ``` The resulting file is `My_File_Name.gpx`. If `exportedFileName` is empty, the SDK uses the original log name. warning Exporting a `.gm` file to other formats can lead to data loss, depending on which sensor types are supported by the target format. Tip The exported file is saved in the same directory as the original log file. ##### Import logs[​](#import-logs "Direct link to Import logs") Import logs from standard formats such as `gpx`, `nmea`, or `kml` into SDK log format: ```swift let bookmarks = LogBookmarksContext(folderPath: tracksPath) let code = bookmarks.importLog("/path/to/file.gpx", importedFileName: "My_File_Name") print("import = \(code)") ``` ##### Access metadata[​](#access-metadata "Direct link to Access metadata") For iOS, metadata is accessed through `LogBookmarksContext` methods for a given log path: ```swift let bookmarks = LogBookmarksContext(folderPath: tracksPath) let logs = bookmarks.getLogs() if let logPath = logs.last, bookmarks.isMetadataAvailable(logPath) { let start = bookmarks.getStartTimestamp(logPath) let end = bookmarks.getEndTimestamp(logPath) let duration = bookmarks.getDuration(logPath) let startPosition = bookmarks.getStartPosition(logPath) let endPosition = bookmarks.getEndPosition(logPath) let route = bookmarks.getRoute(logPath) let preciseRoute = bookmarks.getPreciseRoute(logPath) let transportMode = bookmarks.getTransportMode(logPath) let isProtected = bookmarks.isProtected(logPath) let logSize = bookmarks.getLogSize(logPath) let metrics = bookmarks.getLogMetrics(logPath) print("start=\(start) end=\(end) duration=\(duration)") print("distance=\(metrics.distanceMeters) elevation=\(metrics.elevationGainMeters) avgSpeed=\(metrics.avgSpeedMps)") print("transport=\(transportMode) protected=\(isProtected) size=\(logSize)") print("startPos=\(String(describing: startPosition)) endPos=\(String(describing: endPosition))") print("routePoints=\(route.count) preciseRoutePoints=\(preciseRoute.count)") } ``` warning Check `isMetadataAvailable(_:)` before reading metadata. Metadata access can fail for missing files or invalid log content. The metadata exposed by `LogBookmarksContext` includes: * `getStartPosition(_:)` / `getEndPosition(_:)` - Geographic coordinates for the start and end of the log * `getRoute(_:)` - Reduced route polyline * `getPreciseRoute(_:)` - Full recorded coordinate sequence * `getTransportMode(_:)` - `RecordingTransportMode` used while recording * `getStartTimestamp(_:)` / `getEndTimestamp(_:)` - Timestamp of first/last recorded sensor data * `getDuration(_:)` / `getActiveDuration(_:)` - Total and active recording durations * `isProtected(_:)` - Whether the log is protected from automatic cleanup * `getLogSize(_:)` - Log size in bytes * `isDataTypeAvailable(_:filePath:)` - Whether a specific `DataType` exists in the log * `getSoundMarks(_:)` - Recorded sound marks metadata * `getLogMetrics(_:)` - `LogBookmarksMetrics` values (`distanceMeters`, `elevationGainMeters`, `avgSpeedMps`) ##### Custom user metadata[​](#custom-user-metadata "Direct link to Custom user metadata") Add custom metadata while recording with `RecorderContext`, then read it from `LogBookmarksContext`: ```swift let config = RecorderConfigurationObject(folderPath: tracksPath, context: source) config.setDataType(.position) let recorder = RecorderContext(configuration: config) _ = recorder.startRecording() let text = "Hello world" if let textData = text.data(using: .utf8) { recorder.addUserMetadata("textData", buffer: textData) } _ = recorder.stopRecording() let bookmarks = LogBookmarksContext(folderPath: tracksPath) let logs = bookmarks.getLogs() if let logPath = logs.last, let payload = bookmarks.getUserMetadata(logPath, keyString: "textData"), let decoded = String(data: payload, encoding: .utf8) { print(decoded) } ``` #### Log Activity Files[​](#log-activity-files "Direct link to Log Activity Files") Represented by `LogActivityFileObject` attachments managed through `LogBookmarksContext`. ##### Attributes[​](#attributes "Direct link to Attributes") | Attribute | Description | | ---------------- | ----------------------------------------------------------- | | `activityFileID` | Unique identifier of the activity file. | | `activityID` | Identifier of the related activity. | | `fileType` | Type of attached file (`photo`, `video`, `audio`, `route`). | | `fileData` | Binary payload of the attached file. | | `offsetMillis` | Timestamp offset in milliseconds within the activity. | | `coordinates` | Coordinates where the file was captured. | ##### LogMetrics[​](#logmetrics "Direct link to LogMetrics") Use `LogBookmarksMetrics` (retrieved with `getLogMetrics(_:)`) for log statistics: | Attribute | Description | | --------------------- | ------------------------------------------------------------ | | `distanceMeters` | Total distance covered during the log, measured in meters. | | `elevationGainMeters` | Total elevation gained during the log, measured in meters. | | `avgSpeedMps` | Average speed during the log, measured in meters per second. | ##### Setting the activity record[​](#setting-the-activity-record "Direct link to Setting the activity record") Attach activity-related files to a log using `addActivityFiles(_:filePath:)`: ```swift let bookmarks = LogBookmarksContext(folderPath: tracksPath) let logs = bookmarks.getLogs() if let logPath = logs.last { let activityFile = LogActivityFileObject() activityFile.fileType = .photo activityFile.offsetMillis = 15_000 activityFile.coordinates = bookmarks.getStartPosition(logPath) activityFile.fileData = Data() // Replace with image/audio/video/route bytes bookmarks.addActivityFiles([activityFile], filePath: logPath) } ``` warning Attach activity files to an existing log file path. Ensure recording is completed and the target log exists before adding files. ##### Get the activity record[​](#get-the-activity-record "Direct link to Get the activity record") Retrieve attached files from a log: ```swift let bookmarks = LogBookmarksContext(folderPath: tracksPath) let logs = bookmarks.getLogs() if let logPath = logs.last { let activityFiles = bookmarks.getActivityFiles(logPath) print("activity files count = \(activityFiles.count)") } ``` #### Relevant examples demonstrating recorder related features[​](#relevant-examples-demonstrating-recorder-related-features "Direct link to Relevant examples demonstrating recorder related features") * [Finger Draw Route](/docs/ios/examples/routing-navigation/finger-drawn-route-navigation.md) --- ### Sensors and data sources Last updated: March 31, 2026 | 5 minutes read

Learn how `DataSourceContext` provides live, playback, simulation, and external sensor streams for positioning workflows. Use these APIs when you need to read device sensor data, replay recorded logs, simulate movement on a route, or inject your own custom samples. #### Sensor data types[​](#sensor-data-types "Direct link to Sensor data types") `DataSourceContext` works with `DataType` values exposed by the iOS SDK. The following data types are defined in GEMKit for iOS: | Type | Description | | --------------------------- | ------------------------------------------------------------------------------ | | `DataType.acceleration` | Linear acceleration data from device motion sensors. | | `DataType.activity` | Activity classification data exposed by the SDK data model. | | `DataType.attitude` | 3D device attitude/orientation data. | | `DataType.battery` | Battery state and level information. | | `DataType.camera` | Camera frames and related camera-feed data. | | `DataType.compass` | Heading information from compass sensors. | | `DataType.magneticField` | Raw magnetic-field sensor data. | | `DataType.orientation` | Device orientation updates. | | `DataType.position` | Raw geographic position data. | | `DataType.improvedPosition` | Improved or map-matched position data. | | `DataType.rotationRate` | Rotation-rate / gyroscope-style motion data. | | `DataType.temperature` | Device temperature information. | | `DataType.notification` | System or SDK notification-type data events. | | `DataType.mountInformation` | Device mount/orientation metadata, useful for camera and in-vehicle scenarios. | | `DataType.unknown` | Fallback value for unsupported or unclassified data. | For position structure details, see [Positions](/docs/ios/guides/core/positions.md). info Not every `DataType` is guaranteed to be available from every source or on every device. Use `getAvailableDataTypes()` or `isDataTypeAvailable(_:)` to inspect what a specific `DataSourceContext` can actually provide at runtime. #### Working with data sources[​](#working-with-data-sources "Direct link to Working with data sources") The main iOS classes used in this area are: | Class | Role | | ------------------------------- | ------------------------------------------------------------------------------ | | `DataSourceContext` | Main entry point for live, playback, simulation, and external data streams. | | `DataSourceConfigurationObject` | Configures how a source produces position-related data. | | `DataSourcePlaybackContext` | Adds playback controls such as pause, resume, seek, and speed multiplier. | | `DataSourceContextDelegate` | Receives new data, interruptions, progress updates, and playing-state changes. | #### Create data sources[​](#create-data-sources "Direct link to Create data sources") Create a source type that matches your scenario: | Source kind | Initializer | Typical use | | ---------------- | --------------------------------------- | ---------------------------------------------- | | Live | `DataSourceContext()` | Real device sensor input. | | Log playback | `DataSourceContext(filePath:)` | Replay a previously recorded file such as GPX. | | Route simulation | `DataSourceContext(route:)` | Generate simulated positions along a route. | | External | `DataSourceContext(externalDataTypes:)` | Push custom samples from your own system. | Use the following initializers: ```swift let liveSource = DataSourceContext() let logSource = DataSourceContext(filePath: "/path/to/track.gpx") let simulationSource = DataSourceContext(route: route) let externalSource = DataSourceContext(externalDataTypes: [NSNumber(value: DataType.position.rawValue)]) guard let logSource, let simulationSource, let externalSource else { return } ``` The live source initializer is non-nullable. Log, simulation, and external initializers are nullable and should be unwrapped with `guard let` or `if let`. #### Configure and control sources[​](#configure-and-control-sources "Direct link to Configure and control sources") Use `DataSourceConfigurationObject` to tune how position data is produced, then start or stop the source as needed. ```swift let source = DataSourceContext() let config = DataSourceConfigurationObject() config.setPositionAccuracy(.whenMoving) config.setPositionDistanceFilter(0) _ = source.setConfiguration(config, for: .position) _ = source.start() // ... _ = source.stop() ``` Common operations on `DataSourceContext`: | Method | Description | | ---------------------------- | ------------------------------------------------------------ | | `setConfiguration(_:for:)` | Applies configuration for a specific `DataType`. | | `start()` | Starts the source. | | `stop()` | Stops the source. | | `isStopped()` | Checks whether the source is stopped. | | `getAvailableDataTypes()` | Returns the data types currently supported by the source. | | `getLatestData(_:)` | Returns the latest sample for a specific type, if available. | | `setMockData(withPosition:)` | Applies mock position data for testing on supported sources. | | `pushData(_:)` | Pushes a `DataObject` into an external source. | ##### Inspect source metadata[​](#inspect-source-metadata "Direct link to Inspect source metadata") `DataSourceContext` also exposes helper information about the current source: | Method | Description | | ---------------------- | --------------------------------------------------------------- | | `getDataSourceType()` | Returns whether the source is `live`, `playback`, or `unknown`. | | `getOrigin()` | Tells whether the source is SDK-provided or external. | | `getLogPath()` | Returns the playback file path when applicable. | | `getCurrentPosition()` | Returns the current playback cursor in milliseconds. | | `getDuration()` | Returns the playback duration in milliseconds. | ##### Use playback controls[​](#use-playback-controls "Direct link to Use playback controls") For playback-capable sources, use `DataSourcePlaybackContext`. This applies to log or simulation flows, not standard live sensor sources. ```swift let playback = DataSourcePlaybackContext(context: logSource) playback.setSpeedMultiplier(2.0) _ = playback.pause() _ = playback.resume() ``` Playback-specific methods include: | Method | Description | | ------------------------ | --------------------------------------- | | `pause()` | Pauses playback. | | `resume()` | Resumes playback. | | `step()` | Advances playback incrementally. | | `getState()` | Returns the current `PlayingStatus`. | | `setSpeedMultiplier(_:)` | Adjusts playback speed. | | `setCurrentPosition(_:)` | Seeks to a specific playback timestamp. | | `getDuration()` | Returns total playback duration. | #### Listen for source events[​](#listen-for-source-events "Direct link to Listen for source events") Use `DataSourceContextDelegate` for new data, interruptions, and status changes. Main delegate callbacks: | Callback | Description | | ------------------------- | ------------------------------------------------------------------------- | | `onNewData` | Called when a new `DataObject` is available. | | `onPlayingStatusChanged` | Called when a source changes between stopped, paused, and playing states. | | `onDataInterruptionEvent` | Called when a data stream is interrupted or restored. | | `onProgressChanged` | Reports playback progress in milliseconds. | | `onDeviceMountedChanged` | Reports changes in mount state and portrait orientation. | | `onTemperatureChanged` | Reports thermal state changes. | Example: ```swift final class SourceListener: NSObject, DataSourceContextDelegate { func dataSourceContext(_ dataSourceContext: DataSourceContext, onNewData dataObject: DataObject) { print("new data type=\(dataObject.getType())") } func dataSourceContext(_ dataSourceContext: DataSourceContext, onPlayingStatusChanged type: DataType, status: PlayingStatus) { print("status for \(type) = \(status)") } // Implement other delegate methods as needed... } ``` ```swift func setupListener() { let listener = SourceListener() let source = DataSourceContext() source.delegate = listener _ = source.startDelegateNotification(with: .position) } ``` Call `stopDelegateNotification(with:)` or `stopDelegateNotifications()` when you no longer need updates. #### Relevant examples demonstrating sensors and data source related features[​](#relevant-examples-demonstrating-sensors-and-data-source-related-features "Direct link to Relevant examples demonstrating sensors and data source related features") * [Position Tracker](/docs/ios/examples/maps-3dscene/position-tracker.md) * [Following Position](/docs/ios/examples/maps-3dscene/following-position.md) --- ### Show location on map Last updated: March 31, 2026 | 3 minutes read

Learn how to follow the current position, tune follow-position behavior, and customize the position tracker on the map. When the live data source is active and location permission is granted, the SDK renders the position tracker on the map. In follow mode, the camera keeps the tracker in view and can rotate with heading depending on your follow settings. info GPS quality can vary depending on the environment. Indoors, urban canyons, tunnels, or areas with poor sky visibility can reduce accuracy and make the tracker appear less stable, especially while stationary. #### Start and stop follow position[​](#start-and-stop-follow-position "Direct link to Start and stop follow position") Use `MapViewController` follow APIs: ```swift mapViewController.startFollowingPosition(withAnimationDuration: 700, zoomLevel: -1, viewAngle: 0) { success in print("following started: \(success)") } // ... mapViewController.stopFollowingPosition() ``` Use `zoomLevel: -1` for automatic zoom while following. The `viewAngle` argument controls tilt at follow start. Follow mode can also be exited by user gestures (depending on your follow preferences). To stop following programmatically and restore touch-modified camera values, use `stopFollowingPosition(withRestoreCameraMode:)`. #### Follow mode behavior[​](#follow-mode-behavior "Direct link to Follow mode behavior") Use these APIs to inspect and manage follow state during runtime: | API | What it tells you | | -------------------------------------------------------------------- | ---------------------------------------------------------------------- | | `isFollowingPosition()` | Whether follow mode is currently active | | `isFollowingPositionTouchHandlerModified()` | Whether the user changed follow camera values via gestures | | `isDefaultFollowingPosition()` | Whether follow mode is in default auto-zoom behavior | | `restoreFollowingPosition(withAnimationDuration:completionHandler:)` | Returns from touch-modified follow behavior to default follow behavior | #### Configure follow preferences[​](#configure-follow-preferences "Direct link to Configure follow preferences") `FollowPositionPreferencesContext` lets you control behavior during follow mode. ```swift let preferences = mapViewController.getPreferences() let follow = preferences.getFollowPositionPreferences() follow.setMapRotationMode(.positionHeading, angle: 0, objectFollowMap: true) follow.setTouchHandlerExitAllow(true) follow.setTouchHandlerModifyPersistent(true) follow.setCameraFocus(CGPoint(x: 0.5, y: 0.5)) follow.setAccuracyCircleVisibility(true) ``` The follow preferences only affect camera behavior while follow mode is active. | Preference | Purpose | | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `setMapRotationMode(_:angle:objectFollowMap:)` | Chooses heading-based, compass-based, or fixed map rotation and controls whether the tracker rotates with the map | | `setCameraFocus(_:)` | Places the tracker at a relative viewport position (`(0,0)` top-left, `(1,1)` bottom-right) | | `setTouchHandlerExitAllow(_:)` | Controls whether gestures can exit follow mode | | `setTouchHandlerModifyPersistent(_:)` | Keeps or resets user gesture changes across follow sessions | | `setViewAngle(_:)` | Sets vertical camera angle used in follow mode | | `setZoomLevel(_:animationDuration:)` | Sets follow zoom (`-1` means automatic zoom) | | `setAccuracyCircleVisibility(_:)` | Shows or hides the position accuracy circle | | `setTimeBeforeTurnPresentation(_:)` | Sets lead time before turn presentation | Set `setTouchHandlerModifyPersistent(true)` when you want gesture-adjusted follow zoom/angle to remain active the next time follow mode starts. Set `setTouchHandlerExitAllow(false)` when you want follow mode to remain locked until you explicitly stop it in code. #### Customize the position tracker[​](#customize-the-position-tracker "Direct link to Customize the position tracker") Use the default tracker style and apply visual adjustments such as scale, visibility, and accuracy-circle color. ```swift mapViewController.setDefaultPositionTracker() mapViewController.setPositionTrackerScaleFactor(1.0) mapViewController.setPositionTrackerVisibility(true) mapViewController.setPositionTrackerAccuracyCircleColor(.systemBlue.withAlphaComponent(0.2)) ``` `1.0` is the default scale. For robust UI behavior, keep the scale in `(0, mapViewController.getPositionTrackerMaxScaleFactor()]`. Use a custom image texture for the tracker: ```swift if let image = UIImage(named: "navArrow"), let png = image.pngData() { mapViewController.customizePositionTracker(png) } ``` Multiple tracker instances are not supported on one map view. If you need to return to SDK defaults after customization, call `setDefaultPositionTracker()` again. #### Relevant examples demonstrating position tracker related features[​](#relevant-examples-demonstrating-position-tracker-related-features "Direct link to Relevant examples demonstrating position tracker related features") * [Position Tracker](/docs/ios/examples/maps-3dscene/position-tracker.md) * [Following Position](/docs/ios/examples/maps-3dscene/following-position.md) --- ### Public Transit Stops Last updated: April 8, 2026 | 7 minutes read

This API provides access to public transport data including agencies, routes, stops, and trips. Fetch and explore real-time public transportation information from selected positions on the map. Tip The public transport data structure follows the [General Transit Feed Specification (GTFS)](https://gtfs.org/documentation/schedule/reference/) and offers access to a subset of GTFS fields and entities. **Key features:** * Query public transport overlays by screen position * Retrieve information about transport agencies, stops, routes, and trips * Access real-time data including delays and cancellations * View metadata about accessibility, bike allowances, and platform details **How it works:** * Set a cursor position on the map using `setCursorPosition(_:)` on `MapViewController` * Retrieve overlay items at that position using `getCursorSelectionOverlayItems()` * Filter items by overlay UID to isolate public transit stops * Fetch extended stop data asynchronously using `getPreviewExtendedDataWithCompletionHandler(_:)` on each `OverlayItemObject` * Parse the returned `SearchableParameterListObject` using `findPublicTransportListParameterType(_:)` with the appropriate `PublicTransportParameterType` #### Query Public Transit Stops[​](#query-public-transit-stops "Direct link to Query Public Transit Stops") Implement the `mapViewController(_:onLongTouchPoint:)` delegate method on `MapViewControllerDelegate`. When the user long-presses the map, set the cursor to that position and retrieve public transit overlay items. ```swift func mapViewController(_ mapViewController: MapViewController, onLongTouch point: CGPoint) { mapViewController.setCursorPosition(point) let overlays = mapViewController.getCursorSelectionOverlayItems() let publicTransportId = Int(CommonOverlayIdentifier.publicTransport.rawValue) for item in overlays where item.getOverlayUid() == publicTransportId { guard item.hasPreviewExtendedData() else { continue } item.getPreviewExtendedData { parametersList in // agencies let agencies = parametersList.findPublicTransportListParameterType(.agencies) // stops (routes without heading set) let stops = parametersList.findPublicTransportListParameterType(.stops) // trips (route, agency, stop times, real-time info) let trips = parametersList.findPublicTransportListParameterType(.trips) // example of parsing stops for stop in stops { if let stopId = stop[PublicTransportStopParameterType.id.rawValue] as? NSValue, let stopName = stop[PublicTransportStopParameterType.name.rawValue] as? NSValue { print("Stop id: \(stopId) name: \(stopName)") } if let routes = stop[NSNumber(value: PublicTransportStopParameterType.routes.rawValue)] as? [[NSNumber: NSValue]] { for route in routes { if let value = route[NSNumber(value: PublicTransportRouteParameterType.shortName.rawValue)] { if let data = value.nonretainedObjectValue as? NSData, let name = String(data: data as Data, encoding: .utf8) { print(" Route short name: \(name)") } } } } } } } } ``` danger All returned times are local times encoded as Unix timestamps (seconds since 1970-01-01T00:00:00). Use `TimezoneContext` to convert them to other time zones. danger Two types of public transit stops exist on the map: * `OverlayItemObject` stops selected via `getCursorSelectionOverlayItems()` — provide extensive extended data and display with a blue icon (default style) * `LandmarkObject` stops selected via `getCursorSelectionLandmarks()` — provide limited details and display with a gray icon (default style) #### Agencies[​](#agencies "Direct link to Agencies") Agencies are returned as an array of `[NSNumber: NSValue]` dictionaries. Use `PublicTransportAgencyParameterType` enum values as keys. | Key | Type | Description | | ---------------------------------------- | ------------------ | ------------------------------- | | `PublicTransportAgencyParameterTypeId` | `NSValue` (Int) | Agency ID | | `PublicTransportAgencyParameterTypeName` | `NSValue` (String) | Full name of the transit agency | | `PublicTransportAgencyParameterTypeUrl` | `NSValue` (String) | URL of the transit agency | #### Public Transport Routes[​](#public-transport-routes "Direct link to Public Transport Routes") Route info is embedded within stop and trip dictionaries. Use `PublicTransportRouteParameterType` enum values as keys. | Key | Type | Description | | --------------------------------------------- | ------------------ | -------------------------------------------------------- | | `PublicTransportRouteParameterTypeId` | `NSValue` (Int) | Route ID | | `PublicTransportRouteParameterTypeShortName` | `NSValue` (String) | Short name (e.g., "32", "100X") | | `PublicTransportRouteParameterTypeLongName` | `NSValue` (String) | Full name, often including destination | | `PublicTransportRouteParameterTypeType` | `NSValue` (Int) | Transport type — see `PublicTransportRouteTransportType` | | `PublicTransportRouteParameterTypeRouteColor` | `NSValue` (String) | Route color as a hex string | | `PublicTransportRouteParameterTypeTextColor` | `NSValue` (String) | Text color for use against route color | | `PublicTransportRouteParameterTypeHeading` | `NSValue` (String) | Optional heading information | danger Route info embedded in stop and trip dictionaries provides information about public transit routes available at a specific stop. `PTRouteObject` represents a computed public transit route between multiple waypoints. See [Compute Public Transit Routes](/docs/ios/guides/routing/advanced-features.md#compute-public-transit-routes) for computing routes. ##### Route transport types[​](#route-transport-types "Direct link to Route transport types") The `PublicTransportRouteTransportType` enum represents the type of public transport route: | Enum value | Description | | ------------------------------------------------- | ---------------------------------- | | `PublicTransportRouteTransportTypeBus` | Bus or trolleybus | | `PublicTransportRouteTransportTypeUnderground` | Subway or metro | | `PublicTransportRouteTransportTypeRailway` | Intercity or long-distance rail | | `PublicTransportRouteTransportTypeTram` | Tram, streetcar, or light rail | | `PublicTransportRouteTransportTypeWaterTransport` | Ferry or other water-based transit | | `PublicTransportRouteTransportTypeMisc` | Other types of public transport | #### Stops[​](#stops "Direct link to Stops") Stops are returned as an array of `[NSNumber: NSValue]` dictionaries. Use `PublicTransportStopParameterType` enum values as keys. | Key | Type | Description | | ------------------------------------------- | ------------------------ | ------------------------------------------------------------------- | | `PublicTransportStopParameterTypeId` | `NSValue` (Int) | Stop ID | | `PublicTransportStopParameterTypeName` | `NSValue` (String) | Name of the stop as it appears on timetables and signage | | `PublicTransportStopParameterTypeIsStation` | `NSValue` (Bool) | Whether this location is a station containing one or more platforms | | `PublicTransportStopParameterTypeRoutes` | `NSArray` of route dicts | Routes serving this stop | #### Stop Times[​](#stop-times "Direct link to Stop Times") Stop times are embedded within trip dictionaries under the `PublicTransportTripParameterTypeStopTimes` key. Each stop time is a `[NSNumber: NSValue]` dictionary. Use `PublicTransportStopTimeParameterType` enum values as keys. | Key | Type | Description | | --------------------------------------------------- | ------------------ | ---------------------------------------------------------- | | `PublicTransportStopTimeParameterTypeStopName` | `NSValue` (String) | Name of the serviced stop | | `PublicTransportStopTimeParameterTypeDepartureTime` | `NSValue` (Int) | Departure time (seconds since 1970-01-01T00:00:00) | | `PublicTransportStopTimeParameterTypeHasRealtime` | `NSValue` (Bool) | Whether data is provided in real-time | | `PublicTransportStopTimeParameterTypeDelay` | `NSValue` (Int) | Delay in seconds. Not meaningful if `hasRealtime` is false | | `PublicTransportStopTimeParameterTypeIsBefore` | `NSValue` (Bool) | Whether the stop time is before the current time | | `PublicTransportStopTimeParameterTypeLatitude` | `NSValue` (Double) | Stop latitude | | `PublicTransportStopTimeParameterTypeLongitude` | `NSValue` (Double) | Stop longitude | #### Trips[​](#trips "Direct link to Trips") Trips are returned as an array of `[NSNumber: NSValue]` dictionaries. Use `PublicTransportTripParameterType` enum values as keys. | Key | Type | Description | | ------------------------------------------------------ | ---------------------------- | ------------------------------------------------------------------------ | | `PublicTransportRouteParameterTypeId` | `NSValue` (Int) | Route ID | | `PublicTransportRouteParameterTypeShortName` | `NSValue` (String) | Route short name | | `PublicTransportRouteParameterTypeLongName` | `NSValue` (String) | Route long name | | `PublicTransportRouteParameterTypeType` | `NSValue` (Int) | Route transport type | | `PublicTransportRouteParameterTypeHeading` | `NSValue` (String) | Optional heading | | `PublicTransportTripParameterTypeHasRealtime` | `NSValue` (Bool) | Whether real-time data is available | | `PublicTransportTripParameterTypeDepartureTime` | `NSValue` (Int) | Departure time from first stop (seconds since 1970-01-01T00:00:00) | | `PublicTransportTripParameterTypeTripDate` | `NSValue` (Int) | Date of the trip (seconds since 1970-01-01T00:00:00) | | `PublicTransportTripParameterTypeTripDelayMinutes` | `NSValue` (Int) | Delay in minutes. Not meaningful if `hasRealtime` is false | | `PublicTransportTripParameterTypeStopPlatformCode` | `NSValue` (String) | Platform code | | `PublicTransportTripParameterTypeIndex` | `NSValue` (Int) | Trip index | | `PublicTransportTripParameterTypeStopIndex` | `NSValue` (Int) | Stop index | | `PublicTransportTripParameterTypeIsCancelled` | `NSValue` (Bool) | Whether the trip is cancelled | | `PublicTransportTripParameterTypeWheelchairAccessible` | `NSValue` (Int) | Wheelchair accessibility — see `PublicTransportWheelchairAccessibleType` | | `PublicTransportTripParameterTypeBikesAllowed` | `NSValue` (Int) | Bikes allowed — see `PublicTransportBikesAllowedType` | | `PublicTransportTripParameterTypeAgencyId` | `NSValue` (Int) | ID of the associated agency | | `PublicTransportTripParameterTypeStopTimes` | `NSArray` of stop time dicts | Details of stop times in the trip | ##### Wheelchair accessibility values[​](#wheelchair-accessibility-values "Direct link to Wheelchair accessibility values") | Value | Description | | ----------------------------------------------- | ------------------------------------------------- | | `PublicTransportWheelchairAccessibleTypeNoInfo` | No accessibility information | | `PublicTransportWheelchairAccessibleTypeYes` | At least one wheelchair rider can be accommodated | | `PublicTransportWheelchairAccessibleTypeNo` | No wheelchair riders can be accommodated | ##### Bikes allowed values[​](#bikes-allowed-values "Direct link to Bikes allowed values") | Value | Description | | --------------------------------------- | ---------------------------------------- | | `PublicTransportBikesAllowedTypeNoInfo` | No bike information | | `PublicTransportBikesAllowedTypeYes` | At least one bicycle can be accommodated | | `PublicTransportBikesAllowedTypeNo` | No bicycles are allowed | danger Route info in stop and trip dictionaries represents the public-facing service riders recognize (e.g., "Bus 42"). A trip is a single scheduled journey along that route at a specific time with its own stop times. The route is the line identity; trips are individual vehicle runs throughout the day. #### Relevant examples demonstrating public transit related features[​](#relevant-examples-demonstrating-public-transit-related-features "Direct link to Relevant examples demonstrating public transit related features") * [Public Transit Calculate Route](/docs/ios/examples/routing-navigation/public-transit-calculate-route.md) * [Public Transit Route Instructions](/docs/ios/examples/routing-navigation/public-transit-route-instructions.md) --- ### Routing Routing functionality provides a robust solution for calculating, customizing, and analyzing routes for various transportation needs. You can calculate routes between a start and destination point, include intermediate waypoints for multi-stop routes, and determine areas accessible within a specific range. Additionally, the routing service allows you to retrieve turn-by-turn instructions, traffic event details, and estimate time of arrival (ETA) along with travel durations and distances. The system supports a variety of options such as computing terrain profiles for elevation data, displaying routes on a map, and handling public transit routes. #### [📄️Get started with routing](/docs/ios/guides/routing/get-started-routing.md) [This guide explains how to calculate routes, retrieve turn-by-turn instructions, access terrain profiles, traffic events, and detailed route information using NavigationContext and RoutePreferencesObject.](/docs/ios/guides/routing/get-started-routing.md) #### [📄️Handling Route Preferences](/docs/ios/guides/routing/route-preferences.md) [This guide explains how to customize routes using preferences, configure vehicle profiles, and apply routing constraints for different transport modes.](/docs/ios/guides/routing/route-preferences.md) #### [📄️Advanced features](/docs/ios/guides/routing/advanced-features.md) [This guide covers advanced routing features including route ranges, path-based routes, public transit routing, and route export.](/docs/ios/guides/routing/advanced-features.md) #### [📄️Route Bookmarks](/docs/ios/guides/routing/route-bookmarks.md) [This guide explains how to store, manage, and retrieve route collections as bookmarks between application sessions using RouteBookmarksObject.](/docs/ios/guides/routing/route-bookmarks.md) --- ### Advanced features Last updated: April 7, 2026 | 5 minutes read

This guide covers advanced routing features including route ranges, path-based routes, public transit routing, and route export. #### Compute route ranges[​](#compute-route-ranges "Direct link to Compute route ranges") A route range calculates the area reachable from a starting point within a specified distance, time, or energy budget. To compute a route range: * Set `setRouteRanges(_:quality:)` on `RoutePreferencesObject` with one or more range values. Measurement units correspond to the route type (see table below). * Provide only one landmark — the starting point. * Optionally set `setRouteType(_:)` (default is `RouteTypeFastest`) and `setTransportMode(_:)`. **Measurement units by route type:** | Route type | Units | | ------------------- | ----------- | | `RouteTypeFastest` | Seconds | | `RouteTypeShortest` | Meters | | `RouteTypeEconomic` | Wh (energy) | danger Routes computed using route ranges are **not navigable**. danger `RouteTypeScenic` is not supported for route ranges. Compute two ranges (30 min and 60 min) from a single starting point: ```swift let start = LandmarkObject.landmark( withName: "Paris", location: CoordinatesObject.coordinates(withLatitude: 48.85682, longitude: 2.34375) ) let preferences = RoutePreferencesObject() preferences.setRouteType(.fastest) // 30 minutes and 60 minutes in seconds preferences.setRouteRanges([1800, 3600], quality: 100) let nav = NavigationContext(preferences: preferences) nav.calculateRoute(withWaypoints: [start], statusHandler: { status in // handle status }, completionHandler: { routes, code in if code == .kNoError { print("Range routes computed: \(routes.count)") } else if code == .kCancel { print("Computation cancelled") } else { print("Error: \(code)") } } ) ``` info Display computed range routes on the map like regular routes. Use `RouteRenderSettings` to define polygon fill and border colors. #### Compute path-based routes[​](#compute-path-based-routes "Direct link to Compute path-based routes") A `PathObject` contains a list of coordinates (a track) created from: * Custom coordinate arrays. * GPX or other file formats. * Finger-drawn paths. Create a **path-backed landmark** using `RouteBookmarksObject.setWaypointTrackData(_:)`, then pass it as a waypoint in `calculateRoute(withWaypoints:)`. The path acts as a hint for the routing algorithm and produces a single result route. ```swift let coords: [CoordinatesObject] = [ CoordinatesObject.coordinates(withLatitude: 40.786, longitude: -74.202), CoordinatesObject.coordinates(withLatitude: 40.690, longitude: -74.209), CoordinatesObject.coordinates(withLatitude: 40.695, longitude: -73.814), CoordinatesObject.coordinates(withLatitude: 40.782, longitude: -73.710), ] let path = PathObject(coordinates: coords) // Create a path-backed landmark from the PathObject if let pathLandmark = RouteBookmarksObject.setWaypointTrackData(path) { let preferences = RoutePreferencesObject() let nav = NavigationContext(preferences: preferences) nav.calculateRoute(withWaypoints: [pathLandmark], statusHandler: { status in // handle status }, completionHandler: { routes, code in if code == .kNoError { print("Path route computed: \(routes.count)") } } ) } ``` Tip Add a path to an existing landmark using `RouteBookmarksObject.setWaypointTrackData(_:path:)`. Extract a path from a landmark using `RouteBookmarksObject.getWaypointTrackData(_:)`. danger When computing a route with both path-backed and non-path-backed landmarks, set `setAccurateTrackMatch(true)` on `RoutePreferencesObject`. Without it, routing fails. Configure routing engine behavior with `setTrackResume(_:)`: * **`true`** (default) — Matches the track from the closest coordinate to the last. * **`false`** — Uses the entire track from first to last coordinate. #### Compute routes from GPX files[​](#compute-routes-from-gpx-files "Direct link to Compute routes from GPX files") Compute a route from a GPX file by creating a `PathObject` from the file data: ```swift guard let gpxURL = Bundle.main.url(forResource: "recorded_route", withExtension: "gpx"), let gpxData = try? Data(contentsOf: gpxURL) else { print("GPX file not found") return } // Initialize PathObject from GPX data let path = PathObject(dataBuffer: gpxData, format: .gpx) if let pathLandmark = RouteBookmarksObject.setWaypointTrackData(path) { let preferences = RoutePreferencesObject() preferences.setTransportMode(.bicycle) let nav = NavigationContext(preferences: preferences) nav.calculateRoute(withWaypoints: [pathLandmark], statusHandler: { status in // handle status }, completionHandler: { routes, code in // handle result } ) } ``` #### Compute public transit routes[​](#compute-public-transit-routes "Direct link to Compute public transit routes") Set the transport mode to `.public` to compute public transit routes: ```swift let preferences = RoutePreferencesObject() preferences.setTransportMode(.public) ``` danger Public transit routes are not navigable. Compute and handle a public transit route: ```swift let departure = LandmarkObject.landmark( withName: "Stop A", location: CoordinatesObject.coordinates(withLatitude: 45.6646, longitude: 25.5872) ) let destination = LandmarkObject.landmark( withName: "Stop B", location: CoordinatesObject.coordinates(withLatitude: 45.6578, longitude: 25.6233) ) let preferences = RoutePreferencesObject() preferences.setTransportMode(.public) let nav = NavigationContext(preferences: preferences) nav.calculateRoute(withWaypoints: [departure, destination], statusHandler: { status in // handle status }, completionHandler: { routes, code in guard code == .kNoError, let route = routes.first else { return } for segment in route.getSegments() { if segment.isCommon() { // Walking segment — use standard turn instructions for instruction in segment.getInstructions() { let turn = instruction.getTurnInstruction() print("Walk: \(turn)") } } else { // Public transit segment — use PT instructions for ptInstruction in segment.getPTInstructions() { print("PT Station: \(ptInstruction.getName())") } } } } ) ``` A public transit route contains one or more segments. Check `isCommon()` to determine the segment type: * `false` — Walking segment. Use `getInstructions()` → `[RouteInstructionObject]`. * `true` — Public transit segment. Use `getPTInstructions()` → `[PTRouteInstructionObject]`. Tip Specify departure or arrival time and additional preferences for public transit: ```swift let preferences = RoutePreferencesObject() preferences.setTransportMode(.public) preferences.setAlgorithmType(.arrival) preferences.setSortingStrategy(.bestTime) preferences.setUseBikes(false) preferences.setUseWheelchair(false) // Set a timestamp one hour from now for desired arrival let oneHourFromNow = TimeObject(...) preferences.setTimestamp(oneHourFromNow) ``` #### Export routes to files[​](#export-routes-to-files "Direct link to Export routes to files") Export a specific route from a `RouteBookmarksObject` to a file on disk using `export(toFile:path:)`: ```swift let bookmarks = RouteBookmarksObject.init(fileName: "bookmarksFile", folderName: nil) let exported = bookmarks.export(toFile: 0, path: "/path/to/exported_route") if exported { print("Export successful") } else { print("Export failed — check the index and path") } ``` **Common failure reasons:** * Index out of bounds or route does not exist. * Destination directory does not exist or is not writable. #### Export routes as data[​](#export-routes-as-data "Direct link to Export routes as data") Export a route to a data buffer in a supported file format using `export(as:)`. The method returns `Data?` containing the full route in the requested format. ```swift // Export to GPX format if let gpxData = route.export(as: .gpx) { // Write to disk or share as needed try? gpxData.write(to: outputURL) } ``` **Supported `PathFileFormat` values:** `.gpx`, `.kml`, `.nmea`, `.geoJson`, `.packedGeometry`. #### Relevant examples demonstrating routing related features[​](#relevant-examples-demonstrating-routing-related-features "Direct link to Relevant examples demonstrating routing related features") * [Finger Draw Route](/docs/ios/examples/routing-navigation/finger-drawn-route-navigation.md) * [GPX Route](/docs/ios/examples/routing-navigation/gpx-route.md) * [GPX Thumbnail Image](/docs/ios/examples/routing-navigation/gpx-thumbnail-image.md) * [Public Transit Calculate Route](/docs/ios/examples/routing-navigation/public-transit-calculate-route.md) * [Public Transit Route Instructions](/docs/ios/examples/routing-navigation/public-transit-route-instructions.md) --- ### Get started with routing Last updated: April 17, 2026 | 5 minutes read

This guide explains how to calculate routes, retrieve turn-by-turn instructions, access terrain profiles, traffic events, and detailed route information using `NavigationContext` and `RoutePreferencesObject`. Here's a quick overview of what you can do with routing: * Calculate routes between a start point and destination. * Include intermediate waypoints for multi-stop routes. * Compute range routes to determine areas reachable within a specific range. * Plan routes over predefined tracks using `PathObject`. * Customize routes with `RoutePreferencesObject` — route type, transport mode, vehicle profiles, and avoidances. * Retrieve turn-by-turn instructions via `RouteInstructionObject`. * Access terrain profiles using `RouteTerrainProfileObject`. warning `NavigationContext` must be retained in memory for the duration of the route calculation. If the instance is deallocated before the request completes, the completion handler will not be called. Store it as a property on your view controller or data manager. #### Calculate routes[​](#calculate-routes "Direct link to Calculate routes") Create a `NavigationContext` with a `RoutePreferencesObject`, then call `calculateRoute(withWaypoints:statusHandler:completionHandler:)`. ```swift let preferences = RoutePreferencesObject() let departure = LandmarkObject.landmark( withName: "Paris", location: CoordinatesObject.coordinates(withLatitude: 48.85682, longitude: 2.34375) ) let destination = LandmarkObject.landmark( withName: "Brussels", location: CoordinatesObject.coordinates(withLatitude: 50.84644, longitude: 4.34587) ) let navigationContext = NavigationContext(preferences: preferences) navigationContext.calculateRoute(withWaypoints: [departure, destination], statusHandler: { status in print("Route status: \(status)") }, completionHandler: { routes, code in switch code { case .kNoError: print("Routes computed: \(routes.count)") case .kCancel: print("Route computation cancelled") default: print("Error: \(code)") } } ) ``` The `statusHandler` receives `RouteStatus` values as the calculation progresses: | `RouteStatus` | Meaning | | -------------------------------------- | ------------------------- | | `RouteStatusCalculating` | Computation in progress. | | `RouteStatusWaitingInternetConnection` | Waiting for connectivity. | | `RouteStatusReady` | Route is ready. | | `RouteStatusError` | Computation failed. | The `SDKErrorCode` passed to the completion handler can include: | Code | Significance | | --------------------------------- | ----------------------------------------------------------------- | | `SDKErrorCodeKNoError` | Successfully completed. | | `SDKErrorCodeKCancel` | Cancelled by the user. | | `SDKErrorCodeKWaypointAccess` | One or more waypoints not accessible with current preferences. | | `SDKErrorCodeKConnectionRequired` | Online routing required but `allowOnlineCalculation` is disabled. | | `SDKErrorCodeKRouteTooLong` | Route took too long on the server. | | `SDKErrorCodeKInvalidated` | Offline map data changed during calculation. | | `SDKErrorCodeKNoMemory` | Routing engine could not allocate required memory. | Cancel an ongoing calculation: ```swift navigationContext.cancelCalculateRoute() ``` When cancelled the completion handler receives `SDKErrorCodeKCancel`. #### Retrieve time and distance information[​](#retrieve-time-and-distance-information "Direct link to Retrieve time and distance information") Use `getTimeDistance()` and `getTimeDistance(withActivePart:)` on a `RouteObject` to access the route duration and length. ```swift if let td = route.getTimeDistance() { let totalDistanceM = td.getTotalDistance() let totalDurationS = td.getTotalTime() print("Distance: \(totalDistanceM) m, Duration: \(totalDurationS) s") } // Remaining portion only if let remaining = route.getTimeDistance(withActivePart: true) { print("Remaining distance: \(remaining.getTotalDistance()) m") } ``` Pass `false` for the full route; pass `true` (default) for the active remaining portion. Distance is in meters, time in seconds. ##### Access traffic events[​](#access-traffic-events "Direct link to Access traffic events") ```swift let trafficEvents = route.getTrafficEvents() for event in trafficEvents { let from = event.getFrom() ?? "unknown" let to = event.getTo() ?? "unknown" print("From: \(from) To: \(to)") } ``` See the [Traffic Events guide](/docs/ios/guides/core/traffic-events.md) for detailed information. #### Display routes on the map[​](#display-routes-on-the-map "Direct link to Display routes on the map") Routes are not automatically displayed after calculation. Refer to the [display routes](/docs/ios/guides/maps/display-map-items/display-routes.md) guide for visualization and customization options. #### Get the terrain profile[​](#get-the-terrain-profile "Direct link to Get the terrain profile") Enable terrain profile generation by setting `setBuildTerrainProfile(true)` on `RoutePreferencesObject` before calculating the route: ```swift let preferences = RoutePreferencesObject() preferences.setBuildTerrainProfile(true) ``` danger `setBuildTerrainProfile(true)` must be set on the preferences used for `calculateRoute(withWaypoints:)` before the call. It cannot be applied retroactively to an already-computed route. Access elevation and terrain data from the profile: ```swift if let profile = route.getTerrainProfile() { let minElevation = profile.getMinElevation() let maxElevation = profile.getMaxElevation() let minElevDist = profile.getMinElevationDistance() let maxElevDist = profile.getMaxElevationDistance() let totalUp = profile.getTotalUp() let totalDown = profile.getTotalDown() // Elevation at 100 m from the start let elevation = profile.getElevation(100) for section in profile.getRoadTypeSections() { let roadType: RoadType = section.type let startDist = section.startDistanceM } for section in profile.getSurfaceSections() { let surface: SurfaceType = section.type let startDist = section.startDistanceM } for section in profile.getClimbSections() { let grade: ClimbGrade = section.grade let slope = section.slope let start = section.startDistanceM let end = section.endDistanceM } let categs: [NSNumber] = [-16, -10, -7, -4, -1, 1, 4, 7, 10, 16] for section in profile.getSteepSections(categs) { let categ = section.categ let startDist = section.startDistanceM } } ``` **`RoadType`** values: `motorways`, `stateRoad`, `road`, `street`, `cycleway`, `path`, `singleTrack`. **`SurfaceType`** values: `asphalt`, `paved`, `unpaved`, `unknown`. #### Retrieve route instructions[​](#retrieve-route-instructions "Direct link to Retrieve route instructions") Access turn-by-turn `RouteInstructionObject` instances through each `RouteSegmentObject`. Each **segment** covers the route portion between two consecutive waypoints. A route with five waypoints has four segments. ```swift for segment in route.getSegments() { for instruction in segment.getInstructions() { let turn = instruction.getTurnInstruction() let followRoad = instruction.getFollowRoadInstruction() if let traveled = instruction.getTraveledTimeDistance() { let distanceM = traveled.getTotalDistance() } if let toNextTurn = instruction.getTimeDistanceToNextTurn() { print("To next turn: \(toNextTurn.getTotalDistance()) m") } if instruction.hasTurnInfo(), let details = instruction.getTurnDetails() { // details.getAbstractGeometry() for turn geometry image } if instruction.hasSignpostInfo() { let signpost = instruction.getSignpostInstruction() } let countryCode = instruction.getCountryCodeISO() } } ``` Key `RouteInstructionObject` methods: | Method | Description | | ------------------------------------------------ | ---------------------------------------------------------------- | | `getTurnInstruction()` | Textual description of the turn, e.g. "Bear left onto A 5". | | `getFollowRoadInstruction()` | Follow-road description, e.g. "Follow A 5 for 132m". | | `getTraveledTimeDistance()` | Distance and time traveled from route start to this instruction. | | `getRemainingTravelTimeDistance()` | Remaining distance and time to route end. | | `getRemainingTravelTimeDistanceToNextWaypoint()` | Distance and time to the next waypoint. | | `getTimeDistanceToNextTurn()` | Distance and time to the next turn instruction. | | `getTurnDetails()` | Full `TurnDetailsObject` for the maneuver. | | `getRoadInfo()` | `RoadInfoObject` array for the current road. | | `hasFollowRoadInfo()` | Whether follow-road information is available. | | `getCountryCodeISO()` | ISO 3166-1 alpha-3 country code at this instruction. | #### Relevant examples demonstrating routing related features[​](#relevant-examples-demonstrating-routing-related-features "Direct link to Relevant examples demonstrating routing related features") * [Calculate Route](/docs/ios/examples/routing-navigation/calculate-route.md) * [Route Instructions](/docs/ios/examples/routing-navigation/route-instructions.md) * [Route Profile](/docs/ios/examples/routing-navigation/route-profile.md) * [No Map Just Routing](/docs/ios/examples/routing-navigation/no-map-just-routing.md) * [Calculate Route Multi Map](/docs/ios/examples/routing-navigation/calculate-route-multi-map.md) * [Finger Draw Route](/docs/ios/examples/routing-navigation/finger-drawn-route-navigation.md) * [GPX Route](/docs/ios/examples/routing-navigation/gpx-route.md) --- ### Route Bookmarks Last updated: April 2, 2026 | 3 minutes read

This guide explains how to store, manage, and retrieve route collections as bookmarks between application sessions using `RouteBookmarksObject`. #### Create a bookmarks collection[​](#create-a-bookmarks-collection "Direct link to Create a bookmarks collection") Create a new bookmarks collection using `RouteBookmarksObject(fileName:folderName:)` with a unique name. If a collection with the same name already exists, it is opened: ```swift let bookmarks = RouteBookmarksObject(fileName: "my_trips", folderName: nil) ``` Access the file path of the stored collection: ```swift let path = bookmarks.getFilePath() ``` #### Add routes[​](#add-routes "Direct link to Add routes") Add a route to the collection using `add(_:list:preferences:overwrite:)`. Provide a unique name, a waypoints array, and optionally route preferences and an overwrite flag: ```swift bookmarks.add( "Home to Office", list: [homeLandmark, officeLandmark], preferences: myPreferences, overwrite: false ) ``` **Parameters:** * **`name`** — Unique route name. * **`list`** — Array of `LandmarkObject` waypoints defining the route. * **`preferences`** — Optional `RoutePreferencesObject`. Pass `nil` to store with no preferences. * **`overwrite`** — Replace an existing route with the same name (default: `false`). info If a route with the same name exists and `overwrite` is `false`, the operation fails silently. Use `getBaseUniqueName(_:)` from `RouteBookmarksObject` to generate a guaranteed unique name from waypoints. #### Import routes from files[​](#import-routes-from-files "Direct link to Import routes from files") Import multiple routes from a bookmarks file using `addTrips(_:skipDuplicates:)`. Returns the number of imported routes, or `0` on failure: ```swift let count = bookmarks.addTrips("/path/to/bookmarks_file", skipDuplicates: true) if count == 0 { print("Import failed — check the file path") } else { print("\(count) trips imported successfully") } ``` #### Export routes to files[​](#export-routes-to-files "Direct link to Export routes to files") Export a specific route to a file using `export(toFile:path:)` with the route index and destination path: ```swift let success = bookmarks.export(toFile: 0, path: "/path/to/exported_route") if success { print("Export successful") } else { print("Export failed — route index may not exist or path may be unwritable") } ``` **Common failure reasons:** * Index out of bounds. * Destination directory does not exist or is not writable. #### Access route details[​](#access-route-details "Direct link to Access route details") Get the number of routes in the collection using `getSize()`: ```swift let count = bookmarks.getSize() ``` Get details of a specific route by index: ```swift let name = bookmarks.getName(0) let waypoints = bookmarks.getWaypoints(0) // [LandmarkObject] let prefs = bookmarks.getPreferences(0) let timestamp = bookmarks.getTimestamp(0) // TimeObject? ``` info `getTimestamp(_:)` returns when the route was added or last modified. ##### Sort bookmarks[​](#sort-bookmarks "Direct link to Sort bookmarks") Change the sort order using `setSortOrder(_:)`: ```swift bookmarks.setSortOrder(.byDate) // RouteBookmarksSortByDate (default) — most recent first bookmarks.setSortOrder(.byName) // RouteBookmarksSortByName — alphabetical ``` **Available `RouteBookmarksSort` values:** | Value | Description | | -------------------------- | ----------------------------- | | `RouteBookmarksSortByDate` | Most recent first (default). | | `RouteBookmarksSortByName` | Alphabetical ascending order. | ##### Configure auto-delete mode[​](#configure-auto-delete-mode "Direct link to Configure auto-delete mode") When auto-delete mode is enabled, the underlying database file is deleted when the `RouteBookmarksObject` is deallocated: ```swift bookmarks.setAutoDeleteMode(true) ``` #### Update routes[​](#update-routes "Direct link to Update routes") Update an existing route using `update(_:name:list:preferences:)` with the route index and updated values: ```swift bookmarks.update( 0, name: "Updated Name", list: [newStart, newEnd], preferences: newPrefs ) ``` info The `update` method replaces all fields. To keep existing waypoints or preferences, first retrieve them with `getWaypoints(_:)` and `getPreferences(_:)`. #### Remove routes[​](#remove-routes "Direct link to Remove routes") Remove a route by index using `remove(_:)`: ```swift bookmarks.remove(0) ``` Clear all routes from the collection using `clear()`: ```swift bookmarks.clear() ``` --- ### Handling Route Preferences Last updated: April 7, 2026 | 8 minutes read

This guide explains how to customize routes using preferences, configure vehicle profiles, and apply routing constraints for different transport modes. #### Configure route preferences[​](#configure-route-preferences "Direct link to Configure route preferences") Set route options using **`RoutePreferencesObject`** to customize route calculations. Generic route options: | Property | Setter / Getter | Default | | -------------------------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------- | | accurateTrackMatch | `setAccurateTrackMatch(_:)` / `getAccurateTrackMatch()` | `true` | | allowOnlineCalculation | `setAllowOnlineCalculation(_:)` / `getAllowOnlineCalculation()` | `true` | | alternativeRoutesBalancedSorting | `setAlternativeRoutesBalancedSorting(_:)` / `getAlternativeRoutesBalancedSorting()` | `true` | | alternativesSchema | `setAlternativesSchema(_:)` / `getAlternativesSchema()` | `RouteAlternativesSchemaDefault` | | departureHeading | `setDepartureHeading(_:accuracy:)` / `getDepartureHeading()` + `getDepartureHeadingAccuracy()` | heading: -1, accuracy: 0 | | ignoreRestrictionsOverTrack | `setIgnoreRestrictionsOverTrack(_:)` / `getIgnoreRestrictionsOverTrack()` | `false` | | maximumDistanceConstraint | `setMaximumDistanceConstraint(_:)` / `getMaximumDistanceConstraint()` | `true` | | pathAlgorithm | `setPathAlgorithm(_:)` / `getPathAlgorithm()` | `RoutePathAlgorithmTypeMagicLane` | | pathAlgorithmFlavor | `setPathAlgorithmFlavor(_:)` / `getPathAlgorithmFlavor()` | `RoutePathAlgorithmFlavorTypeMagicLane` | | resultDetails | `setResultDetails(_:)` / `getResultDetails()` | `RouteResultDetailsFull` | | routeRanges | `setRouteRanges(_:quality:)` / `getRouteRanges()` | `[]` | | routeRangesQuality | `setRouteRanges(_:quality:)` / `getRouteRangesQuality()` | `100` | | routeType | `setRouteType(_:)` / `getRouteType()` | `RouteTypeFastest` | | timestamp | `setTimestamp(_:)` / `getTimestamp()` | current time | | transportMode | `setTransportMode(_:)` / `getTransportMode()` | `RouteTransportModeCar` | Complex structure creation options: | Property | Setter / Getter | Default | | ------------------- | -------------------------------------------------------------- | ------- | | buildConnections | `setBuildConnections(_:maxLengthM:)` / `getBuildConnections()` | `false` | | buildTerrainProfile | `setBuildTerrainProfile(_:)` / `getBuildTerrainProfile()` | `false` | Vehicle profile options: | Property | Setter / Getter | Default | | ------------------- | ------------------------------------------------------------------------- | --------------------------- | | bikeProfile | `setBikeProfile(_:)` / `getBikeProfile()` | `.road` (`BikeProfileRoad`) | | eBikeProfileDetails | `setBikeProfile(_:withEBikeProfileDetails:)` / `getEBikeProfileDetails()` | — | | pedestrianProfile | `setPedestrianProfile(_:)` / `getPedestrianProfile()` | `PedestrianProfileWalk` | | truckProfile | `setTruckProfile(_:)` / `getTruckProfile()` | all-zero struct | Route avoidance options: | Property | Setter / Getter | Default | | -------------------------- | ----------------------------------------------------------------------- | -------------------------- | | avoidMotorways | `setAvoidMotorways(_:)` / `getAvoidMotorways()` | `false` | | avoidTollRoads | `setAvoidTollRoads(_:)` / `getAvoidTollRoads()` | `false` | | avoidFerries | `setAvoidFerries(_:)` / `getAvoidFerries()` | `false` | | avoidCarpoolLanes | `setAvoidCarpoolLanes(_:)` / `getAvoidCarpoolLanes()` | `false` | | avoidUnpavedRoads | `setAvoidUnpavedRoads(_:)` / `getAvoidUnpavedRoads()` | `false` | | avoidTurnAroundInstruction | `setAvoidTurnAroundInstruction(_:)` / `getAvoidTurnAroundInstruction()` | `false` | | avoidTraffic | `setAvoidTrafficType(_:)` / `getAvoidTraffic()` | `TrafficAvoidanceTypeNone` | Emergency vehicle options: | Property | Setter / Getter | Default | | ---------------------------------- | -------------------------------------------------------------------------------------- | ------- | | emergencyVehicleMode | `setEmergencyVehicleMode(_:)` / `getEmergencyVehicleMode()` | `false` | | emergencyVehicleExtraFreedomLevels | `setEmergencyVehicleMode(_:extraFreedom:)` / `getEmergencyVehicleExtraFreedomLevels()` | `0` | Public transport options: | Property | Setter / Getter | Default | | ---------------------------- | --------------------------------------------------------------------------- | -------------------------------- | | algorithmType | `setAlgorithmType(_:)` / `getAlgorithmType()` | `RouteAlgorithmTypeDeparture` | | minimumTransferTimeInMinutes | `setMinimumTransferTimeInMinutes(_:)` / `getMinimumTransferTimeInMinutes()` | `1` | | maximumTransferTimeInMinutes | `setMaximumTransferTimeInMinutes(_:)` / `getMaximumTransferTimeInMinutes()` | `300` | | maximumWalkDistance | `setMaximumWalkDistance(_:)` / `getMaximumWalkDistance()` | `5000` | | sortingStrategy | `setSortingStrategy(_:)` / `getSortingStrategy()` | `PTRouteSortingStrategyBestTime` | | routeTypePreferences | `setRouteTypePreferences(_:)` / `getRouteTypePreferences()` | `0` (none) | | useBikes | `setUseBikes(_:)` / `getUseBikes()` | `false` | | useWheelchair | `setUseWheelchair(_:)` / `getUseWheelchair()` | `false` | Example — calculate the fastest car route with terrain profile: ```swift let preferences = RoutePreferencesObject() preferences.setTransportMode(.car) preferences.setRouteType(.fastest) preferences.setBuildTerrainProfile(true) ``` #### Configure vehicle profiles[​](#configure-vehicle-profiles "Direct link to Configure vehicle profiles") Customize routing behavior for different vehicle types using profile configurations. ##### Truck profile[​](#truck-profile "Direct link to Truck profile") Define truck-specific routing preferences using the **`TruckProfileDetails`** C struct. Available fields: | Field | Type | Default | Description | | ---------- | -------- | ------- | ---------------------------- | | `mass` | `Int32` | 0 | Truck weight in kg. | | `height` | `Int32` | 0 | Truck height in cm. | | `length` | `Int32` | 0 | Truck length in cm. | | `width` | `Int32` | 0 | Truck width in cm. | | `axleLoad` | `Int32` | 0 | Maximum load per axle in kg. | | `maxSpeed` | `Double` | 0 | Maximum speed in m/s. | All fields default to 0, meaning they are not considered in routing. Set at least one field to a non-zero value to activate truck-specific restrictions. ##### Electric bike profile[​](#electric-bike-profile "Direct link to Electric bike profile") Define electric bike routing preferences using the **`EBikeProfileDetails`** C struct together with a `BikeProfile` enum value. Available `EBikeProfileDetails` fields: | Field | Type | Default | Description | | ------------------------- | -------------- | ------------------ | ---------------------------------------------------- | | `type` | `EBikeProfile` | `EBikeProfileNone` | E-bike type. | | `bikeMass` | `Float` | 0 | Bike mass in kg. | | `bikerMass` | `Float` | 0 | Biker mass in kg. | | `auxConsumptionDay` | `Float` | 0 | Auxiliary power consumption during day in Watts. | | `auxConsumptionNight` | `Float` | 0 | Auxiliary power consumption during night in Watts. | | `batteryCapacity` | `Float` | 1000 | Battery usable capacity in Wh. | | `departureSoc` | `Float` | — | Battery state of charge at departure (`0.0`–`1.0`). | | `refSpeed` | `Float` | 0 | Reference speed in m/s. | | `ignoreLegalRestrictions` | `Bool` | `false` | Ignore country-based legal restrictions for e-bikes. | **`EBikeProfile`** values: `EBikeProfileNone`, `EBikeProfilePedelec`, `EBikeProfilePowerOnDemand`. **`BikeProfile`** values: `BikeProfileRoad`, `BikeProfileCross`, `BikeProfileCity`, `BikeProfileMountain`. **`PedestrianProfile`** values: `PedestrianProfileWalk`, `PedestrianProfileHike`. #### Calculate truck routes[​](#calculate-truck-routes "Direct link to Calculate truck routes") Compute routes optimized for trucks by setting truck-specific preferences and the lorry transport mode. ```swift let departure = LandmarkObject.landmark( withName: "Paris", location: CoordinatesObject.coordinates(withLatitude: 48.87126, longitude: 2.33787) ) let destination = LandmarkObject.landmark( withName: "London", location: CoordinatesObject.coordinates(withLatitude: 51.4739, longitude: -0.0302) ) var truckProfile = TruckProfileDetails() truckProfile.height = 180 // cm truckProfile.length = 500 // cm truckProfile.width = 200 // cm truckProfile.axleLoad = 1500 // kg truckProfile.maxSpeed = 60 // m/s truckProfile.mass = 3000 // kg let preferences = RoutePreferencesObject() preferences.setTruckProfile(truckProfile) preferences.setTransportMode(.lorry) // crucial let nav = NavigationContext(preferences: preferences) nav.calculateRoute(withWaypoints: [departure, destination], statusHandler: { status in // handle status, }, completionHandler: { routes, code in // handle routes } ) ``` #### Calculate caravan routes[​](#calculate-caravan-routes "Direct link to Calculate caravan routes") Compute routes for caravans and trailers with size and weight restrictions. Caravans or trailers may be restricted on some roads due to size or weight, yet still permitted on roads where trucks are not. The key difference from a truck route is the transport mode: use `.car` instead of `.lorry`. ```swift let departure = LandmarkObject.landmark( withName: "Paris", location: CoordinatesObject.coordinates(withLatitude: 48.87126, longitude: 2.33787) ) let destination = LandmarkObject.landmark( withName: "London", location: CoordinatesObject.coordinates(withLatitude: 51.4739, longitude: -0.0302) ) var caravanProfile = TruckProfileDetails() caravanProfile.height = 180 // cm caravanProfile.length = 500 // cm caravanProfile.width = 200 // cm caravanProfile.axleLoad = 1500 // kg let preferences = RoutePreferencesObject() preferences.setTruckProfile(caravanProfile) preferences.setTransportMode(.car) // crucial — distinguishes caravan from truck let nav = NavigationContext(preferences: preferences) nav.calculateRoute(withWaypoints: [departure, destination], statusHandler: { status in // handle status, }, completionHandler: { routes, code in // handle routes } ) ``` warning Set at least one of `height`, `length`, `width`, or `axleLoad` to a non-zero value for restrictions to take effect. If all fields are 0, a normal car route is calculated. #### Relevant examples demonstrating routing related features[​](#relevant-examples-demonstrating-routing-related-features "Direct link to Relevant examples demonstrating routing related features") * [Calculate Route](/docs/ios/examples/routing-navigation/calculate-route.md) * [Finger Draw Route](/docs/ios/examples/routing-navigation/finger-drawn-route-navigation.md) --- ### Search The SDK supports multiple search workflows through `SearchContext`, including free-text search, proximity search, category filtering, overlay search, route-based search, and guided address search. Search results are returned as `LandmarkObject` instances, which you can use directly for highlighting, routing, overlay handling, and address extraction. #### [📄️Getting started with Search](/docs/ios/guides/search/get-started-search.md) [The Maps SDK for iOS provides flexible search functionality for finding locations using text queries, coordinates, categories, landmark stores, and overlays:](/docs/ios/guides/search/get-started-search.md) #### [📄️Search & Geocoding features](/docs/ios/guides/search/search-geocoding-features.md) [This guide explains how to convert coordinates to addresses and addresses to coordinates, work with hierarchical address search, build auto-suggestions, and search along routes with SearchContext.](/docs/ios/guides/search/search-geocoding-features.md) --- ### Getting started with Search Last updated: April 1, 2026 | 7 minutes read

The Maps SDK for iOS provides flexible search functionality for finding locations using text queries, coordinates, categories, landmark stores, and overlays: * **Text search** - Perform searches using a text query and geographic coordinates to prioritize results within a specific area * **Search preferences** - Customize search behavior using fuzzy results, address inclusion, offline-only mode, and distance limits * **Category-based search** - Filter search results by predefined categories such as fuel stations, parking, or entertainment * **Proximity search** - Retrieve nearby landmarks without specifying a text query warning These are simplified code snippets. To make sure the `SearchContext` object works correctly, it must be retained in memory for the duration of the search request. If the `SearchContext` instance is deallocated before the request completes, the completion handler will not be called. Check the examples linked at the end of this page for reference implementations. #### Text search[​](#text-search "Direct link to Text search") Search by providing text and coordinates. The coordinates act as a location hint so the SDK can prioritize results near the specified area. ```swift let searchContext = SearchContext() searchContext.setMaxMatches(40) searchContext.setAllowFuzzyResults(true) let location = CoordinatesObject.coordinates(withLatitude: 45.0, longitude: 10.0) searchContext.search(withQuery: "Paris", location: location) { results in if results.isEmpty { print("No results") } else { print("Results count: \(results.count)") } } ``` info `SearchContext` completion handlers return `[LandmarkObject]` only. If you need to stop an in-flight request before launching another search, call `cancelSearch()`. You can also restrict the search area to a rectangle or radius using `setLocationHint(_:)`: ```swift let hint = RectangleGeographicAreaObject( location: location, horizontalRadius: 2_000, verticalRadius: 2_000 ) searchContext.setLocationHint(hint) ``` #### Specify preferences[​](#specify-preferences "Direct link to Specify preferences") Before searching, configure `SearchContext` to control the search behavior. | Method | Description | | ------------------------------------ | ------------------------------------------------------------------------------------- | | `setMaxMatches(_:)` | Sets the maximum number of results returned by the request. | | `setAllowFuzzyResults(_:)` | Enables approximate matching for partially matching queries. | | `setExactMatch(_:)` | Restricts results to exact text matches only. | | `setSearchAddresses(_:)` | Includes address results, including roads. | | `setSearchMapPOIs(_:)` | Includes map POIs in the result set. | | `setSearchOnlyOnboard(_:)` | Restricts the request to onboard data only. | | `setThresholdDistance(_:)` | Sets the maximum lookup distance in meters for nearby searches and reverse geocoding. | | `setEstimateMissingHouseNumbers(_:)` | Enables house-number interpolation for address search results. | | `setEasyAccessOnlyResults(_:)` | Restricts results to locations considered easily accessible. | | `setSearchGeofences(_:)` | Includes geofence search targets in the request. | | `setLocationHint(_:)` | Restricts the search to a specific `RectangleGeographicAreaObject`. | ##### Search by category[​](#search-by-category "Direct link to Search by category") Filter search results using predefined categories from `GenericCategoriesContext`. The following example limits results to the **Food & Drink** category. ```swift let searchContext = SearchContext() searchContext.setSearchAddresses(false) searchContext.setSearchMapPOIs(false) let genericCategories = GenericCategoriesContext() if let foodAndDrink = genericCategories.getCategory(.foodAndDrink) { _ = searchContext.setCategory(foodAndDrink) } let location = CoordinatesObject.coordinates(withLatitude: 45.0, longitude: 10.0) searchContext.searchAround(withLocation: location) { results in print("Category results: \(results.count)") } ``` Tip Set `setSearchAddresses(false)` and `setSearchMapPOIs(false)` when you want only category-filtered results. You can retrieve the full predefined category list with `GenericCategoriesContext().getCategories()`. ##### Search on custom landmarks[​](#search-on-custom-landmarks "Direct link to Search on custom landmarks") By default, `SearchContext` searches the built-in map stores. To search custom landmarks, create or open a `LandmarkStoreContext`, add landmarks to it, and enable that store in the search store collection. ```swift let customStore = LandmarkStoreContext(name: "searchable-landmarks") let landmark1 = LandmarkObject.landmark( withName: "My Custom Landmark 1", location: CoordinatesObject.coordinates(withLatitude: 25.0, longitude: 30.0) ) let landmark2 = LandmarkObject.landmark( withName: "My Custom Landmark 2", location: CoordinatesObject.coordinates(withLatitude: 25.005, longitude: 30.005) ) _ = customStore.addLandmark(landmark1) _ = customStore.addLandmark(landmark2) let searchContext = SearchContext() searchContext.setSearchAddresses(false) searchContext.setSearchMapPOIs(false) if let stores = searchContext.getLandmarkStoreCollection() { _ = stores.addAllStoreCategories(customStore.getId()) } let location = CoordinatesObject.coordinates(withLatitude: 25.003, longitude: 30.003) searchContext.search(withQuery: "My Custom Landmark", location: location) { results in print("Custom landmark results: \(results.count)") } ``` danger `LandmarkStoreContext(name:)` persists its data in the app sandbox. Reusing the same store name can reopen an existing store that already contains landmarks and categories. See the [Landmarks guide](/docs/ios/guides/core/landmarks.md#manage-landmark-stores) for details. Tip Set `setSearchAddresses(false)` and `setSearchMapPOIs(false)` when you want only custom-landmark results. ##### Search on overlays[​](#search-on-overlays "Direct link to Search on overlays") Perform searches on overlays by adding overlay identifiers to the search overlay collection. The example below searches within the built-in safety overlay. ```swift let searchContext = SearchContext() searchContext.setSearchAddresses(false) searchContext.setSearchMapPOIs(false) if let overlays = searchContext.geOverlayMutableCollection() { overlays.add(Int32(CommonOverlayIdentifier.publicTransport.rawValue)) } let location = CoordinatesObject.coordinates(withLatitude: 48.76930, longitude: 2.34483) searchContext.search(withQuery: "Louis", location: location) { results in if let landmark = results.first, let overlayItem = landmark.getOverlayItem() { print("Overlay item: \(overlayItem.getName())") } } ``` To convert a returned `LandmarkObject` to an overlay-backed result, use `getOverlayItem()`. Tip Set `setSearchAddresses(false)` and `setSearchMapPOIs(false)` when you want only overlay results. danger Overlay search requires a map style that contains the target overlay. If the overlay is not available in the current style, the request may return no results. #### Search for location[​](#search-for-location "Direct link to Search for location") Without specifying text, nearby landmarks are returned around the reference location, limited by `setMaxMatches(_:)`. ```swift let searchContext = SearchContext() searchContext.setMaxMatches(40) searchContext.setAllowFuzzyResults(true) let location = CoordinatesObject.coordinates(withLatitude: 45.0, longitude: 10.0) searchContext.searchAround(withLocation: location) { results in if results.isEmpty { print("No results") } else { print("Nearby results: \(results.count)") } } ``` #### Search in a certain area[​](#search-in-a-certain-area "Direct link to Search in a certain area") Use `searchAround(withLocation:)` together with `setLocationHint(_:)` to find landmarks within a specific rectangular area, ordered by distance from the reference coordinates. Provide a `RectangleGeographicAreaObject` defined by its top-left and bottom-right corners, and set the reference coordinates to a point inside that area. ```swift let topLeft = CoordinatesObject.coordinates(withLatitude: 67.69866, longitude: 24.81115) let bottomRight = CoordinatesObject.coordinates(withLatitude: 67.58326, longitude: 25.36093) let referencePoint = CoordinatesObject.coordinates(withLatitude: 67.63826, longitude: 24.94154) let area = RectangleGeographicAreaObject(topLeftLocation: topLeft, bottomRightLocation: bottomRight) let searchContext = SearchContext() searchContext.setLocationHint(area) searchContext.searchAround(withLocation: referencePoint) { results in if results.isEmpty { print("No results") } else { print("Area results: \(results.count)") } } ``` To additionally filter results by name, use `search(withQuery:location:)` instead of `searchAround(withLocation:)` and pass the text query alongside the reference coordinates: ```swift searchContext.setLocationHint(area) searchContext.search(withQuery: "cafe", location: referencePoint) { results in print("Filtered area results: \(results.count)") } ``` danger The reference coordinates must be located **within** the `RectangleGeographicAreaObject` provided to `setLocationHint(_:)`. If the reference point is outside the area, the search will return empty results. #### Show results on the map[​](#show-results-on-the-map "Direct link to Show results on the map") In most use cases, landmarks returned by a search are already rendered on the map as part of the loaded map data. If the search was performed on custom landmark stores, refer to the [display landmarks](/docs/ios/guides/maps/display-map-items/display-landmarks.md#add-custom-landmarks) guide for adding them to the map view. To center the map on a landmark returned from search, retrieve its coordinates with `getCoordinates()` and pass them to `center(onCoordinates:zoomLevel:animationDuration:)` on your `MapViewController`. See the [adjust map view](/docs/ios/guides/maps/adjust-map.md) guide for full centering options. ```swift let searchContext = SearchContext() let location = CoordinatesObject.coordinates(withLatitude: 48.8566, longitude: 2.3522) searchContext.search(withQuery: "Eiffel Tower", location: location) { results in guard let landmark = results.first else { return } let coords = landmark.getCoordinates() // mapViewController is your MapViewController instance mapViewController.center(onCoordinates: coords, zoomLevel: 15, animationDuration: 0.5) } ``` #### Change the language of results[​](#change-the-language-of-results "Direct link to Change the language of results") The language of search results and category names is controlled by the SDK-wide language setting, set via `GEMSdk.shared().setLanguage(_:)`. By default the SDK uses the device's preferred language and region. The language value uses ISO 639-3 (e.g. `"eng"` for generic English, `"fra"` for French) or ISO 639-1 (e.g. `"en"`, `"fr"`) format, optionally combined with an ISO 3166 region code (e.g. `"eng-USA"`, `"en-US"`). ```swift GEMSdk.shared().setLanguage("eng-USA") ``` All subsequent search requests will return results in the specified language where the data is available. #### Relevant examples demonstrating search related features[​](#relevant-examples-demonstrating-search-related-features "Direct link to Relevant examples demonstrating search related features") * [Search Text](/docs/ios/examples/places-search/search-text.md) * [Search Category](/docs/ios/examples/places-search/search_category.md) * [Search On Map](/docs/ios/examples/places-search/search-on-map.md) * [What's Nearby](/docs/ios/examples/places-search/whats-nearby.md) --- ### Search & Geocoding features Last updated: April 1, 2026 | 4 minutes read

This guide explains how to convert coordinates to addresses and addresses to coordinates, work with hierarchical address search, build auto-suggestions, and search along routes with `SearchContext`. warning These are simplified code snippets. To make sure the `SearchContext` object works correctly, it must be retained in memory for the duration of the search request. If the `SearchContext` instance is deallocated before the request completes, the completion handler will not be called. Check the examples linked at the end of this page for reference implementations. #### Convert coordinates to addresses[​](#convert-coordinates-to-addresses "Direct link to Convert coordinates to addresses") Search around a coordinate to retrieve a matching `LandmarkObject`, then extract structured address fields with `getAddressFieldName(with:)`. ```swift let searchContext = SearchContext() searchContext.setThresholdDistance(50) let coordinates = CoordinatesObject.coordinates(withLatitude: 51.519305, longitude: -0.128022) searchContext.searchAround(withLocation: coordinates) { results in guard let landmark = results.first else { print("No results found") return } let country = landmark.getAddressFieldName(with: .country) let city = landmark.getAddressFieldName(with: .city) let street = landmark.getAddressFieldName(with: .streetName) let streetNumber = landmark.getAddressFieldName(with: .streetNumber) let fullAddress = "\(streetNumber) \(street), \(city), \(country)" print(fullAddress) } ``` Common address fields include: * `.country` * `.city` * `.streetName` * `.streetNumber` * `.postalCode` * `.state` * `.district` * `.countryCode` #### Convert addresses to coordinates[​](#convert-addresses-to-coordinates "Direct link to Convert addresses to coordinates") Address search uses a hierarchical structure where each node is represented by a `LandmarkObject` and an `AddressLevelType`. danger Address hierarchies vary by country. Some countries do not provide state or province levels. Use `hasAddressSearchState(withCountry:)` or move through the available levels dynamically. ##### Search for countries[​](#search-for-countries "Direct link to Search for countries") Use country-level search when you need the root landmark for a guided address-search flow. ```swift let searchContext = SearchContext() searchContext.addressSearchCountries(withQuery: "Germany") { results in if results.isEmpty { print("No results") } else { print("Countries found: \(results.count)") } } ``` You can also get a country directly from its ISO code: ```swift let searchContext = SearchContext() let country = searchContext.addressSearchGetCountry(withIsoCode: "ESP") print(country.getLandmarkName()) ``` ##### Navigate the address hierarchy[​](#navigate-the-address-hierarchy "Direct link to Navigate the address hierarchy") Search through the address structure from country to city, street, and house number using `addressSearch(withLandmark:level:query:completionHandler:)`. `AddressLevelType` values include `noDetail`, `country`, `state`, `county`, `district`, `city`, `settlement`, `postalCode`, `street`, `streetSection`, `streetLane`, `streetAlley`, `houseNumber`, and `crossing`. ```swift func searchAddress( searchContext: SearchContext, landmark: LandmarkObject, level: AddressLevelType, text: String, completion: @escaping (LandmarkObject?) -> Void ) { searchContext.addressSearch(withLandmark: landmark, level: level, query: text) { results in completion(results.first) } } ``` Use this helper to walk down the hierarchy step by step: ```swift let searchContext = SearchContext() let country = searchContext.addressSearchGetCountry(withIsoCode: "ESP") searchAddress(searchContext: searchContext, landmark: country, level: .city, text: "Barcelona") { city in guard let city else { return } searchAddress(searchContext: searchContext, landmark: city, level: .street, text: "Carrer de Mallorca") { street in guard let street else { return } searchAddress(searchContext: searchContext, landmark: street, level: .houseNumber, text: "401") { houseNumber in guard let houseNumber else { return } print(houseNumber.getLandmarkName()) } } } ``` #### Implement auto-suggestions[​](#implement-auto-suggestions "Direct link to Implement auto-suggestions") Provide real-time suggestions by reissuing a search whenever the search text changes. Cancel the previous request before starting a new one. ```swift final class SearchViewController: UIViewController, UISearchBarDelegate { let searchContext = SearchContext() var suggestions: [LandmarkObject] = [] func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { updateSuggestions(for: searchText) } private func updateSuggestions(for text: String) { searchContext.cancelSearch() guard !text.isEmpty else { suggestions = [] return } searchContext.setAllowFuzzyResults(true) let reference = CoordinatesObject.coordinates(withLatitude: 48.8566, longitude: 2.3522) searchContext.search(withQuery: text, location: reference) { [weak self] results in self?.suggestions = results // Reload your table view or collection view here. } } } ``` Set `setAllowFuzzyResults(true)` to improve partial match behavior. Replace the reference coordinates with the user's position or the current map center for better ranking. #### Search along routes[​](#search-along-routes "Direct link to Search along routes") Find landmarks and POIs around a route using `searchAlong(withRoute:query:completionHandler:)`. Use `setThresholdDistance(_:)` to control how far from the route results may be returned. ```swift let searchContext = SearchContext() searchContext.setThresholdDistance(500) searchContext.searchAlong(withRoute: route, query: "charging") { results in if results.isEmpty { print("No results") } else { print("Route search results: \(results.count)") for landmark in results { print(landmark.getLandmarkName()) } } } ``` `route` must be a valid `RouteObject`, for example returned by the routing APIs. #### Relevant examples demonstrating search related features[​](#relevant-examples-demonstrating-search-related-features "Direct link to Relevant examples demonstrating search related features") * [Search Along Route](/docs/ios/examples/places-search/search-along-route.md) * [Search Text](/docs/ios/examples/places-search/search-text.md) * [What's Nearby](/docs/ios/examples/places-search/whats-nearby.md) --- ### Timezone service Last updated: April 6, 2026 | 2 minutes read

The `TimezoneContext` class provides functionality for retrieving time zone information based on coordinates and a UTC timestamp, using built-in offline data. info The iOS SDK provides an offline timezone lookup that uses built-in SDK data. Update the SDK regularly to keep the timezone data current. #### Understand the TimezoneResultObject structure[​](#understand-the-timezoneresultobject-structure "Direct link to Understand the TimezoneResultObject structure") `TimezoneResultObject` represents the result of a timezone lookup and exposes the following methods: | Method | Return type | Description | | ----------------- | ---------------- | ------------------------------------------------------------------------------------ | | `getDstOffset()` | `Int` | Daylight Saving Time (DST) offset in seconds | | `getUtcOffset()` | `Int` | Raw UTC offset in seconds, excluding DST. Can be negative | | `getOffset()` | `Int` | Total offset relative to UTC in seconds (`dstOffset` + `utcOffset`). Can be negative | | `getStatus()` | `TimezoneStatus` | Status of the response — see values below | | `getTimezoneId()` | `String` | Timezone identifier (e.g., `"Europe/Paris"`, `"America/New_York"`) | | `getLocalTime()` | `TimeObject?` | Local time for the requested timezone | ##### TimezoneStatus values[​](#timezonestatus-values "Direct link to TimezoneStatus values") The `TimezoneStatus` enum indicates the result of a timezone lookup: | Value | Description | | --------------------------------- | ------------------------------------------------------------ | | `TimezoneStatusSuccess` | Request completed successfully | | `TimezoneStatusInvalidCoordinate` | Provided coordinates were invalid or out of range | | `TimezoneStatusWrongTimezoneId` | Provided timezone identifier was malformed or not recognized | | `TimezoneStatusWrongTimestamp` | Provided timestamp was invalid or could not be parsed | | `TimezoneStatusNotFound` | No timezone found for the given input | #### Retrieve timezone by coordinates[​](#retrieve-timezone-by-coordinates "Direct link to Retrieve timezone by coordinates") Use `getOfflineTimezoneInfo(_:time:)` on `TimezoneContext.sharedInstance()` to retrieve timezone information based on geographic coordinates and a UTC `TimeObject`: ```swift let location = CoordinatesObject.coordinates(withLatitude: 55.626, longitude: 37.457) let utcTime = TimeObject() utcTime.setYear(2025) utcTime.setMonth(7) utcTime.setDay(1) let result = TimezoneContext.sharedInstance().getOfflineTimezoneInfo(location, time: utcTime) if result.getStatus() == .success { print("Timezone ID: \(result.getTimezoneId())") print("UTC offset: \(result.getUtcOffset()) seconds") print("DST offset: \(result.getDstOffset()) seconds") print("Total offset: \(result.getOffset()) seconds") if let localTime = result.getLocalTime() { print("Local time year: \(localTime.getYear()) month: \(localTime.getMonth()) day: \(localTime.getDay())") } } else { print("Timezone lookup failed: \(result.getStatus())") } ``` warning The offline method uses built-in timezone data that may become outdated. Update the SDK regularly to refresh timezone data. --- ### Weather The Maps SDK for iOS provides weather capabilities through its `WeatherContext` class. With it, you can retrieve current conditions, hourly forecasts, and daily outlooks — including temperature, air quality, atmospheric pressure, wind speed, UV index, and more. #### [📄️Base Entities](/docs/ios/guides/weather/base-entities.md) [Weather-related data is organized into distinct classes, each designed to encapsulate specific weather information.](/docs/ios/guides/weather/base-entities.md) #### [📄️Weather Service](/docs/ios/guides/weather/weather-service.md) [The WeatherContext class provides methods for retrieving current, hourly, and daily weather forecasts. Access the shared instance via WeatherContext.shared().](/docs/ios/guides/weather/weather-service.md) --- ### Base Entities Last updated: April 6, 2026 | 3 minutes read

Weather-related data is organized into distinct classes, each designed to encapsulate specific weather information. The main classes include `WeatherContextForecast`, `WeatherContextConditions`, and `WeatherContextParameter`. This guide provides a detailed explanation of each class and its purpose. #### WeatherContextForecast[​](#weathercontextforecast "Direct link to WeatherContextForecast") The `WeatherContextForecast` class retains data such as the forecast update timestamp, the geographic location, and a single set of forecast conditions for a specific time slot. | Property | Type | Description | | ------------------- | -------------------------- | -------------------------------------- | | `updateTimestamp` | `TimeObject?` | Forecast update timestamp | | `coordinatesObject` | `CoordinatesObject?` | Geographic location | | `conditions` | `WeatherContextConditions` | Forecast conditions for this time slot | #### WeatherContextConditions[​](#weathercontextconditions "Direct link to WeatherContextConditions") The `WeatherContextConditions` class retains weather conditions for a given timestamp. | Property | Type | Description | | ------------- | --------------------------- | --------------------------------------------------------------------------------------------- | | `type` | `String` | For possible values see [Predefined parameter type values](#predefined-parameter-type-values) | | `stamp` | `TimeObject?` | Timestamp for condition | | `imageObject` | `ImageObject?` | Image representation | | `details` | `String` | Description translated according to the current SDK language | | `daylight` | `WeatherDaylight` | Daylight condition | | `parameters` | `[WeatherContextParameter]` | Parameter list | ##### WeatherDaylight[​](#weatherdaylight "Direct link to WeatherDaylight") The `WeatherDaylight` enum indicates the daylight state for a given set of conditions. | Value | Description | | ----------------------------- | -------------------------------- | | `WeatherDaylightNotAvailable` | Daylight condition not available | | `WeatherDaylightDay` | Daytime | | `WeatherDaylightNight` | Nighttime | ##### Predefined parameter type values[​](#predefined-parameter-type-values "Direct link to Predefined parameter type values") The following strings are the common values for `WeatherContextConditions.type` and `WeatherContextParameter.type`. | Value | Description | Unit | | ------------------- | ------------------------- | ---- | | `"AirQuality"` | Air quality index | - | | `"DewPoint"` | Dew point temperature | °C | | `"FeelsLike"` | Apparent temperature | °C | | `"Humidity"` | Relative humidity | % | | `"Pressure"` | Atmospheric pressure | mb | | `"Sunrise"` | Sunrise time | - | | `"Sunset"` | Sunset time | - | | `"Temperature"` | Current temperature | °C | | `"UV"` | UV index | - | | `"Visibility"` | Visibility distance | km | | `"WindDirection"` | Wind direction | ° | | `"WindSpeed"` | Wind speed | km/h | | `"TemperatureLow"` | Daily minimum temperature | °C | | `"TemperatureHigh"` | Daily maximum temperature | °C | danger The `WeatherContext` may return data with varying parameter types, depending on data availability. A response might include only a subset of the values listed above. #### WeatherContextParameter[​](#weathercontextparameter "Direct link to WeatherContextParameter") The `WeatherContextParameter` class contains weather parameter data. | Property | Type | Description | | -------- | -------- | --------------------------------------------------------------------------------------------- | | `type` | `String` | For possible values see [Predefined parameter type values](#predefined-parameter-type-values) | | `value` | `Double` | Value | | `name` | `String` | Name translated according to the current SDK language | | `unit` | `String` | Unit translated according to the current SDK language | --- ### Weather Service Last updated: April 6, 2026 | 3 minutes read

The `WeatherContext` class provides methods for retrieving current, hourly, and daily weather forecasts. Access the shared instance via `WeatherContext.shared()`. #### Get Current Weather Forecast[​](#get-current-weather-forecast "Direct link to Get Current Weather Forecast") Use `requestCurrentForecast(_:completionHandler:)` to retrieve the current weather forecast. Provide an array of `CoordinatesObject` for the desired locations. The completion handler receives an array of `WeatherContextForecast` objects — one per coordinate. ```swift let coordinates = CoordinatesObject.coordinates(withLatitude: 48.864716, longitude: 2.349014) WeatherContext.shared().requestCurrentForecast([coordinates]) { code, forecasts in guard code == .kNoError else { print("Error: \(code)") return } print("Current forecast count: \(forecasts.count)") } ``` danger Verify that each `WeatherContextForecast` contains a `WeatherContextConditions` and that `parameters` is non-empty. If data is unavailable for the specified location, the API may return an empty array. info The result contains as many `WeatherContextForecast` objects as coordinates provided to the request. #### Get Hourly Weather Forecast[​](#get-hourly-weather-forecast "Direct link to Get Hourly Weather Forecast") Use `requestHourlyForecast(_:hours:completionHandler:)` to retrieve hourly weather forecasts. The completion handler receives a nested array — one inner array of `WeatherContextForecast` objects per coordinate, with each element representing a single hour. ```swift let coordinates = CoordinatesObject.coordinates(withLatitude: 48.864716, longitude: 2.349014) WeatherContext.shared().requestHourlyForecast([coordinates], hours: 24) { code, forecasts in guard code == .kNoError else { print("Error: \(code)") return } print("Hourly forecasts per location: \(forecasts.count)") } ``` danger The number of requested hours must not exceed 240. Exceeding this limit results in an empty response and a `.kOutOfRange` error. #### Get Daily Weather Forecast[​](#get-daily-weather-forecast "Direct link to Get Daily Weather Forecast") Use `requestDailyForecast(_:days:completionHandler:)` to retrieve daily weather forecasts. The completion handler receives a nested array — one inner array of `WeatherContextForecast` objects per coordinate, with each element representing a single day. ```swift let coordinates = CoordinatesObject.coordinates(withLatitude: 48.864716, longitude: 2.349014) WeatherContext.shared().requestDailyForecast([coordinates], days: 10) { code, forecasts in guard code == .kNoError else { print("Error: \(code)") return } print("Daily forecasts per location: \(forecasts.count)") } ``` danger The number of requested days must not exceed 10. Exceeding this limit results in an empty response and a `.kOutOfRange` error. #### Get Weather Forecast with Timestamp[​](#get-weather-forecast-with-timestamp "Direct link to Get Weather Forecast with Timestamp") Use `requestForecast(_:completionHandler:)` to retrieve weather forecasts for specific coordinates and time offsets. Provide an array of `TimeDistanceCoordinatesObject` items — each pairing a location with a timestamp in seconds relative to the current time. The completion handler receives a nested array of `WeatherContextForecast` objects, one inner array per item in the request. ```swift let coordinates = CoordinatesObject.coordinates(withLatitude: 48.864716, longitude: 2.349014) let tdCoord = TimeDistanceCoordinatesObject( coordinates: coordinates, distance: 0, timestamp: 2 * 24 * 3600 // 2 days in seconds ) WeatherContext.shared().requestForecast([tdCoord]) { code, forecasts in guard code == .kNoError else { print("Error: \(code)") return } print("Forecast count: \(forecasts.count)") } ``` info The `timestamp` parameter in `TimeDistanceCoordinatesObject` specifies the time offset into the future for the forecast, expressed in seconds relative to the current time. #### Cancel Requests[​](#cancel-requests "Direct link to Cancel Requests") Call `cancelRequests()` to abort all pending weather requests: ```swift WeatherContext.shared().cancelRequests() ``` ---