Custom navigation camera
Add custom data source and transitions to navigation camera.
/*This code example is part of the Mapbox Navigation SDK for iOS demo app,which you can build and run: https://github.com/mapbox/mapbox-navigation-ios-examplesTo learn more about each example in this app, including descriptions and linksto documentation, see our docs: https://docs.mapbox.com/ios/navigation/examples/custom-navigation-camera*/ import UIKitimport MapboxNavigationimport MapboxMapsimport MapboxDirectionsimport MapboxCoreNavigation class CustomNavigationCameraViewController: UIViewController { var navigationMapView: NavigationMapView!var routeResponse: RouteResponse!var startNavigationButton: UIButton! // MARK: - UIViewController lifecycle methods override func viewDidLoad() {super.viewDidLoad() setupNavigationMapView()setupStartNavigationButton()} override func viewDidLayoutSubviews() {super.viewDidLayoutSubviews() startNavigationButton.layer.cornerRadius = startNavigationButton.bounds.midYstartNavigationButton.clipsToBounds = truestartNavigationButton.setNeedsDisplay()} // MARK: - Setting-up methods func setupNavigationMapView() {navigationMapView = NavigationMapView(frame: view.bounds)navigationMapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]navigationMapView.userLocationStyle = .puck2D() // Modify default `NavigationViewportDataSource` and `NavigationCameraStateTransition` to change// `NavigationCamera` behavior during free drive and when locations are provided by Maps SDK directly.navigationMapView.navigationCamera.viewportDataSource = CustomViewportDataSource(navigationMapView.mapView)navigationMapView.navigationCamera.cameraStateTransition = CustomCameraStateTransition(navigationMapView.mapView) let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))navigationMapView.addGestureRecognizer(longPressGestureRecognizer) view.addSubview(navigationMapView)} func setupStartNavigationButton() {startNavigationButton = UIButton()startNavigationButton.setTitle("Start Navigation", for: .normal)startNavigationButton.translatesAutoresizingMaskIntoConstraints = falsestartNavigationButton.backgroundColor = .lightGraystartNavigationButton.setTitleColor(.darkGray, for: .highlighted)startNavigationButton.setTitleColor(.white, for: .normal)startNavigationButton.contentEdgeInsets = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)startNavigationButton.addTarget(self, action: #selector(startNavigationButtonPressed(_:)), for: .touchUpInside)startNavigationButton.isHidden = trueview.addSubview(startNavigationButton) startNavigationButton.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = truestartNavigationButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true} @objc func startNavigationButtonPressed(_ sender: UIButton) {let indexedRouteResponse = IndexedRouteResponse(routeResponse: routeResponse, routeIndex: 0)let navigationService = MapboxNavigationService(indexedRouteResponse: indexedRouteResponse,customRoutingProvider: NavigationSettings.shared.directions,credentials: NavigationSettings.shared.directions.credentials,simulating: simulationIsEnabled ? .always : .onPoorGPS) let navigationOptions = NavigationOptions(navigationService: navigationService)let navigationViewController = NavigationViewController(for: indexedRouteResponse,navigationOptions: navigationOptions)navigationViewController.modalPresentationStyle = .fullScreen // Modify default `NavigationViewportDataSource` and `NavigationCameraStateTransition` to change// `NavigationCamera` behavior during active guidance.if let mapView = navigationViewController.navigationMapView?.mapView {let customViewportDataSource = CustomViewportDataSource(mapView)navigationViewController.navigationMapView?.navigationCamera.viewportDataSource = customViewportDataSource let customCameraStateTransition = CustomCameraStateTransition(mapView)navigationViewController.navigationMapView?.navigationCamera.cameraStateTransition = customCameraStateTransition} present(navigationViewController, animated: true, completion: nil)} @objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) {guard gesture.state == .ended,let origin = navigationMapView.mapView.location.latestLocation?.coordinate else { return } let destination = navigationMapView.mapView.mapboxMap.coordinate(for: gesture.location(in: navigationMapView.mapView))let navigationRouteOptions = NavigationRouteOptions(coordinates: [origin, destination]) Directions.shared.calculate(navigationRouteOptions) { [weak self] (_, result) inswitch result {case .failure(let error):NSLog("Error occured while requesting route: \(error.localizedDescription).")case .success(let response):guard let route = response.routes?.first else { return } self?.startNavigationButton.isHidden = falseself?.routeResponse = responseself?.navigationMapView.show([route])self?.navigationMapView.showWaypoints(on: route)}}}}
import MapboxMapsimport MapboxNavigation /**Custom implementation of Navigation Camera transitions, which conforms to `CameraStateTransition`protocol. To be able to use custom camera transitions user has to create instance of `CustomCameraStateTransition`and then override with it default implementation, by modifying`NavigationMapView.NavigationCamera.CameraStateTransition` or`NavigationViewController.NavigationMapView.NavigationCamera.CameraStateTransition` properties. By default Navigation SDK for iOS provides default implementation of `CameraStateTransition`in `NavigationCameraStateTransition`.*/class CustomCameraStateTransition: CameraStateTransition { weak var mapView: MapView? required init(_ mapView: MapView) {self.mapView = mapView} func transitionToFollowing(_ cameraOptions: CameraOptions, completion: @escaping (() -> Void)) {mapView?.camera.ease(to: cameraOptions, duration: 0.5, curve: .linear, completion: { _ incompletion()})} func transitionToOverview(_ cameraOptions: CameraOptions, completion: @escaping (() -> Void)) {mapView?.camera.ease(to: cameraOptions, duration: 0.5, curve: .linear, completion: { _ incompletion()})} func update(to cameraOptions: CameraOptions, state: NavigationCameraState) {mapView?.camera.ease(to: cameraOptions, duration: 0.5, curve: .linear, completion: nil)} func cancelPendingTransition() {mapView?.camera.cancelAnimations()}}
import MapboxMapsimport MapboxNavigationimport MapboxCoreNavigation /**Custom implementation of Navigation Camera data source, which is used to fill and store`CameraOptions` which will be later used by `CustomCameraStateTransition` for execution oftransitions and continuous camera updates. To be able to use custom camera data source user has to create instance of `CustomCameraStateTransition`and then override with it default implementation, by modifying`NavigationMapView.NavigationCamera.ViewportDataSource` or`NavigationViewController.NavigationMapView.NavigationCamera.ViewportDataSource` properties. By default Navigation SDK for iOS provides default implementation of `ViewportDataSource`in `NavigationViewportDataSource`.*/class CustomViewportDataSource: ViewportDataSource { public weak var delegate: ViewportDataSourceDelegate? public var followingMobileCamera: CameraOptions = CameraOptions() public var followingCarPlayCamera: CameraOptions = CameraOptions() public var overviewMobileCamera: CameraOptions = CameraOptions() public var overviewCarPlayCamera: CameraOptions = CameraOptions() weak var mapView: MapView? // MARK: - Initializer methods public required init(_ mapView: MapView) {self.mapView = mapViewself.mapView?.location.addLocationConsumer(newConsumer: self) subscribeForNotifications()} deinit {unsubscribeFromNotifications()} // MARK: - Notifications observer methods func subscribeForNotifications() {// `CustomViewportDataSource` uses raw locations provided by `LocationConsumer` in// free-drive mode and locations snapped to the road provided by// `Notification.Name.routeControllerProgressDidChange` notification.NotificationCenter.default.addObserver(self,selector: #selector(progressDidChange(_:)),name: .routeControllerProgressDidChange,object: nil)} func unsubscribeFromNotifications() {NotificationCenter.default.removeObserver(self,name: .routeControllerProgressDidChange,object: nil)} @objc func progressDidChange(_ notification: NSNotification) {let location = notification.userInfo?[RouteController.NotificationUserInfoKey.locationKey] as? CLLocationlet routeProgress = notification.userInfo?[RouteController.NotificationUserInfoKey.routeProgressKey] as? RouteProgresslet cameraOptions = self.cameraOptions(location, routeProgress: routeProgress) delegate?.viewportDataSource(self, didUpdate: cameraOptions)} func cameraOptions(_ location: CLLocation?, routeProgress: RouteProgress? = nil) -> [String: CameraOptions] {followingMobileCamera.center = location?.coordinate// Set the bearing of the `MapView` (measured in degrees clockwise from true north).followingMobileCamera.bearing = location?.coursefollowingMobileCamera.padding = .zerofollowingMobileCamera.zoom = 15.0followingMobileCamera.pitch = 45.0 if let shape = routeProgress?.route.shape,let camera = mapView?.mapboxMap.camera(for: .lineString(shape),padding: UIEdgeInsets(top: 150.0, left: 10.0, bottom: 150.0, right: 10.0),bearing: 0.0,pitch: 0.0) {overviewMobileCamera = camera} let cameraOptions = [CameraOptions.followingMobileCamera: followingMobileCamera,CameraOptions.overviewMobileCamera: overviewMobileCamera] return cameraOptions}} // MARK: - LocationConsumer delegate extension CustomViewportDataSource: LocationConsumer { var shouldTrackLocation: Bool {return true} func locationUpdate(newLocation: Location) {let location = CLLocation(coordinate: newLocation.coordinate,altitude: 0.0,horizontalAccuracy: newLocation.horizontalAccuracy,verticalAccuracy: 0.0,course: newLocation.course,speed: 0.0,timestamp: Date()) let cameraOptions = self.cameraOptions(location)delegate?.viewportDataSource(self, didUpdate: cameraOptions)}}