Modify the navigation UI

Using the Navigation SDK for iOS, you can modify the user experience with your map by determining which of the built-in UI controls and elements appear on the map, and which gestures you allow. You can also modify the visual appearance of the Navigation UI. Refer to the Policies page for guidelines on acceptable modifications to the Navigation UI.

Map UI controls

The Navigation SDK provides some built-in UI controls similar to those found in the Google Maps for iOS application. You can toggle the visibility of these controls using the GMSUISettings class. Changes you make on this class reflect immediately on the map.

Compass

The Navigation SDK provides a compass graphic which appears in the top right corner of the map under certain circumstances and only when enabled. The compass only appears when the camera is oriented such that it has a bearing other than exact north (a non-zero bearing). When the user clicks on the compass, the camera animates back to a position with a bearing of zero (the default orientation) and the compass fades away shortly after.

If navigation is enabled and the camera mode is set to "following," the compass remains visible and tapping the compass toggles between tilted and overview camera perspectives.

The compass is disabled by default. You can enable the compass by setting the compassButton property of GMSUISettings to true. However, you cannot force the compass to always be shown.

Swift

mapView.settings.compassButton = true

Objective-C

mapView.settings.compassButton = YES;

My Location button

The My Location button appears in the bottom right corner of the screen only when the My Location button is enabled. When a user clicks the button, the camera animates to focus on the user's current location if the user's location is currently known. You can enable the button by setting the myLocationButton property of GMSUISettings to true.

Swift

mapView.settings.myLocationButton = true

Objective-C

mapView.settings.myLocationButton = YES;

Recenter button

When navigation is enabled, the recenter button appears when the user scrolls the map view, and disappears when the user taps to re-center the map. To allow the recenter button to appear, set the recenterButtonEnabled property of GMSUISettings to true. To prevent the recenter button from appearing, set recenterButtonEnabled to false.

Swift

mapView.settings.isRecenterButtonEnabled = true

Objective-C

mapView.settings.recenterButtonEnabled = YES;

Map UI accessories

The Navigation SDK provides UI accessories that appear during navigation similar to those found in the Google Maps for iOS application. You can adjust the visibility or visual appearance of these controls as described in this section. Changes you make here reflect during the user's next trip.

During navigation, the navigation header appears at the top of the screen and the navigation footer appears at the bottom. The navigation header shows the street name and direction for the next turn on the route, as well as the direction of the following turn. The navigation footer shows the estimated time and distance to the destination, as well as the estimated time of arrival.

You can toggle the visibility of the navigation header and footer and set its colors programmatically using the following properties:

  • navigationHeaderEnabled — controls whether the navigation header is visible (default is true).
  • navigationFooterEnabled — controls whether the navigation footer is visible (default is true).
  • navigationHeaderPrimaryBackgroundColor — sets the primary background color for the navigation header.
  • navigationHeaderSecondaryBackgroundColor — sets the secondary background color for the navigation header.

The following code example shows turning on visibility for the header and footer, then setting navigationHeaderPrimaryBackgroundColor to blue, and navigationHeaderSecondaryBackgroundColor to red.

Swift

mapView.settings.isNavigationHeaderEnabled = true
mapView.settings.isNavigationFooterEnabled = true
mapView.settings.navigationHeaderPrimaryBackgroundColor = .blue
mapView.settings.navigationHeaderSecondaryBackgroundColor = .red

Objective-C

mapView.settings.navigationHeaderEnabled = YES;
mapView.settings.navigationFooterEnabled = YES;
mapView.settings.navigationHeaderPrimaryBackgroundColor = [UIColor blueColor];
mapView.settings.navigationHeaderSecondaryBackgroundColor = [UIColor redColor];

You can customize your app by replacing the secondary navigation header view with your own customized accessory view. You do it by creating a view that implements the GMSNavigationAccessoryView protocol. This protocol has one required method: -heightForAccessoryViewConstrainedToSize:onMapView:. You are given the maximum available size for your view on the given mapView, and you must provide the height that your view requires.

