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
documentation image

For all code examples, refer to Navigation Code Examples

CustomCameraController view source

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

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