User interface
Free-drive navigation is a unique Mapbox Navigation SDK feature that allows drivers to navigate without a set destination. This mode is sometimes referred to as passive navigation.
The Navigation SDK’s free-driving mode snaps the device’s location to the road network using map matching and predicts location updates to prevent visual lags and disruptions in areas with poor GPS coverage (including tunnels). Unlike in turn-by-turn navigation, you do not provide a route and the user does not need to stick to a predefined path.
This allows you to keep the map in your application continually centered on the device’s current location and proactively provide drivers with relevant information about nearby places and roads including traffic, incidents, and points of interest. For example, free-driving mode would be helpful to drivers who are in areas they are familiar with and do not need turn-by-turn instructions, but may want to avoid congested roads.
You'll also be able to take advantage of electronic horizon and learn about road objects around the device's location, you can read more about it in the Electronic horizon guide.
Start a free-drive trip
A free-drive trip starts when you create an instance of the PassiveLocationManager
class.
Create a free-drive view.
Build a free-drive UI
You can build any free-driving user interface that fits the needs of your users. While you don't have to use the UI components included in the Navigation SDK, displaying a user's location on a map and providing additional context like speed limits may be helpful in many applications.
Map
While applications using the Mapbox Navigation SDK for iOS don't have to include a map, many use cases will require maps to provide an appropriate navigation experience to your users.
For more information on initializing a NavigationMapView
, see the Maps for navigation guide.
To enable free-driving mode in a NavigationMapView
, create a PassiveLocationManager
and override default LocationProvider
with a custom one by calling MapView.Location.overrideLocationProvider(with:)
method.
private let passiveLocationManager = PassiveLocationManager()
private lazy var passiveLocationProvider = PassiveLocationProvider(locationManager: passiveLocationManager)
let locationProvider: LocationProvider = passiveLocationProvider
navigationMapView.mapView.location.overrideLocationProvider(with: locationProvider)
passiveLocationProvider.startUpdatingLocation()
To receive these locations without an MapView
, use the
PassiveLocationManagerDelegate
class and implement the
PassiveLocationManagerDelegate.passiveLocationManager(_:didUpdateLocation:rawLocation:)
method or observe
passiveLocationManagerDidUpdate
notifications.
You have full control over the style of the map. For more information on customizing the map style including day and night styling, adding 3D buildings, and more, read the Maps for navigation guide.
Speed limit
The speed limit of the road the user is currently traveling on can be helpful context regardless of whether they are following a specific route. Start by adding SpeedLimitView
to the current view as a subview:
// Create the view
let speedLimitView = SpeedLimitView()
// Add it as a subview
view.addSubview(speedLimitView)
Speed limit information is available via the notifications posted by PassiveLocationManager
:
speedLimitKey
is the maximum speed limit of the current road.signStandard
is the sign standard used for speed limit signs along the current road, so you can display the speed limit in a visually recognizable sign-like view regardless of where the user is traveling.
You can use this data to power a custom UI component, or you can pass the data to the pre-built SpeedLimitView
included in the Navigation SDK as seen below:
@objc func didUpdatePassiveLocation(_ notification: Notification) {
// Set the sign standard of the speed limit UI components
// to the standard posted by PassiveLocationManager
speedLimitView.signStandard = notification.userInfo?[PassiveLocationManager.NotificationUserInfoKey.signStandardKey] as? SignStandard
// Set the value of the speed limit UI component to the value
// posted by PassiveLocationManager
speedLimitView.speedLimit = notification.userInfo?[PassiveLocationManager.NotificationUserInfoKey.speedLimitKey] as? Measurement<UnitSpeed>
}
If you are using SpeedLimitView
, you can customize how the component is styled. See the SpeedLimitView
documentation for more details.
Road name
The name of the road the driver is currently traveling on can also be helpful context when navigating without a route. This information is available via the notifications posted by PassiveLocationManager
. roadNameKey
is the name of the road the user is currently traveling on. You can use this data as the content of a custom UI component by referencing PassiveLocationManager.NotificationUserInfoKey.roadNameKey
.
Go further
The possibilities are near endless when creating your free-driving UI. The Navigation SDK's electronic horizon provides additional information that you can use to provide more context for drivers. You can use electronic horizon to learn about road objects around the device's location and present drivers with information to enhance the driving experience. You can read more about it in the Electronic horizon guide.
Predict user's most probable path and show upcoming intersections.
Avoid location tracking crosstalk
Applications that use the Navigation SDK often begin by showing a preview map view before presenting the NavigationViewController
for active guidance. But allowing the preview map view, PassiveLocationManager
, and the RouteController
built into NavigationViewController
to run at the same time causes cross talk that can lead to location tracking issues. To avoid this behavior, you need to either uninstall the preview map view or at least pause the PassiveLocationManager
before presenting the NavigationViewController
. To uninstall the map view, remove any notification observers related to free-driving and call UIView.removeFromSuperView()
within the present(_:animation:completion:)
completion handler:
present(navigationViewController, animated: true) {
NotificationCenter.default.removeObeserver(self, name: .passiveLocationDataSourceDidUpdate, object: nil)
self.mapView?.removeFromSuperView()
self.mapView = nil
}
The preview map view and PassiveLocationManager
are not of use during active guidance, so it is safe to dispose of them. Following these instructions should also free up around 15 megabytes of memory. Make sure to then add new PassiveLocationManager
notification observers and add the preview map view back to the view hierarchy when the user exits navigation.
Sometimes removing the map view entirely is too invasive. Instead, you can pause the PassiveLocationManager
by calling stopUpdatingLocation()
and stopUpdatingHeading()
on your PassiveLocationManager
.