Mengaktifkan navigasi untuk CarPlay

Bagian ini menjelaskan cara menggunakan Navigation SDK dengan library Apple CarPlay untuk menampilkan pengalaman navigasi aplikasi Anda di unit head-up display head display. Jika sistem head-up display pengemudi mendukung CarPlay, pengemudi dapat menggunakan aplikasi Anda langsung di layar mobil dengan menghubungkan ponsel ke unit. Panduan suara juga berjalan di speaker mobil.

Anda membuat aplikasi CarPlay dari serangkaian template UI yang disediakan oleh Apple. Aplikasi Anda bertanggung jawab untuk memilih template yang akan ditampilkan dan menyediakan data di dalamnya.

Sistem head-up display menampilkan elemen interaktif yang disetujui keamanannya sehingga pengemudi dapat menavigasi ke tujuannya dengan aman tanpa gangguan yang tidak semestinya. Anda juga dapat memprogram aplikasi sehingga pengemudi dapat berinteraksi dengan fitur khusus aplikasi, seperti menerima atau menolak pesanan, atau melihat lokasi pelanggan di peta. Pembaruan status pesanan juga dapat diprogram untuk muncul di unit head-up display.

Tampilan navigasi CarPlay dan ponsel
Gambar di sebelah kiri menunjukkan contoh tampilan navigasi CarPlay. Gambar di sebelah kanan menunjukkan navigasi yang sama seperti yang muncul di ponsel.

Penyiapan

Mulai dengan CarPlay

Pertama, pahami dokumentasi Apple:

Menyiapkan Navigation SDK

  1. Setelah membaca dokumentasi Apple, Anda siap menggunakan Navigation SDK.
  2. Siapkan project jika Anda belum mengintegrasikan Navigation SDK ke dalam aplikasi.
  3. Aktifkan feed panduan TurnByTurn untuk aplikasi Anda.
  4. Opsional. Gunakan ikon yang dibuat dari Navigation SDK.
  5. Gambar peta menggunakan class GMSMapView yang disediakan di class UIView. Lihat Menavigasi rute untuk mengetahui informasi selengkapnya. Isi CPNavigationSession dengan data dari library TurnByTurn.

Menggambar peta dan UI navigasi

Class GMSMapView merender peta dan CPMapTemplate merender UI di layar CarPlay. Class ini menyediakan sebagian besar fungsi yang sama dengan GMSMapView untuk ponsel, tetapi dengan interaktivitas terbatas.

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];
}

Mengaktifkan interaksi peta

Untuk memastikan keselamatan pengemudi, CarPlay membatasi interaksi permukaan layar ke serangkaian metode CPMapTemplateDelegate. Gunakan callback ini untuk mendukung interaksi pengemudi terbatas dengan peta di layar head-up display.

Untuk mendukung tindakan pengguna tambahan, buat array CPMapButton dan tetapkan keCPMapTemplate.mapButtons.

Kode berikut membuat interaksi dan tombol geser untuk menggeser, memperbesar dan memperkecil, serta memberikan lokasi pengguna.

Interaksi geser

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;
}

Penggunaan tombol umum

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;
}

Catatan: Rute alternatif tidak dapat dipilih di layar CarPlay. Rute harus dipilih dari ponsel sebelum CarPlay dimulai.

Menampilkan rute navigasi

Bagian ini membahas cara menyiapkan pemroses untuk feed data dan cara mengisi rute navigasi di panel panduan dan perkiraan perjalanan. Lihat bagian "Membuat Aplikasi Navigasi CarPlay" di Panduan Pemrograman Aplikasi CarPlay untuk mengetahui informasi selengkapnya.

Panel panduan dan perkiraan perjalanan menyediakan kartu navigasi yang menampilkan informasi navigasi terkait perjalanan saat ini. Library TurnByTurn di Navigation SDK dapat membantu memberikan beberapa informasi ini, seperti simbol, teks, dan waktu tersisa.

Menyiapkan pemroses

Ikuti petunjuk untuk menyiapkan pemroses peristiwa di Detail tentang feed data turn-by-turn.

Mengisi informasi navigasi

Bagian pertama contoh kode berikut menunjukkan cara membuat perkiraan perjalanan CarPlay dengan menerjemahkan GMSNavigationNavInfo.timeToCurrentStepSeconds ke CPTravelEstimate. Anda dapat membaca lebih lanjut tentang elemen tampilan ini dan lainnya di Detail tentang feed data turn-by-turn.

Bagian kedua contoh menunjukkan cara membuat objek dan menyimpannya di kolom userInfo dari CPManuevers. Hal ini menentukan CPManeuverDisplayStyle, yang juga digunakan untuk informasi panduan jalur. Lihat Panduan Pemrograman Aplikasi CarPlay Apple untuk mengetahui informasi selengkapnya.

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;
}

Manuver

CarPlay uses the CPManeuver class untuk memberikan panduan turn-by-turn. Lihat Detail tentang data turn-by-turn feed untuk mengetahui informasi selengkapnya tentang manuver dan panduan jalur.