Esta seção descreve como usar o SDK Navigation com a biblioteca Apple CarPlay para mostrar a experiência de navegação do seu app em centrais multimídia. Se o sistema no painel de um motorista for compatível com o CarPlay, ele poderá usar seu app diretamente na tela do carro conectando o smartphone à unidade. A orientação por voz também é reproduzida nos alto-falantes do carro.
Você cria o app para CarPlay usando um conjunto de modelos de interface fornecidos pela Apple. Seu app é responsável por selecionar o modelo a ser mostrado e por fornecer os dados dentro dele.
O sistema no painel mostra os elementos interativos aprovados para segurança para que o motorista possa navegar até o destino com segurança, sem distrações indevidas. Você também pode programar o app para que o motorista possa interagir com recursos específicos do app, como aceitar ou rejeitar pedidos ou ver a localização do cliente em um mapa. As atualizações de status do pedido também podem ser programadas para aparecer na unidade do painel.

Configuração
Começar com o CarPlay
Primeiro, familiarize-se com a documentação da Apple:
Configurar o SDK Navigation
- Depois de ler a documentação da Apple, você poderá trabalhar com o SDK Navigation.
- Configure seu projeto se você ainda não tiver integrado o SDK Navigation ao seu app.
- Ative o feed de orientação TurnByTurn para seu app.
- Opcional. Use ícones gerados do SDK Navigation.
- Desenhe o mapa usando a classe
GMSMapView
fornecida na classe UIView. Consulte Navegar por um trajeto para mais informações. PreenchaCPNavigationSession
com os dados da biblioteca TurnByTurn.
Desenhar a interface do mapa e da navegação
A classe
GMSMapView
renderiza um mapa, e a
CPMapTemplate
renderiza a interface nas telas do CarPlay. Ele oferece grande parte da mesma funcionalidade do
GMSMapView
para smartphones, mas com interatividade limitada.
Swift
init(window: CPWindow) {
super.init(nibName: nil, bundle: nil)
self.window = window
// More CPMapTemplate initialization
}
override func viewDidLoad() {
super.viewDidLoad()
let mapViewOptions = GMSMapViewOptions()
mapViewOptions.screen = window.screen
mapViewOptions.frame = self.view.bounds
mapView = GMSMapView(options: mapViewOptions)
mapView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
mapView.settings.isNavigationHeaderEnabled = false
mapView.settings.isNavigationFooterEnabled = false
// Disable buttons: in CarPlay, no part of the map is clickable.
// The app should instead place these buttons in the appropriate slots of the CarPlay template.
mapView.settings.compassButton = false
mapView.settings.isRecenterButtonEnabled = false
mapView.shouldDisplaySpeedometer = false
mapView.isMyLocationEnabled = true
self.view.addSubview(mapView)
}
Objective-C
- (instancetype)initWithWindow:(CPWindow *)window {
self = [super initWithNibName:nil bundle:nil];
if (self) {
_window = window;
// More CPMapTemplate initialization
}
}
- (void)viewDidLoad {
[super viewDidLoad];
GMSMapViewOptions *options = [[GMSMapViewOptions alloc] init];
options.screen = _window.screen;
options.frame = self.view.bounds;
_mapView = [[GMSMapView alloc] initWithOptions:options];
_mapView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
_mapView.settings.navigationHeaderEnabled = NO;
_mapView.settings.navigationFooterEnabled = NO;
// Disable buttons: in CarPlay, no part of the map is clickable.
// The app should instead place these buttons in the appropriate slots of the CarPlay template.
_mapView.settings.compassButton = NO;
_mapView.settings.recenterButtonEnabled = NO;
_mapView.shouldDisplaySpeedometer = NO;
_mapView.myLocationEnabled = YES;
[self.view addSubview:_mapView];
}
Ativar a interação com o mapa
Para garantir a segurança do motorista, o CarPlay limita a interação com a superfície da tela a uma série
de
métodos
CPMapTemplateDelegate
. Use esses callbacks para oferecer suporte à interação limitada do motorista com o mapa
em uma tela no painel.
Para oferecer suporte a outras ações do usuário, crie uma matriz de CPMapButton
e atribua-a a CPMapTemplate.mapButtons
.
O código a seguir cria interações de movimento e botões para mover, aumentar e diminuir o zoom e fornecer a localização do usuário.
Interações de movimento panorâmico
Swift
// MARK: CPMapTemplateDelegate
func mapTemplate(_ mapTemplate: CPMapTemplate, panBeganWith direction: CPMapTemplate.PanDirection) {
}
func mapTemplate(_ mapTemplate: CPMapTemplate, panWith direction: CPMapTemplate.PanDirection) {
let scrollAmount = scrollAmount(for: direction)
let scroll = GMSCameraUpdate.scrollBy(x: scrollAmount.x, y: scrollAmount.y)
mapView.animate(with: scroll)
}
func mapTemplate(_ mapTemplate: CPMapTemplate, panEndedWith direction: CPMapTemplate.PanDirection) {
}
func scrollAmount(for panDirection: CPMapTemplate.PanDirection) -> CGPoint {
let scrollDistance = 80.0
var scrollAmount = CGPoint(x: 0, y: 0)
switch panDirection {
case .left:
scrollAmount.x -= scrollDistance
break;
case .right:
scrollAmount.x += scrollDistance
break;
case .up:
scrollAmount.y += scrollDistance
break;
case .down:
scrollAmount.y -= scrollDistance
break;
default:
break;
}
if scrollAmount.x != 0 && scrollAmount.y != 0 {
// Adjust length if scrolling diagonally.
scrollAmount = CGPointMake(scrollAmount.x * sqrt(1.0/2.0), scrollAmount.y * sqrt(1.0/2.0))
}
return scrollAmount
}
Objective-C
#pragma mark - CPMapTemplateDelegate
- (void)mapTemplate:(CPMapTemplate *)mapTemplate panBeganWithDirection:(CPPanDirection)direction {
}
- (void)mapTemplate:(CPMapTemplate *)mapTemplate panWithDirection:(CPPanDirection)direction {
CGPoint scrollAmount = [self scrollAmountForPanDirection:direction];
GMSCameraUpdate *scroll = [GMSCameraUpdate scrollByX:scrollAmount.x Y:scrollAmount.y];
[_mapView animateWithCameraUpdate:scroll];
}
- (void)mapTemplate:(CPMapTemplate *)mapTemplate panEndedWithDirection:(CPPanDirection)direction {
}
- (CGPoint)scrollAmountForPanDirection:(CPPanDirection)direction {
static const CGFloat scrollDistance = 80.;
CGPoint scrollAmount = {0., 0.};
if (direction & CPPanDirectionLeft) {
scrollAmount.x = -scrollDistance;
}
if (direction & CPPanDirectionRight) {
scrollAmount.x = scrollDistance;
}
if (direction & CPPanDirectionUp) {
scrollAmount.y = -scrollDistance;
}
if (direction & CPPanDirectionDown) {
scrollAmount.y = scrollDistance;
}
if (scrollAmount.x != 0 && scrollAmount.y != 0) {
// Adjust length if scrolling diagonally.
scrollAmount =
CGPointMake(scrollAmount.x * (CGFloat)M_SQRT1_2, scrollAmount.y * (CGFloat)M_SQRT1_2);
}
return scrollAmount;
}
Usos comuns de botões
Swift
// MARK: Create Buttons
func createMapButtons() -> [CPMapButton] {
let panButton = mapButton(systemImageName: "dpad.fill") { [weak self] in
self?.didTapPanButton()
}
let zoomOutButton = mapButton(systemImageName: "minus.magnifyingglass") { [weak self] in
self?.didTapZoomOutButton()
}
let zoomInButton = mapButton(systemImageName: "plus.magnifyingglass") { [weak self] in
self?.didTapZoomInButton()
}
let myLocationButton = mapButton(systemImageName: "location") { [weak self] in
self?.didTapMyLocationButton()
}
let mapButtons = [panButton, zoomOutButton, zoomInButton, myLocationButton]
return mapButtons
}
func mapButton(systemImageName: String, handler: @escaping () -> Void) -> CPMapButton {
}
// MARK: Button callbacks
@objc func didTapPanButton() {
mapTemplate?.showPanningInterface(animated: true)
}
@objc func didTapZoomOutButton() {
mapView.animate(with: GMSCameraUpdate.zoomOut())
}
@objc func didTapZoomInButton() {
mapView.animate(with: GMSCameraUpdate.zoomIn())
}
@objc func didTapMyLocationButton() {
if let lastLocation = lastLocation {
let cameraPosition = GMSCameraPosition(target: lastLocation.coordinate, zoom: 15)
mapView.animate(to: cameraPosition)
}
}
Objective-C
#pragma mark - Create Buttons
- (NSArray<CPMapButton *>*)createMapButtons {
NSMutableArray<CPMapButton *> *mapButtons = [NSMutableArray<CPMapButton *> array];
__weak __typeof__(self) weakSelf = self;
CPMapButton *panButton = [self mapButtonWithSystemImageNamed:@"dpad.fill"
handler:^(CPMapButton *_) {
[weakSelf didTapPanButton];
}];
[mapButtons addObject:panButton];
CPMapButton *zoomOutButton =
[self mapButtonWithSystemImageNamed:@"minus.magnifyingglass"
handler:^(CPMapButton *_Nonnull mapButon) {
[weakSelf didTapZoomOutButton];
}];
[mapButtons addObject:zoomOutButton];
CPMapButton *zoomInButton =
[self mapButtonWithSystemImageNamed:@"plus.magnifyingglass"
handler:^(CPMapButton *_Nonnull mapButon) {
[weakSelf didTapZoomInButton];
}];
[mapButtons addObject:zoomInButton];
CPMapButton *myLocationButton =
[self mapButtonWithSystemImageNamed:@"location"
handler:^(CPMapButton *_Nonnull mapButton) {
[weakSelf didTapMyLocationButton];
}];
[mapButtons addObject:myLocationButton];
return mapButtons;
}
#pragma mark - Button Callbacks
- (void)didTapZoomOutButton {
[_mapView animateWithCameraUpdate:[GMSCameraUpdate zoomOut]];
}
- (void)didTapZoomInButton {
[_mapView animateWithCameraUpdate:[GMSCameraUpdate zoomIn]];
}
- (void)didTapMyLocationButton {
CLLocation *location = self.lastLocation;
if (location) {
GMSCameraPosition *position =
[[GMSCameraPosition alloc] initWithTarget:self.lastLocation.coordinate zoom:15.];
[_mapView animateToCameraPosition:position];
}
}
- (void)didTapPanButton {
[_mapTemplate showPanningInterfaceAnimated:YES];
_isPanningInterfaceEnabled = YES;
}
- (void)didTapStopPanningButton {
[_mapTemplate dismissPanningInterfaceAnimated:YES];
_isPanningInterfaceEnabled = NO;
}
Observação:não é possível selecionar rotas alternativas na tela do CarPlay. Eles precisam ser selecionados no smartphone antes do início do CarPlay.
Mostrar rotas de navegação
Esta seção mostra como configurar um listener para um feed de dados e preencher as instruções de navegação nos painéis de orientação e estimativa de viagem. Consulte a seção "Criar um app de navegação para CarPlay" do Guia de programação de apps para CarPlay para mais informações.
Os painéis de orientação e estimativa de viagem fornecem um card de navegação que mostra informações relacionadas à viagem atual. A biblioteca TurnByTurn no SDK Navigation pode ajudar a fornecer algumas dessas informações, como símbolo, texto e tempo restante.
Configurar um listener
Siga as instruções para configurar um listener de eventos em Detalhes sobre o feed de dados de navegação trecho a trecho.
Preencher informações de navegação
A primeira parte do exemplo de código a seguir mostra como criar estimativas de viagem do CarPlay traduzindo GMSNavigationNavInfo.timeToCurrentStepSeconds
para CPTravelEstimate
. Leia mais sobre esses e outros elementos de exibição em Detalhes sobre o feed de dados de navegação
detalhada.
A segunda parte do exemplo mostra como criar um objeto e armazená-lo no campo
userInfo
de CPManuevers
. Isso determina o CPManeuverDisplayStyle
, que também é usado para informações de orientação de faixa. Para mais informações, consulte o Guia de programação de apps do CarPlay da Apple (em inglês).
Swift
// Get a CPTravelEstimate from GMSNavigationNavInfo
func getTravelEstimates(from navInfo:GMSNavigationNavInfo) -> CPTravelEstimates {
let distanceRemaining = navInfo.roundedDistance(navInfo.distanceToCurrentStepMeters)
let timeRemaining = navInfo.roundedTime(navInfo.timeToCurrentStepSeconds)
let travelEstimates = CPTravelEstimates(distanceRemaining: distanceRemaining, timeRemaining: timeRemaining)
return travelEstimates
}
// Create an object to be stored in the userInfo field of CPManeuver to determine the CPManeuverDisplayStyle.
/** An object to be stored in the userInfo field of a CPManeuver. */
struct ManeuverUserInfo {
var stepInfo: GMSNavigationStepInfo
var isLaneGuidance: Bool
}
func mapTemplate(_ mapTemplate: CPMapTemplate, displayStyleFor maneuver: CPManeuver) -> CPManeuverDisplayStyle {
let userInfo = maneuver.userInfo
if let maneuverUserInfo = userInfo as? ManeuverUserInfo {
return maneuverUserInfo.isLaneGuidance ? .symbolOnly : .leadingSymbol
}
return .leadingSymbol
}
// Get a CPManeuver with instructionVariants and symbolImage from GMSNavigationStepInfo
func getManeuver(for stepInfo: GMSNavigationStepInfo) -> CPManeuver {
let maneuver = CPManeuver()
maneuver.userInfo = ManeuverUserInfo(stepInfo: stepInfo, isLaneGuidance: false)
switch stepInfo.maneuver {
case .destination:
maneuver.instructionVariants = ["Your destination is ahead."]
break
case .destinationLeft:
maneuver.instructionVariants = ["Your destination is ahead on your left."]
break
case .destinationRight:
maneuver.instructionVariants = ["Your destination is ahead on your right."]
break
default:
maneuver.attributedInstructionVariants = currentNavInfo?.instructions(forStep: stepInfo, options: instructionOptions)
break
}
maneuver.symbolImage = stepInfo.maneuverImage(with: instructionOptions.imageOptions)
return maneuver
}
// Get the lane image for a CPManeuver from GMSNavigationStepInfo
func laneGuidanceManeuver(for stepInfo: GMSNavigationStepInfo) -> CPManeuver? {
let maneuver = CPManeuver()
maneuver.userInfo = ManeuverUserInfo(stepInfo: stepInfo, isLaneGuidance: true)
let lanesImage = stepInfo.lanesImage(with: imageOptions)
guard let lanesImage = lanesImage else { return nil }
maneuver.symbolImage = lanesImage
return maneuver
}
Objective-C
// Get a CPTravelEstimate from GMSNavigationNavInfo
- (nonull CPTravelEstimates *)travelEstimates:(GMSNavigationNavInfo *_Nonnull navInfo) {
NSMeasurement<NSUnitLength *> *distanceRemaining = [navInfo roundedDistance:navInfo.distanceToCurrentStepMeters];
NSTimeInterval timeRemaining = [navInfo roundedTime:navInfo.timeToCurrentStepSeconds];
CPTravelEstimate* travelEstimate = [[CPTravelEstimates alloc] initWithDistanceRemaining:distanceRemaining
timeRemaining:timeRemaining];
}
// Create an object to be stored in the userInfo field of CPManeuver to determine the CPManeuverDisplayStyle.
/** An object to be stored in the userInfo field of a CPManeuver. */
@interface ManeuverUserInfo : NSObject
@property(nonatomic, readonly, nonnull) GMSNavigationStepInfo *stepInfo;
@property(nonatomic, readonly, getter=isLaneGuidance) BOOL laneGuidance;
- (nonnull instancetype)initWithStepInfo:(GMSNavigationStepInfo *)stepInfo
isLaneGuidance:(BOOL)isLaneGuidance NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@end
- (CPManeuverDisplayStyle)mapTemplate:(CPMapTemplate *)mapTemplate
displayStyleForManeuver:(nonnull CPManeuver *)maneuver {
ManeuverUserInfo *userInfo = maneuver.userInfo;
return userInfo.laneGuidance ? CPManeuverDisplayStyleSymbolOnly : CPManeuverDisplayStyleDefault;
}
// Get a CPManeuver with instructionVariants and symbolImage from GMSNavigationStepInfo
- (nonnull CPManeuver *)maneuverForStep:(nonnull GMSNavigationStepInfo *)stepInfo {
CPManeuver *maneuver = [[CPManeuver alloc] init];
maneuver.userInfo = [[ManeuverUserInfo alloc] initWithStepInfo:stepInfo isLaneGuidance:NO];
switch (stepInfo.maneuver) {
case GMSNavigationManeuverDestination:
maneuver.instructionVariants = @[ @"Your destination is ahead." ];
break;
case GMSNavigationManeuverDestinationLeft:
maneuver.instructionVariants = @[ @"Your destination is ahead on your left." ];
break;
case GMSNavigationManeuverDestinationRight:
maneuver.instructionVariants = @[ @"Your destination is ahead on your right." ];
break;
default: {
maneuver.attributedInstructionVariants =
[_currentNavInfo instructionsForStep:stepInfo options:_instructionOptions];
break;
}
}
maneuver.symbolImage = [stepInfo maneuverImageWithOptions:_instructionOptions.imageOptions];
return maneuver;
}
// Get the lane image for a CPManeuver from GMSNavigationStepInfo
- (nullable CPManeuver *)laneGuidanceManeuverForStep:(nonnull GMSNavigationStepInfo *)stepInfo {
CPManeuver *maneuver = [[CPManeuver alloc] init];
maneuver.userInfo = [[ManeuverUserInfo alloc] initWithStepInfo:stepInfo isLaneGuidance:YES];
UIImage *lanesImage = [stepInfo lanesImageWithOptions:_imageOptions];
if (!lanesImage) {
return nil;
}
maneuver.symbolImage = lanesImage;
return maneuver;
}
Manobras
O CarPlay usa a classe CPManeuver
para fornecer orientação curva a curva. Consulte Detalhes sobre o feed de dados de navegação
detalhada para mais informações
sobre manobras e orientação de faixa.
Documentação relacionada
- https://developer.apple.com/carplay/
- https://developer.apple.com/carplay/documentation/CarPlay-App-Programming-Guide.pdf
- https://developer.apple.com/design/human-interface-guidelines/carplay
- Detalhes sobre o feed de dados de navegação ponto a ponto
- Navegar por um trajeto