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

documentation image
documentation image

For all code examples, refer to Navigation Code Examples

CustomLocationViewController view source

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

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.