Questa sezione descrive come utilizzare l'SDK Navigation con la libreria Apple CarPlay per visualizzare l'esperienza di navigazione della tua app sulle unità di testa integrate nel cruscotto. Se il sistema di bordo di un conducente supporta CarPlay, i conducenti possono utilizzare la tua app direttamente sul display dell'auto collegando il telefono all'unità. La guida vocale viene riprodotta anche sugli altoparlanti dell'auto.
Puoi creare la tua app per CarPlay da un insieme di modelli di UI forniti da Apple. È compito della tua app selezionare il modello da mostrare e fornire i dati al suo interno.
Il sistema nella dashboard mostra gli elementi interattivi approvati per la sicurezza in modo che il conducente possa raggiungere in sicurezza la sua destinazione senza distrazioni eccessive. Puoi anche programmare l'app in modo che il conducente possa interagire con le funzionalità specifiche dell'app, ad esempio accettare o rifiutare gli ordini o visualizzare la posizione del cliente su una mappa. Gli aggiornamenti dello stato degli ordini possono anche essere programmati per essere visualizzati nell'unità della dashboard.
Configurazione
Iniziare con CarPlay
Innanzitutto, consulta la documentazione di Apple:
Configurare l'SDK Navigation
- Dopo aver letto la documentazione di Apple, puoi iniziare a utilizzare l'SDK Navigation.
- Configura il progetto se non hai ancora integrato l'SDK Navigation nella tua app.
- Attiva il feed di indicazioni stradali turn-by-turn per la tua app.
- (Facoltativo) Utilizza le icone generate dall'SDK Navigation.
- Disegna la mappa utilizzando la classe
GMSMapView
fornita nella classe UIView. Per ulteriori informazioni, consulta la sezione Percorrere una route. CompilaCPNavigationSession
con i dati della libreria TurnByTurn.
Disegna l'interfaccia utente della mappa e della navigazione
La classe
GMSMapView
esegue il rendering di una mappa, mentre la classe
CPMapTemplate
esegue il rendering dell'interfaccia utente sulle schermate di CarPlay. Offre molte delle stesse funzionalità del GMSMapView
per gli smartphone, ma con un'interattività limitata.
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];
}
Attivare l'interazione con la mappa
Per garantire la sicurezza del conducente, CarPlay limita l'interazione con la superficie dello schermo a una serie di metodi.CPMapTemplateDelegate
Utilizza questi callback per supportare un'interazione limitata del conducente con la mappa su una schermata del cruscotto.
Per supportare ulteriori azioni utente, crea un array di CPMapButton
e assegnalo a CPMapTemplate.mapButtons
.
Il codice seguente crea interazioni con panoramica e pulsanti per eseguire la panoramica, aumentare e diminuire lo zoom e fornire la posizione dell'utente.
Interazioni con la panoramica
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;
}
Utilizzi comuni dei pulsanti
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;
}
Nota:non è possibile selezionare itinerari alternativi sullo schermo di CarPlay. Devono essere selezionate dallo smartphone prima dell'avvio di CarPlay.
Mostrare le indicazioni stradali
Questa sezione spiega come configurare un ascoltatore per un feed di dati e come compilare le indicazioni stradali nei riquadri delle indicazioni e delle stime di viaggio. Per ulteriori informazioni, consulta la sezione "Creare un'app di navigazione CarPlay" della Guida alla programmazione delle app CarPlay.
I riquadri di indicazioni stradali e di stima del viaggio forniscono una scheda di navigazione che mostra informazioni di navigazione relative al viaggio corrente. La libreria TurnByTurn nell'SDK Navigation può aiutarti a fornire alcune di queste informazioni, ad esempio simboli, testo e tempo rimanente.
Configurare un listener
Segui le istruzioni per configurare un ascoltatore di eventi in Dettagli sul feed di dati di navigazione assistita.
Compilare le informazioni di navigazione
La prima parte del seguente esempio di codice mostra come creare stime di viaggio di CarPlay traducendo GMSNavigationNavInfo.timeToCurrentStepSeconds
in CPTravelEstimate
. Puoi approfondire questi e altri elementi di visualizzazione in Dettagli sul feed di dati turn-by-turn.
La seconda parte dell'esempio mostra come creare un oggetto e archiviarlo nel
campo userInfo
di CPManuevers
. Questo determina il valore CPManeuverDisplayStyle
,
che viene utilizzato anche per le informazioni sulle corsie. Per ulteriori informazioni, consulta la Guida alla programmazione delle app CarPlay di Apple.
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;
}
Manovre
CarPlay utilizza la CPManeuver
classe per fornire indicazioni stradali passo passo. Per ulteriori informazioni su manovre e indicazioni stradali, consulta la sezione Dettagli sul feed di dati per indicazioni stradali.