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 NbmapDirections
5
import Nbmap
6
7
class 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.

© 2024 NextBillion.ai all rights reserved.