You can then pass this view to the mapView by calling setHeaderAccessoryView: The mapView animates-out any current views, and then animates-in your custom view. The navigation header must be visible so that your custom view can display.

To remove the custom header accessory view, pass nil to setHeaderAccessoryView:.

If your view must change size at any time, then you can call invalidateLayoutForAccessoryView:, passing in the view that needs to change size.

Example

The following code example demonstrates a custom view that implements the GMSNavigationAccessoryView protocol. This custom view is then used to set a custom navigation header accessory view.

Swift

class MyCustomView: UIView, GMSNavigationAccessoryView {

  func heightForAccessoryViewConstrained(to size: CGSize, on mapView: GMSMapView) -> CGFloat {
    // viewHeight gets calculated as the height your view needs.
    return viewHeight
  }

}

let customView = MyCustomView(...)
mapView.setHeaderAccessory(customView)

// At some later point customView changes size.
mapView.invalidateLayout(forAccessoryView: customView)

// Remove the custom header accessory view.
mapView.setHeaderAccessory(nil)

Objective-C

@interface MyCustomView : UIView <GMSNavigationAccessoryView>

@end

@implementation MyCustomView

- (CGFloat)heightForAccessoryViewConstrainedToSize:(CGSize)size onMapView:(GMSMapView *)mapView {
  // viewHeight gets calculated as the height your view needs.
  return viewHeight;
}

@end

MyCustomView *customView = [[MyCustomView alloc] init];
[_mapView setHeaderAccessoryView:customView];

// At some later point customView changes size.
[_mapView invalidateLayoutForAccessoryView:customView];

// Remove the custom header accessory view.
[_mapView setHeaderAccessoryView:nil];

Directions List

You can provide step-by-step directions in your app. The following example shows one possible way to do this. These steps may vary depending on your own implementation.

  1. Enable an entry point button after setDestinations on the GMSNavigator (navigator) has completed successfully and guidanceActive on the navigator has been enabled.
  2. Upon a user tapping on the entry point button, create a GMSNavigationDirectionsListController (controller) with the navigator associated with the GMSMapView (mapView).
  3. Add the controller to an instance of UIViewController (view controller), and add the directionsListView as a subview of the view controller. The reloadData and invalidateLayout methods on the controller should be called as one would with a UICollectionView.
  4. Push the view controller onto the app's view controller hierarchy.

The following code example shows adding a DirectionsListViewController.

Swift

override func viewDidLoad() {
  super.viewDidLoad()
  // Add the directionsListView to the host view controller's view.
  let directionsListView = directionsListController.directionsListView
  directionsListView.frame = self.view.frame
  self.view.addSubview(directionsListView)
  directionsListView.translatesAutoresizingMaskIntoConstraints = false
  directionsListView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
  directionsListView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
  directionsListView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
  directionsListView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
  ...
}

override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated)
  // Ensure data is fresh when the view appears.
  directionsListController.reloadData()
  ...
}

override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
  super.willTransition(to: newCollection, with: coordinator)
  // Invalidate the layout during rotation.
  coordinator.animate(alongsideTransition: {_ in
    self.directionsListController.invalidateLayout()
  })
  ...
}

Objective-C

