Advanced navigation app

In this example, we are going to create an App that allows users to long press on the map to add a destination to request routes and display the routes on the map, this app also enables route selection from alternative routes, lastly, users can start navigation by tapping the “Start” button, then the app will present the NavigationViewController to begin turn-by-turn navigation.

docs-image

For all code examples, refer to Navigation Code Examples

AdvancedNavigationViewController view code

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
197
198
199
import UIKit
import NbmapNavigation
import NbmapCoreNavigation
import NbmapDirections
import Nbmap

class AdvancedNavigationViewController: UIViewController, NGLMapViewDelegate {
  
   var navigationMapView: NavigationMapView? {
       didSet {
           oldValue?.removeFromSuperview()
           if let navigationMapView = navigationMapView {
               view.insertSubview(navigationMapView, at: 0)
           }
       }
   }
  
   var routes : [Route]? {
       didSet {
           guard routes != nil else{
               startButton.isEnabled = false
               return
           }
           startButton.isEnabled = true
          
           guard let routes = routes,
                 let current = routes.first else { navigationMapView?.removeRoutes(); return }
          
           navigationMapView?.showRoutes(routes)
           navigationMapView?.showWaypoints(current)
           navigationMapView?.showRouteDurationSymbol(routes)
       }
   }
  
   var startButton = UIButton()
  
   override func viewDidLoad() {
       super.viewDidLoad()
      
       self.navigationMapView = NavigationMapView(frame: view.bounds)
      
       navigationMapView?.userTrackingMode = .followWithHeading
      
       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
       navigationMapView?.navigationMapDelegate = self
      
       setupStartButton()
      
       self.view.setNeedsLayout()
      
   }
  
   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(tappedButton), 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 tappedButton(sender: UIButton) {
       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)
      
       // Set the delegate of navigationViewController to subscribe for NavigationView's events, This is optional
       // navigationViewController.delegate = weakSelf
       navigationViewController.modalPresentationStyle = .fullScreen
      
       present(navigationViewController, animated: true, completion: nil)
   }
  
   @objc func didLongPress(tap: UILongPressGestureRecognizer) {
       guard let navigationMapView = navigationMapView, tap.state == .began else {
           return
       }
       let coordinates = navigationMapView.convert(tap.location(in: navigationMapView), toCoordinateFrom: navigationMapView)
       let destination = Waypoint(coordinate: coordinates, name: "\(coordinates.latitude),\(coordinates.longitude)")
       addNewDestinationIcon(coordinates: coordinates)
      
       guard let currentLocation =  navigationMapView.userLocation?.coordinate else { return}
       let currentWayPoint = Waypoint.init(coordinate: currentLocation, name: "My Location")
      
       requestRoutes(origin: currentWayPoint, destination: destination)
   }
  
  
   func addNewDestinationIcon(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 = NavigationRouteOptions(origin: origin, destination: destination)
       /**
        Sets whether the route contains an alternate route
        By default, it set to `false`
        */
       options.includesAlternativeRoutes = true
       /**
        The route classes that the calculated routes will avoid.
        We can set an array road class to avoid.  This property can be set to `.toll`,`.ferry`,`.highway`
        By default ,  this property is set to empty
        */
       options.roadClassesToAvoid = [.toll,.ferry,.highway]
       /**
        Set up the navigation measurement unit
        This property should be set to `.metric` or `.imperial`
        By default,  this property is set to the unit following with the current system locale.
        */
       options.distanceMeasurementSystem = .imperial
      
       /**
        Set specifying the primary mode of transportation for the routes.
        This property should be set to `NBNavigationModeCar`, `NBNavigationModeAuto`, `NBNavigationModeBike`, `NBNavigationMode4W`,`NBNavigationMode2W`,`NBNavigationMode6W`, or `NBNavigationModeEscooter`. The default value of this property is `NBNavigationMode4W`,  which specifies driving directions.
        */
       options.profileIdentifier = NBNavigationMode.mode4W
       /**
        Set the departureTime of the route, By default,  it sets the current timestamp since 1970
        */
       options.departureTime = Int(Date().timeIntervalSince1970)
      
       /**
        Set up the locale in which the route's instructions are written.
        If you use NbmapDirections.swift with the Nbmap Directions API, this property affects the sentence contained within the `RouteStep.instructions` property, but it does not affect any road names contained in that property or other properties such as `RouteStep.name`.
        The Navigation API can provide instructions in [a number of languages]. Set this property to `Bundle.main.preferredLocalizations.first` or `Locale.autoupdatingCurrent` to match the application's language or the system language, respectively.
        By default, this property is set to the current system locale.
        */
       options.locale = Locale.autoupdatingCurrent
      
       /**
        Set map options, This property may make the ETA more accurate.  If set to `NBMapOption.valhala`, shapeFormat needs to be set to `polyline`.
        By default, the value of this property is `NBMapOption.none`
        */
       options.mapOption = NBMapOption.none
       /**
        Format of the data from which the shapes of the returned route and its steps are derived.
       
        This property has no effect on the returned shape objects, although the choice of format can significantly affect the size of the underlying HTTP response.
       
        The default value of this property is `polyline6`.
        */
       options.shapeFormat = .polyline6
      
       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
       }
   }
}

extension AdvancedNavigationViewController: NavigationMapViewDelegate {
   // Delegate method called when the user selects a route
   func navigationMapView(_ mapView: NavigationMapView, didSelect route: Route) {
       guard let routes = routes else { return }
       guard let index = routes.firstIndex(where: { $0 == route }) else { return }
       self.routes!.remove(at: index)
       self.routes!.insert(route, at: 0)
   }
}

The code defines a custom view controller called AdvancedNavigationViewController that inherits from UIViewController and conforms to the NGLMapViewDelegate protocol. The purpose of this custom view controller is to implement an advanced navigation experience using NbmapNavigation, NbmapCoreNavigation, NbmapDirections, and Nbmap libraries.

  • Import statements: The necessary frameworks are imported at the beginning of the file.

  • navigationMapView: A property for the NavigationMapView instance. When its value is set, the old instance is removed from the view hierarchy, and the new one is inserted as a subview.

  • routes: An array of Route objects. When its value is set, the start button's state is updated accordingly, and the route, waypoints, and route duration symbols are shown on the navigation map view.

  • startButton: A UIButton that initiates navigation when tapped.

  • viewDidLoad(): This method sets up the view controller after the view is loaded. It creates a NavigationMapView, adds a long press gesture recognizer, sets up the start button, and calls view.setNeedsLayout() to invalidate the current layout of the receiver and trigger a layout update during the next update cycle.

  • setupStartButton(): This method configures the start button's appearance and adds a target to handle the button tap.

  • tappedButton(sender:): The action method for the start button tap. It initializes a NBNavigationService, sets up navigation options and a NavigationViewController, and then presents the navigation view controller.

  • didLongPress(tap:): The method that handles the long press gesture. It converts the tap location to coordinates and then adds a new destination based on the coordinates. It also makes a request for routes between the user's current location and the destination.

  • addNewDestinationIcon(coordinates:): This method adds a new destination annotation to the navigation map view.

  • requestRoutes(origin:destination:): This method requests routes between the given origin and destination using the specified options.

  • An extension of AdvancedNavigationViewController that conforms to the NavigationMapViewDelegate protocol. It contains the navigationMapView(_:didSelect:) delegate method to handle when the user selects a route from the displayed routes.

ios-sdk-7

Have Questions ?