Custom banners
Substitute the default top and bottom banners with custom view controllers.
/*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-banner*/ import Foundationimport UIKitimport MapboxCoreNavigationimport MapboxNavigationimport MapboxDirections class CustomBarsViewController: UIViewController {override func viewDidLoad() {super.viewDidLoad() let origin = CLLocationCoordinate2DMake(37.77440680146262, -122.43539772352648)let destination = CLLocationCoordinate2DMake(37.76556957793795, -122.42409811526268)let routeOptions = NavigationRouteOptions(coordinates: [origin, destination]) Directions.shared.calculate(routeOptions) { [weak self] (_, result) inswitch result {case .failure(let error):print(error.localizedDescription)case .success(let response):guard let strongSelf = self else {return} // For demonstration purposes, simulate locations if the Simulate Navigation option is on.let indexedRouteResponse = IndexedRouteResponse(routeResponse: response, routeIndex: 0)let navigationService = MapboxNavigationService(indexedRouteResponse: indexedRouteResponse,customRoutingProvider: NavigationSettings.shared.directions,credentials: NavigationSettings.shared.directions.credentials,simulating: simulationIsEnabled ? .always : .onPoorGPS) // Pass your custom implementations of `topBanner` and/or `bottomBanner` to `NavigationOptions`// If you do not specify them explicitly, `TopBannerViewController` and `BottomBannerViewController` will be used by default.// Those are `Open`, so you can also check thier source for more examples of using standard UI controls!let topBanner = CustomTopBarViewController()let bottomBanner = CustomBottomBarViewController()let navigationOptions = NavigationOptions(navigationService: navigationService,topBanner: topBanner,bottomBanner: bottomBanner)let navigationViewController = NavigationViewController(for: indexedRouteResponse,navigationOptions: navigationOptions)bottomBanner.navigationViewController = navigationViewController let parentSafeArea = navigationViewController.view.safeAreaLayoutGuidelet bannerHeight: CGFloat = 80.0let verticalOffset: CGFloat = 20.0let horizontalOffset: CGFloat = 10.0 // To change top and bottom banner size and position change layout constraints directly.topBanner.view.topAnchor.constraint(equalTo: parentSafeArea.topAnchor).isActive = true bottomBanner.view.heightAnchor.constraint(equalToConstant: bannerHeight).isActive = truebottomBanner.view.bottomAnchor.constraint(equalTo: parentSafeArea.bottomAnchor, constant: -verticalOffset).isActive = truebottomBanner.view.leadingAnchor.constraint(equalTo: parentSafeArea.leadingAnchor, constant: horizontalOffset).isActive = truebottomBanner.view.trailingAnchor.constraint(equalTo: parentSafeArea.trailingAnchor, constant: -horizontalOffset).isActive = true navigationViewController.modalPresentationStyle = .fullScreen strongSelf.present(navigationViewController, animated: true, completion: nil)navigationViewController.floatingButtons = []navigationViewController.showsSpeedLimits = false}}}} // MARK: - CustomTopBarViewController class CustomTopBarViewController: ContainerViewController {private lazy var instructionsBannerTopOffsetConstraint = {return instructionsBannerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10)}()private lazy var centerOffset: CGFloat = calculateCenterOffset(with: view.bounds.size)private lazy var instructionsBannerCenterOffsetConstraint = {return instructionsBannerView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0)}()private lazy var instructionsBannerWidthConstraint = {return instructionsBannerView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9)}() // You can Include one of the existing Views to display route-specific infolazy var instructionsBannerView: InstructionsBannerView = {let banner = InstructionsBannerView()banner.translatesAutoresizingMaskIntoConstraints = falsebanner.heightAnchor.constraint(equalToConstant: 100.0).isActive = truebanner.layer.cornerRadius = 25banner.layer.opacity = 0.75banner.separatorView.isHidden = truereturn banner}() override func viewDidLoad() {view.addSubview(instructionsBannerView) setupConstraints()} override func viewWillAppear(_ animated: Bool) {super.viewWillAppear(animated) updateConstraints()} private func setupConstraints() {instructionsBannerCenterOffsetConstraint.isActive = trueinstructionsBannerTopOffsetConstraint.isActive = trueinstructionsBannerWidthConstraint.isActive = true} private func updateConstraints() {instructionsBannerCenterOffsetConstraint.constant = centerOffset} // MARK: - Device rotation private func calculateCenterOffset(with size: CGSize) -> CGFloat {return (size.height < size.width ? -size.width / 5 : 0)} override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {super.viewWillTransition(to: size, with: coordinator)centerOffset = calculateCenterOffset(with: size)} open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {super.traitCollectionDidChange(previousTraitCollection)updateConstraints()} // MARK: - NavigationServiceDelegate implementation public func navigationService(_ service: NavigationService, didUpdate progress: RouteProgress, with location: CLLocation, rawLocation: CLLocation) {// pass updated data to sub-views which also implement `NavigationServiceDelegate`instructionsBannerView.updateDistance(for: progress.currentLegProgress.currentStepProgress)} public func navigationService(_ service: NavigationService, didPassVisualInstructionPoint instruction: VisualInstructionBanner, routeProgress: RouteProgress) {instructionsBannerView.update(for: instruction)} public func navigationService(_ service: NavigationService, didRerouteAlong route: Route, at location: CLLocation?, proactive: Bool) {instructionsBannerView.updateDistance(for: service.routeProgress.currentLegProgress.currentStepProgress)}} // MARK: - CustomBottomBarViewController class CustomBottomBarViewController: ContainerViewController, CustomBottomBannerViewDelegate { weak var navigationViewController: NavigationViewController? // Or you can implement your own UI elementslazy var bannerView: CustomBottomBannerView = {let banner = CustomBottomBannerView()banner.translatesAutoresizingMaskIntoConstraints = falsebanner.delegate = selfreturn banner}() override func loadView() {super.loadView() view.addSubview(bannerView) let safeArea = view.layoutMarginsGuideNSLayoutConstraint.activate([bannerView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),bannerView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor),bannerView.heightAnchor.constraint(equalTo: view.heightAnchor),bannerView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor)])} override func viewWillAppear(_ animated: Bool) {super.viewWillAppear(animated)setupConstraints()} private func setupConstraints() {if let superview = view.superview?.superview {view.bottomAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.bottomAnchor).isActive = true}} // MARK: - NavigationServiceDelegate implementation func navigationService(_ service: NavigationService, didUpdate progress: RouteProgress, with location: CLLocation, rawLocation: CLLocation) {// Update your controls manuallybannerView.progress = Float(progress.fractionTraveled)bannerView.eta = "~\(Int(round(progress.durationRemaining / 60))) min"} // MARK: - CustomBottomBannerViewDelegate implementation func customBottomBannerDidCancel(_ banner: CustomBottomBannerView) {navigationViewController?.dismiss(animated: true,completion: nil)}}
import UIKitimport MapboxNavigation protocol CustomBottomBannerViewDelegate: AnyObject {func customBottomBannerDidCancel(_ banner: CustomBottomBannerView)} class CustomBottomBannerView: UIView { @IBOutlet var contentView: UIView!@IBOutlet weak var etaLabel: UILabel!@IBOutlet weak var progressBar: UIProgressView!@IBOutlet weak var cancelButton: UIButton! var progress: Float {get {return progressBar.progress}set {progressBar.setProgress(newValue, animated: false)}} var eta: String? {get {return etaLabel.text}set {etaLabel.text = newValue}} weak var delegate: CustomBottomBannerViewDelegate? private func initFromNib() {Bundle.main.loadNibNamed(String(describing: CustomBottomBannerView.self),owner: self,options: nil)addSubview(contentView)contentView.frame = boundscontentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] progressBar.progressTintColor = .systemGreenprogressBar.layer.borderColor = UIColor.black.cgColorprogressBar.layer.borderWidth = 2progressBar.layer.cornerRadius = 5 cancelButton.backgroundColor = .systemGraycancelButton.layer.cornerRadius = 5cancelButton.setTitleColor(.darkGray, for: .highlighted) backgroundColor = UIColor.black.withAlphaComponent(0.3)layer.cornerRadius = 10} override init(frame: CGRect) {super.init(frame: frame)initFromNib()} required init?(coder: NSCoder) {super.init(coder: coder)initFromNib()} @IBAction func onCancel(_ sender: Any) {delegate?.customBottomBannerDidCancel(self)}}