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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import UIKit
import NbmapNavigation
import NbmapCoreNavigation
import NbmapDirections
import Nbmap

class CustomLocationViewController: UIViewController ,NGLMapViewDelegate{
   typealias ActionHandler = (UIAlertAction) -> Void
  
   var navigationMapView: NavigationMapView! {
       didSet {
           oldValue?.removeFromSuperview()
           if let mapView = navigationMapView {
               view.insertSubview(mapView, at: 0)
           }
       }
   }
  
   var routes : [Route]? {
       didSet {
           guard routes != nil else{
               startButton.isEnabled = false
               return
           }
           startButton.isEnabled = true
       }
   }
  
   private let startButton = UIButton()
  
   override func viewDidLoad() {
       super.viewDidLoad()
      
       self.navigationMapView = NavigationMapView(frame: view.bounds)
      
       navigationMapView.userTrackingMode = .follow
       let singleTap = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(tap:)))
       navigationMapView?.gestureRecognizers?.filter({ $0 is UILongPressGestureRecognizer }).forEach(singleTap.require(toFail:))
       navigationMapView?.addGestureRecognizer(singleTap)
       navigationMapView?.delegate = self
      
       let navigationViewportDataSource = NavigationViewportDataSource(navigationMapView)
       navigationViewportDataSource.options.followingCameraOptions.zoomUpdatesAllowed = false
       navigationViewportDataSource.followingMobileCamera = NavMapCameraOption()
       navigationMapView.navigationCamera.viewportDataSource = navigationViewportDataSource
      
       setupStartButton()
   }
  
   func setupStartButton() {
       startButton.setTitle("Start", for: .normal)
       startButton.layer.cornerRadius = 5
       startButton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
       startButton.backgroundColor = .blue
      
       startButton.addTarget(self, action: #selector(performAction), for: .touchUpInside)
       view.addSubview(startButton)
       startButton.translatesAutoresizingMaskIntoConstraints = false
       startButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -50).isActive = true
       startButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
       startButton.titleLabel?.font = UIFont.systemFont(ofSize: 25)
   }
  
   @objc func didLongPress(tap: UILongPressGestureRecognizer) {
       guard let mapView = navigationMapView, tap.state == .began else {
           return
       }
       let coordinates = mapView.convert(tap.location(in: mapView), toCoordinateFrom: mapView)
       // Note: The destination name can be modified. The value is used in the top banner when arriving at a destination.
       let destination = Waypoint(coordinate: coordinates, name: "\(coordinates.latitude),\(coordinates.longitude)")
       addNewDestinationcon(coordinates: coordinates)
      
       guard let currentLocation =  mapView.userLocation?.coordinate else { return}
       let currentWayPoint = Waypoint.init(coordinate: currentLocation, name: "My Location")
      
       requestRoutes(origin: currentWayPoint, destination: destination)
   }
  
   @objc func performAction(_ sender: Any) {
       guard routes != nil else {
           let alertController = UIAlertController(title: "Create route",
                                                   message: "Long tap on the map to create a route first.",
                                                   preferredStyle: .alert)
           alertController.addAction(UIAlertAction(title: "OK", style: .default))
           return present(alertController, animated: true)
       }
       // Set Up the alert controller to switch between different userLocationStyle.
       let alertController = UIAlertController(title: "Choose UserLocationStyle",
                                               message: "Select the user location style",
                                               preferredStyle: .actionSheet)
      
       let courseView: ActionHandler = { _ in self.setupCourseView() }
       let defaultPuck2D: ActionHandler = { _ in self.setupDefaultPuck2D() }
       let invisiblePuck: ActionHandler = { _ in self.setupInvisiblePuck() }
       let puck2D: ActionHandler = { _ in self.setupCustomPuck2D() }
       let cancel: ActionHandler = { _ in }
      
       let actionPayloads: [(String, UIAlertAction.Style, ActionHandler?)] = [
           ("Invisible Puck", .default, invisiblePuck),
           ("Default Course View", .default, courseView),
           ("2D Default Puck", .default, defaultPuck2D),
           ("2D Custom Puck", .default, puck2D),
           ("Cancel", .cancel, cancel)
       ]
      
       actionPayloads
           .map { payload in UIAlertAction(title: payload.0, style: payload.1, handler: payload.2) }
           .forEach(alertController.addAction(_:))
      
       if let popoverController = alertController.popoverPresentationController {
           popoverController.sourceView = self.startButton
           popoverController.sourceRect = self.startButton.bounds
       }
      
       present(alertController, animated: true, completion: nil)
   }
  
   func setupCourseView() {
       presentNavigationViewController(.courseView())
   }
  
   func setupDefaultPuck2D() {
       presentNavigationViewController(.puck2D)
   }
  
   func setupInvisiblePuck() {
       presentNavigationViewController(.none)
   }
  
   func setupCustomPuck2D() {
       let courseView = UserPuckCourseView(frame: CGRect(origin: .zero, size: 75.0))
       courseView.puckView.image = UIImage(named: "airplane")
       let userLocationStyle = UserLocationStyle.courseView(courseView)
       presentNavigationViewController(userLocationStyle)
   }
  
   func presentNavigationViewController(_ userLocationStyle: UserLocationStyle? = nil) {
       guard let routes = self.routes else {
           return
       }
       let navigationService = NBNavigationService(routes: routes, routeIndex: 0)
       let navigationOptions = NavigationOptions(navigationService: navigationService)
       let navigationViewController = NavigationViewController(for: routes,navigationOptions: navigationOptions)
       navigationViewController.modalPresentationStyle = .fullScreen
       navigationViewController.navigationMapView?.userLocationStyle = userLocationStyle
       present(navigationViewController, animated: true, completion: nil)
   }
  
   func addNewDestinationcon(coordinates: CLLocationCoordinate2D){
       guard let mapView = navigationMapView else {
           return
       }
      
       if let annotation = mapView.annotations?.last {
           mapView.removeAnnotation(annotation)
       }
      
       let annotation = NGLPointAnnotation()
       annotation.coordinate = coordinates
       mapView.addAnnotation(annotation)
   }
  
   func requestRoutes(origin: Waypoint, destination: Waypoint){
      
       let options = RouteOptions.init(origin: origin, destination: destination)
       // Includes alternative routes in the response
       options.includesAlternativeRoutes = true
      
       // Avoid road options , Set an array road class to avoid.
       options.roadClassesToAvoid = []
      
       // Set the measurement format system
       options.distanceMeasurementSystem = MeasurementSystem.metric
      
       Directions.shared.calculate(options) { [weak self] routes, error in
           guard let weakSelf = self else {
               return
           }
           guard error == nil else {
               print(error!)
               return
           }
          
           guard let routes = routes else { return }
          
          
           // Process or display routes information.For example,display the routes,waypoints and duration symbol on the map
           weakSelf.navigationMapView?.showRoutes(routes)
           weakSelf.navigationMapView?.showRouteDurationSymbol(routes)
          
           guard let current = routes.first else { return }
           weakSelf.navigationMapView?.showWaypoints(current)
           weakSelf.routes = routes
       }
   }
}

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.

Have Questions ?