Custom navigation camera

Optimize navigation experience by adjusting MapView camera based on requested route, ensuring clear visibility and focus on route.

This example shows:

  • How to fit the mapview camera according to the requested route info
docs-image

For all code examples, refer to Navigation Code Examples

CustomCameraController view source

1import UIKit
2import NbmapNavigation
3import NbmapCoreNavigation
4import NbmapDirections
5import Nbmap
6
7class CustomCameraController: UIViewController, NGLMapViewDelegate, NavigationViewControllerDelegate {
8
9 var mapView: NavigationMapView? {
10 didSet {
11 oldValue?.removeFromSuperview()
12 if let mapView = mapView {
13 view.insertSubview(mapView, at: 0)
14 }
15 }
16 }
17
18 var routes : [Route]? {
19 didSet {
20 guard let routes = routes,
21 let current = routes.first
22 else {
23 mapView?.removeRoutes()
24 mapView?.removeRouteDurationSymbol()
25 if let annotation = mapView?.annotations?.last {
26 mapView?.removeAnnotation(annotation)
27 }
28 return
29
30 }
31
32 mapView?.showRoutes(routes)
33 mapView?.showWaypoints(current)
34 mapView?.showRouteDurationSymbol(routes)
35
36 fitCameraWithRoutes(routes: routes)
37 }
38 }
39
40 var startButton = UIButton()
41
42 var trackingImage = UIImageView()
43
44 override func viewDidLoad() {
45 super.viewDidLoad()
46
47 self.mapView = NavigationMapView(frame: view.bounds)
48
49 mapView?.userTrackingMode = .followWithHeading
50
51 let singleTap = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(tap:)))
52 mapView?.gestureRecognizers?.filter({ $0 is UILongPressGestureRecognizer }).forEach(singleTap.require(toFail:))
53 mapView?.addGestureRecognizer(singleTap)
54 mapView?.delegate = self
55
56 setupStartButton()
57 setupLocationTrackingButton()
58 }
59
60 func setupStartButton() {
61 startButton.setTitle("Start", for: .normal)
62 startButton.layer.cornerRadius = 5
63 startButton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
64 startButton.backgroundColor = .blue
65
66 startButton.addTarget(self, action: #selector(tappedButton), for: .touchUpInside)
67 view.addSubview(startButton)
68 startButton.translatesAutoresizingMaskIntoConstraints = false
69 startButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -50).isActive = true
70 startButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
71 startButton.titleLabel?.font = UIFont.systemFont(ofSize: 25)
72 }
73
74 func setupLocationTrackingButton(){
75 trackingImage.isUserInteractionEnabled = true
76 trackingImage.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(trackingClick)))
77 trackingImage.image = UIImage(named: "my_location")
78 view.addSubview(trackingImage)
79 startButton.translatesAutoresizingMaskIntoConstraints = false
80 startButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -50).isActive = true
81 startButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor,constant: 16).isActive = true
82 }
83
84 @objc func didLongPress(tap: UILongPressGestureRecognizer) {
85 guard let mapView = mapView, tap.state == .began else {
86 return
87 }
88 let coordinates = mapView.convert(tap.location(in: mapView), toCoordinateFrom: mapView)
89 let destination = Waypoint(coordinate: coordinates, name: "\(coordinates.latitude),\(coordinates.longitude)")
90
91 guard let currentLocation = mapView.userLocation?.coordinate else { return}
92 let currentWayPoint = Waypoint.init(coordinate: currentLocation, name: "My Location")
93
94 requestRoutes(origin: currentWayPoint, destination: destination)
95 }
96
97
98 @objc func tappedButton(sender: UIButton) {
99 guard let routes = self.routes else {
100 return
101 }
102 var engineConfig = NavigationEngineConfig()
103 // Config mininum and maxnum camera zoom
104 engineConfig.minNavigationCameraZoom = 16
105 engineConfig.maxNavigationCameraZoom = 18
106 let navigationService = NBNavigationService(routes: routes, routeIndex: 0,navigationEngConfig: engineConfig)
107
108 let navigationOptions = NavigationOptions(navigationService: navigationService)
109 let navigationViewController = NavigationViewController(for: routes,navigationOptions: navigationOptions)
110 navigationViewController.modalPresentationStyle = .fullScreen
111
112 navigationViewController.delegate = self
113
114 // Start navigation
115 present(navigationViewController, animated: true, completion: nil)
116 }
117
118 @objc func trackingClick() {
119 guard let location = mapView?.userLocation else {
120 return
121 }
122 guard let newCamera = mapView?.camera else {
123 return
124 }
125 newCamera.centerCoordinate = location.coordinate
126 newCamera.viewingDistance = 1000
127 mapView?.setCamera(newCamera, animated: true)
128 }
129
130 func fitCameraWithRoutes(routes: [Route]) {
131 guard let mapView = mapView else {
132 return
133 }
134
135 var coordinates: [CLLocationCoordinate2D] = []
136 routes.forEach({route in
137 coordinates.append(contentsOf: route.coordinates!)
138 })
139 let polyLine = NGLPolyline(coordinates: coordinates, count: UInt(coordinates.count))
140 let camera = mapView.cameraThatFitsShape(polyLine, direction: mapView.camera.heading, edgePadding: UIEdgeInsets(top: view.safeAreaInsets.top, left: view.safeAreaInsets.left, bottom: view.safeAreaInsets.bottom, right: view.safeAreaInsets.right))
141 mapView.setCamera(camera, animated: true)
142 }
143
144 func requestRoutes(origin: Waypoint, destination: Waypoint){
145 let options = NavigationRouteOptions(origin: origin, destination: destination)
146
147 Directions.shared.calculate(options) { [weak self] routes, error in
148 guard let weakSelf = self else {
149 return
150 }
151 guard error == nil else {
152 print(error!)
153 return
154 }
155
156 guard let routes = routes else { return }
157 weakSelf.routes = routes
158 }
159 }
160}

This class is a view controller that allows users to add a custom destination marker to a map and then navigate to that destination. The class has the following properties:

  • mapView: A NavigationMapView object that displays the map.

  • routes: An array of Route objects that represent the routes between the user's current location and the custom destination marker.

  • startButton: A UIButton object that is used to start navigation.

  • trackingImage: A UIImageView object that is used to show the current location of the user.

The class has the following methods:

  • viewDidLoad(): This method is called when the view controller is loaded. In this method, the map view is created and configured.

  • didLongPress(): This method is called when the user long-presses on the map. In this method, a custom destination marker is added to the map at the location of the long press.

  • tappedButton(): This method is called when the user taps the "Start Navigation" button. In this method, a navigation controller is created and presented to the user. The navigation controller uses the routes property to display the route between the user's current location and the custom destination marker.

  • trackingClick(): This method is called when the user taps the "My Location" button. In this method, the map view is centered on the user's current location.

  • fitCameraWithRoutes(): This method fits the camera to the routes on the map.

  • requestRoutes(): This method requests routes between the user's current location and the custom destination marker.

The code you provided also has two extensions:

  • CustomCameraController: NGLMapViewDelegate

  • CustomCameraController: NavigationViewControllerDelegate

These extensions conform the CustomCameraController class to the NGLMapViewDelegate and NavigationViewControllerDelegate protocols. These protocols allow the CustomCameraController class to respond to events from the map view and the navigation controller.

ios-sdk-7