Custom location indicator

Customize the location indicator in the Navigation view based on user preferences and add a bottom sheet to allow user-customized actions.

This example shows:

  • How to add a bottom sheet to allow user-customized actions

  • How to Custom location indicator in Navigation view based on user preferences

docs-image
docs-image

For all code examples, refer to Navigation Code Examples

CustomLocationViewController view source

1import UIKit
2import NbmapNavigation
3import NbmapCoreNavigation
4import NbmapDirections
5import Nbmap
6
7class CustomLocationViewController: UIViewController ,NGLMapViewDelegate{
8 typealias ActionHandler = (UIAlertAction) -> Void
9
10 var navigationMapView: NavigationMapView! {
11 didSet {
12 oldValue?.removeFromSuperview()
13 if let mapView = navigationMapView {
14 view.insertSubview(mapView, at: 0)
15 }
16 }
17 }
18
19 var routes : [Route]? {
20 didSet {
21 guard routes != nil else{
22 startButton.isEnabled = false
23 return
24 }
25 startButton.isEnabled = true
26 }
27 }
28
29 private let startButton = UIButton()
30
31 override func viewDidLoad() {
32 super.viewDidLoad()
33
34 self.navigationMapView = NavigationMapView(frame: view.bounds)
35
36 navigationMapView.userTrackingMode = .follow
37 let singleTap = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(tap:)))
38 navigationMapView?.gestureRecognizers?.filter({ $0 is UILongPressGestureRecognizer }).forEach(singleTap.require(toFail:))
39 navigationMapView?.addGestureRecognizer(singleTap)
40 navigationMapView?.delegate = self
41
42 let navigationViewportDataSource = NavigationViewportDataSource(navigationMapView)
43 navigationViewportDataSource.options.followingCameraOptions.zoomUpdatesAllowed = false
44 navigationViewportDataSource.followingMobileCamera = NavMapCameraOption()
45 navigationMapView.navigationCamera.viewportDataSource = navigationViewportDataSource
46
47 setupStartButton()
48 }
49
50 func setupStartButton() {
51 startButton.setTitle("Start", for: .normal)
52 startButton.layer.cornerRadius = 5
53 startButton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
54 startButton.backgroundColor = .blue
55
56 startButton.addTarget(self, action: #selector(performAction), for: .touchUpInside)
57 view.addSubview(startButton)
58 startButton.translatesAutoresizingMaskIntoConstraints = false
59 startButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -50).isActive = true
60 startButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
61 startButton.titleLabel?.font = UIFont.systemFont(ofSize: 25)
62 }
63
64 @objc func didLongPress(tap: UILongPressGestureRecognizer) {
65 guard let mapView = navigationMapView, tap.state == .began else {
66 return
67 }
68 let coordinates = mapView.convert(tap.location(in: mapView), toCoordinateFrom: mapView)
69 // Note: The destination name can be modified. The value is used in the top banner when arriving at a destination.
70 let destination = Waypoint(coordinate: coordinates, name: "\(coordinates.latitude),\(coordinates.longitude)")
71 addNewDestinationcon(coordinates: coordinates)
72
73 guard let currentLocation = mapView.userLocation?.coordinate else { return}
74 let currentWayPoint = Waypoint.init(coordinate: currentLocation, name: "My Location")
75
76 requestRoutes(origin: currentWayPoint, destination: destination)
77 }
78
79 @objc func performAction(_ sender: Any) {
80 guard routes != nil else {
81 let alertController = UIAlertController(title: "Create route",
82 message: "Long tap on the map to create a route first.",
83 preferredStyle: .alert)
84 alertController.addAction(UIAlertAction(title: "OK", style: .default))
85 return present(alertController, animated: true)
86 }
87 // Set Up the alert controller to switch between different userLocationStyle.
88 let alertController = UIAlertController(title: "Choose UserLocationStyle",
89 message: "Select the user location style",
90 preferredStyle: .actionSheet)
91
92 let courseView: ActionHandler = { _ in self.setupCourseView() }
93 let defaultPuck2D: ActionHandler = { _ in self.setupDefaultPuck2D() }
94 let invisiblePuck: ActionHandler = { _ in self.setupInvisiblePuck() }
95 let puck2D: ActionHandler = { _ in self.setupCustomPuck2D() }
96 let cancel: ActionHandler = { _ in }
97
98 let actionPayloads: [(String, UIAlertAction.Style, ActionHandler?)] = [
99 ("Invisible Puck", .default, invisiblePuck),
100 ("Default Course View", .default, courseView),
101 ("2D Default Puck", .default, defaultPuck2D),
102 ("2D Custom Puck", .default, puck2D),
103 ("Cancel", .cancel, cancel)
104 ]
105
106 actionPayloads
107 .map { payload in UIAlertAction(title: payload.0, style: payload.1, handler: payload.2) }
108 .forEach(alertController.addAction(_:))
109
110 if let popoverController = alertController.popoverPresentationController {
111 popoverController.sourceView = self.startButton
112 popoverController.sourceRect = self.startButton.bounds
113 }
114
115 present(alertController, animated: true, completion: nil)
116 }
117
118 func setupCourseView() {
119 presentNavigationViewController(.courseView())
120 }
121
122 func setupDefaultPuck2D() {
123 presentNavigationViewController(.puck2D)
124 }
125
126 func setupInvisiblePuck() {
127 presentNavigationViewController(.none)
128 }
129
130 func setupCustomPuck2D() {
131 let courseView = UserPuckCourseView(frame: CGRect(origin: .zero, size: 75.0))
132 courseView.puckView.image = UIImage(named: "airplane")
133 let userLocationStyle = UserLocationStyle.courseView(courseView)
134 presentNavigationViewController(userLocationStyle)
135 }
136
137 func presentNavigationViewController(_ userLocationStyle: UserLocationStyle? = nil) {
138 guard let routes = self.routes else {
139 return
140 }
141 let navigationService = NBNavigationService(routes: routes, routeIndex: 0)
142 let navigationOptions = NavigationOptions(navigationService: navigationService)
143 let navigationViewController = NavigationViewController(for: routes,navigationOptions: navigationOptions)
144 navigationViewController.modalPresentationStyle = .fullScreen
145 navigationViewController.navigationMapView?.userLocationStyle = userLocationStyle
146 present(navigationViewController, animated: true, completion: nil)
147 }
148
149 func addNewDestinationcon(coordinates: CLLocationCoordinate2D){
150 guard let mapView = navigationMapView else {
151 return
152 }
153
154 if let annotation = mapView.annotations?.last {
155 mapView.removeAnnotation(annotation)
156 }
157
158 let annotation = NGLPointAnnotation()
159 annotation.coordinate = coordinates
160 mapView.addAnnotation(annotation)
161 }
162
163 func requestRoutes(origin: Waypoint, destination: Waypoint){
164
165 let options = RouteOptions.init(origin: origin, destination: destination)
166 // Includes alternative routes in the response
167 options.includesAlternativeRoutes = true
168
169 // Avoid road options , Set an array road class to avoid.
170 options.roadClassesToAvoid = []
171
172 // Set the measurement format system
173 options.distanceMeasurementSystem = MeasurementSystem.metric
174
175 Directions.shared.calculate(options) { [weak self] routes, error in
176 guard let weakSelf = self else {
177 return
178 }
179 guard error == nil else {
180 print(error!)
181 return
182 }
183
184 guard let routes = routes else { return }
185
186
187 // Process or display routes information.For example,display the routes,waypoints and duration symbol on the map
188 weakSelf.navigationMapView?.showRoutes(routes)
189 weakSelf.navigationMapView?.showRouteDurationSymbol(routes)
190
191 guard let current = routes.first else { return }
192 weakSelf.navigationMapView?.showWaypoints(current)
193 weakSelf.routes = routes
194 }
195 }
196}

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:

  • navigationMapView: 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.

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.

  • performAction(): 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.

  • setupStartButton(): This method sets up the "Start Navigation" button.

  • setupCourseView(): This method sets up the map view to use a course view for the user location.

  • setupDefaultPuck2D(): This method sets up the map view to use the default 2D puck for the user location.

  • setupInvisiblePuck(): This method sets up the map view to use an invisible puck for the user location.

  • setupCustomPuck2D(): This method sets up the map view to use a custom 2D puck for the user location.

  • presentNavigationViewController(): This method presents a navigation controller for the given routes.

  • addNewDestinationcon(): This method adds a new destination annotation to the map.

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

The code also has two extensions:

  • CustomLocationViewController: NGLMapViewDelegate

  • CustomLocationViewController: NavigationViewControllerDelegate

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