فعال کردن پیمایش برای CarPlay

این بخش توضیح می‌دهد که چگونه می‌توانید از «کیت توسعه نرم‌افزار پیمایش» با کتابخانه Apple CarPlay برای نمایش تجربه پیمایش برنامه‌تان در واحد سر داشبورد استفاده کنید. اگر سیستم داشبورد راننده از CarPlay پشتیبانی کند، رانندگان می‌توانند با متصل کردن تلفنشان به واحد، مستقیماً از برنامه شما در نمایشگر خودرو استفاده کنند. راهنمای گفتاری نیز از بلندگوهای خودرو پخش می‌شود.

برنامه CarPlay خود را از مجموعه‌ای از الگوهای رابط کاربری که توسط Apple ارائه شده است، می‌سازید. برنامه شما مسئول انتخاب الگویی است که نمایش داده می‌شود و همچنین مسئول ارائه داده‌ها درون آن است.

سیستم درون داشبورد عناصر تعاملی تأییدشده ازنظر ایمنی را نمایش می‌دهد تا راننده بتواند بدون حواس‌پرتی بی‌مورد، به‌طور ایمن به مقصد خود پیمایش کند. همچنین می‌توانید برنامه‌تان را طوری برنامه‌ریزی کنید که راننده بتواند با ویژگی‌های مختص برنامه شما تعامل داشته باشد، مثلاً سفارش‌ها را بپذیرد یا رد کند، یا مکان مشتری را روی نقشه ببیند. به‌روزرسانی‌های وضعیت سفارش را نیز می‌توان برنامه‌ریزی کرد تا در واحد داخل داشبورد نمایش داده شود.

نمایشگرهای CarPlay و پیمایش تلفن
تصویر سمت راست نمونه‌ای از نمایشگر پیمایش CarPlay را نشان می‌دهد. تصویر سمت راست همان پیمایش را نشان می‌دهد که در تلفن ظاهر می‌شود.

راه‌اندازی

شروع با CarPlay

ابتدا با اسناد Apple آشنا شوید:

راه‌اندازی کیت توسعه نرم‌افزار پیمایش

  1. پس‌از اینکه مستندات Apple را خواندید، آماده کار با «کیت توسعه نرم‌افزار ناوبری» هستید.
  2. اگر قبلاً «کیت توسعه نرم‌افزار ناوبری» را در برنامه‌تان ادغام نکرده‌اید، پروژه‌تان را راه‌اندازی کنید.
  3. فید راهنمایی گام‌به‌گام را برای برنامه‌تان فعال کنید.
  4. اختیاری. از نمادهای تولیدشده از Navigation SDK استفاده کنید.
  5. نقشه را بااستفاده از کلاس GMSMapView ارائه‌شده در کلاس UIView بکشید. برای اطلاعات بیشتر، پیمایش مسیر را ببینید. CPNavigationSession را با داده‌های کتابخانه TurnByTurn پر کنید.

رسم نقشه و واسط کاربر ناوبری

کلاس GMSMapView نقشه را ارائه می‌کند و CPMapTemplate میانای کاربر را در صفحه‌نمایش‌های CarPlay ارائه می‌کند. این برنامه بسیاری از عملکردهای مشابه GMSMapView برای تلفن‌ها را ارائه می‌دهد، اما با تعامل محدود.

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

آبجکتیو-سی

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

فعال کردن تعامل با نقشه

برای اطمینان از ایمنی راننده، CarPlay تعامل با سطح صفحه‌نمایش را به مجموعه‌ای از روش‌های CPMapTemplateDelegate محدود می‌کند. از این فراخوانی‌های برگشتی برای پشتیبانی از تعامل محدود راننده با نقشه در صفحه‌نمایش داشبورد استفاده کنید.

برای پشتیبانی از کنش‌های کاربر اضافی، آرایه‌ای از CPMapButton ایجاد کنید و آن را به CPMapTemplate.mapButtons اختصاص دهید.

کد زیر تعامل‌های پانوراما و دکمه‌هایی برای پانوراما، زوم‌پیش و زوم‌پس کردن، و ارائه مکان کاربر ایجاد می‌کند.

تعاملات پیمایش

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
}

آبجکتیو-سی

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

کاربردهای رایج دکمه

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

آبجکتیو-سی

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

توجه: مسیرهای جایگزین را نمی‌توان در صفحه CarPlay انتخاب کرد. این موارد باید قبل‌از شروع CarPlay از تلفن انتخاب شوند.

نمایش مسیرهای پیمایش

این بخش نحوه راه‌اندازی شنونده برای فید داده و نحوه پر کردن مسیرهای ناوبری در پانل‌های راهنمایی و تخمین سفر را پوشش می‌دهد. برای اطلاعات بیشتر، بخش «ساختن برنامه ناوبری CarPlay» در راهنمای برنامه‌نویسی برنامه CarPlay را ببینید.

پانل‌های راهنمایی و تخمین سفر یک کارت ناوبری ارائه می‌دهند که اطلاعات ناوبری مربوط به سفر فعلی را نمایش می‌دهد. کتابخانه TurnByTurn در «کیت توسعه نرم‌افزار ناوبری» می‌تواند در ارائه برخی‌از این اطلاعات، مثل نماد، نوشتار، و زمان باقی‌مانده، کمک کند.

راه‌اندازی شنونده

دستورالعمل‌های راه‌اندازی شنونده رویداد را در جزئیات مربوط به فید داده‌های گام‌به‌گام دنبال کنید.

پر کردن اطلاعات پیمایش

بخش اول نمونه کد زیر نحوه ایجاد تخمین‌های سفر CarPlay را با ترجمه کردن GMSNavigationNavInfo.timeToCurrentStepSeconds به CPTravelEstimate نشان می‌دهد. می‌توانید درباره این عناصر و سایر عناصر نمایشگر در جزئیات مربوط به فید داده‌های گام‌به‌گام بیشتر بخوانید.

بخش دوم نمونه نشان می‌دهد چگونه شیء ایجاد کنید و آن را در فیلد userInfo از CPManuevers ذخیره کنید. این تنظیم CPManeuverDisplayStyle را تعیین می‌کند، که برای اطلاعات راهنمایی در مسیر نیز استفاده می‌شود. برای اطلاعات بیشتر، به راهنمای برنامه‌نویسی برنامه CarPlay 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
}

آبجکتیو-سی

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

مانورها

‫CarPlay از CPManeuver کلاس برای ارائه راهنمایی گام‌به‌گام استفاده می‌کند. برای اطلاعات بیشتر درباره مانورها و راهنمایی خطی، جزئیات فید داده گام‌به‌گام را ببینید.