- (void)viewDidLoad {
  [super viewDidLoad];
  // Add the directionsListView to the host view controller's view.
  UIView *directionsListView = _directionsListController.directionsListView;
  directionsListView.frame = self.view.bounds;
  [self.view addSubview:directionsListView];
  directionsListView.translatesAutoresizingMaskIntoConstraints = NO;
  [directionsListView.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES;
  [directionsListView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
  [directionsListView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES;
  [directionsListView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
  ...
}

- (void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
  // Ensure data is fresh when the view appears.
  [_directionsListController reloadData];
  ...
}

- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection
              withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
  [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
  void(^animationBlock)(id <UIViewControllerTransitionCoordinatorContext>context) =
      ^void(id <UIViewControllerTransitionCoordinatorContext>context) {
    [_directionsListController invalidateLayout];
  };
  // Invalidate the layout during rotation.
  [coordinator animateAlongsideTransition:animationBlock
                               completion:nil];
  ...
}

...

Trip progress bar

The trip progress bar added to navigation.

The trip progress bar is a vertical bar that appears on the trailing right edge of the map when navigation starts. When enabled, it displays an overview for an entire trip, along with the user's destination and current position.

The provides users the ability to quickly anticipate any upcoming issues, such as traffic, without needing to zoom in. They can then reroute the trip if necessary. If the user reroutes the trip, the progress bar resets as if a new trip has started from that point.

The trip progress bar displays the following status indicators:

  • Traffic status—the status of upcoming traffic.

  • Current location—the driver's current location in the trip.

  • Route elapsed—the elapsed portion of the trip.

Enable the trip progress bar by setting the navigationTripProgressBarEnabled property in GMSUISettings.

Swift

mapView.settings.isNavigationTripProgressBarEnabled = true

Objective-C

mapView.settings.navigationTripProgressBarEnabled = YES;

Traffic lights and stop signs

Stop signs and traffic lights shown during navigation.

You can enable traffic lights and stop signs in the mapView. With this feature, the user can enable the display of traffic lights or stop sign icons along their route, providing better context for more efficient and accurate trips.

By default, traffic lights and stop signs are disabled in the Navigation SDK for iOS. To enable this feature, call the GMSMapView settings for each option independently: showsTrafficLights and showsStopSigns.


Swift

mapView.settings.showsTrafficLights = true
mapView.settings.showsStopSigns = true

Objective-C

mapView.settings.showsTrafficLights = YES;
mapView.settings.showsStopSigns = YES;

Speedometer control

When navigation is enabled and the travel mode is set to driving, the Navigation SDK for iOS displays a speed limit control in the lower corner of the map that shows the current speed limit. When the driver exceeds the speed limit, the control expands to display a second speedometer with the driver's current speed.

You can set alert levels to change the formatting of the speedometer display when the driver exceeds the speed limit by a specified amount. For example, you can specify that the current speed displays with a red text color when the driver exceeds the speed limit by 5 mph, and with a red background color when the driver exceeds the speed limit by 10 mph.

To display the speed limit control, set the shouldDisplaySpeedometer property of GMSUISettings to true. To disable the display of the speed limit control, set shouldDisplaySpeedometer to false.

Swift

mapView.shouldDisplaySpeedometer = true

Objective-C

mapView.shouldDisplaySpeedometer = YES;

For more information on setting alerts for the speedometer, see Configure speedometer alerts.

Destination markers

You can show or hide the destination markers for a given route by setting the showsDestinationMarkers property of GMSUISettings. The following example shows turning off the destination markers.

Swift

mapView.settings.showsDestinationMarkers = false

Objective-C

mapView.settings.showsDestinationMarkers = NO;

Map experience features

The Navigation SDK provides you the ability to make further customizations to the navigation experience for your users. Changes you make to your instance reflect during the user's next update of your app.

Disable default map gestures

You can disable the default gestures on the map by setting properties of the GMSUISettings class, which is available as a property of the GMSMapView. The following gestures can be enabled and disabled programmatically. Note that disabling the gesture will not limit programmatic access to the camera settings.

  • scrollGestures — controls whether scroll gestures are enabled or disabled. If enabled, users may swipe to pan the camera.
  • zoomGestures — controls whether zoom gestures are enabled or disabled. If enabled, users may double tap, two-finger tap, or pinch to zoom the camera. Note that double tapping or pinching when scrollGestures are enabled may pan the camera to the specified point.
  • tiltGestures — controls whether tilt gestures are enabled or disabled. If enabled, users may use a two-finger vertical down or up swipe to tilt the camera.
  • rotateGestures — controls whether rotate gestures are enabled or disabled. If enabled, users may use a two-finger rotate gesture to rotate the camera.

In this example, both pan and zoom gestures have been disabled.

Swift

mapView.settings.scrollGestures = false
mapView.settings.zoomGestures = false

Objective-C

mapView.settings.scrollGestures = NO;
mapView.settings.zoomGestures = NO;

Position controls and UI elements

You can position controls and other UI elements relative to the position of the navigation header and footer, by using the following properties:

  • navigationHeaderLayoutGuide
  • navigationFooterLayoutGuide

The following code example shows using the layout guides to position a pair of labels in the map view:

Swift

/* Add a label to the top left, positioned below the header. */
let topLabel = UILabel()
topLabel.text = "Top Left"
mapView.addSubview(topLabel)
topLabel.translatesAutoresizingMaskIntoConstraints = false
topLabel.topAnchor.constraint(equalTo: mapView.navigationHeaderLayoutGuide.bottomAnchor).isActive = true
topLabel.leadingAnchor.constraint(equalTo: mapView.leadingAnchor).isActive = true

/* Add a label to the bottom right, positioned above the footer. */
let bottomLabel = UILabel()
bottomLabel.text = "Bottom Right"
mapView.addSubview(bottomLabel)
bottomLabel.translatesAutoresizingMaskIntoConstraints = false
bottomLabel.bottomAnchor.constraint(equalTo: mapView.navigationFooterLayoutGuide.topAnchor).isActive = true
bottomLabel.trailingAnchor.constraint(equalTo: mapView.trailingAnchor).isActive = true

Objective-C

/* Add a label to the top left, positioned below the header. */
UILabel *topLabel = [[UILabel alloc] init];
topLabel.text = @"Top Left";
[view addSubview:topLabel];
topLabel.translatesAutoresizingMaskIntoConstraints = NO;
[topLabel.topAnchor
    constraintEqualToAnchor:mapView.navigationHeaderLayoutGuide.bottomAnchor].active = YES;
[topLabel.leadingAnchor constraintEqualToAnchor:mapView.leadingAnchor].active = YES;

/* Add a label to the bottom right, positioned above the footer. */
UILabel *bottomLabel = [[UILabel alloc] init];
bottomLabel.text = @"Bottom Right";
[view addSubview:bottomLabel];
bottomLabel.translatesAutoresizingMaskIntoConstraints = NO;
[bottomLabel.bottomAnchor
    constraintEqualToAnchor:mapView.navigationFooterLayoutGuide.topAnchor].active = YES;
[bottomLabel.trailingAnchor constraintEqualToAnchor:mapView.trailingAnchor].active = YES;

Hide alternate routes

When the user interface becomes cluttered with too much information, you can reduce clutter by displaying fewer alternate routes than the default (two), or by displaying no alternate routes at all. You can configure this option before you fetch the routes by configuring GMSNavigationRoutingOptions, and setting the alternateRoutesStrategy with one of the following enumeration values:

Enumeration ValueDescription
GMSNavigationAlternateRoutesStrategyAll Default. Displays up to two alternate routes.
GMSNavigationAlternateRoutesStrategyOne Displays one alternate route (if one is available).
GMSNavigationAlternateRoutesStrategyNone Hides alternate routes.

Example

The following code example demonstrates how to hide alternate routes altogether.

Swift

let routingOptions = GMSNavigationRoutingOptions(alternateRoutesStrategy: .none)
navigator?.setDestinations(destinations,
                           routingOptions: routingOptions) { routeStatus in
  ...
}

Objective-C

GMSNavigationRoutingOptions *routingOptions = [[GMSNavigationRoutingOptions alloc] initWithAlternateRoutesStrategy:GMSNavigationAlternateRoutesStrategyNone];
[navigator setDestinations:destinations
            routingOptions:routingOptions
                  callback:^(GMSRouteStatus routeStatus){...}];