在 iOS 中追蹤行程

選取平台: Android iOS JavaScript

當您追蹤行程時,消費者應用程式會向消費者顯示適當車輛的位置。為此,應用程式需要開始追蹤行程、更新行程進度,並在行程完成時停止追蹤。

本文件將說明這項程序的運作方式。

開始追蹤行程

以下說明如何開始追蹤行程:

  • ViewController 收集所有使用者輸入內容,例如下車和上車地點。

  • 建立新的 ViewController,即可直接開始追蹤行程。

以下範例說明如何在檢視畫面載入後立即開始追蹤行程。

Swift

/*
 * MapViewController.swift
 */
override func viewDidLoad() {
  super.viewDidLoad()
  ...
  self.mapView = GMTCMapView(frame: UIScreen.main.bounds)
  self.mapView.delegate = self
  self.view.addSubview(self.mapView)
}

func mapViewDidInitializeCustomerState(_: GMTCMapView) {
  self.mapView.pickupLocation = self.selectedPickupLocation
  self.mapView.dropoffLocation = self.selectedDropoffLocation

  self.startConsumerMatchWithLocations(
    pickupLocation: self.mapView.pickupLocation!,
    dropoffLocation: self.mapView.dropoffLocation!
  ) { [weak self] (tripName, error) in
    guard let strongSelf = self else { return }
    if error != nil {
      // print error message.
      return
    }
    let tripService = GMTCServices.shared().tripService
    // Create a tripModel instance for listening the update of the trip
    // specified by this trip name.
    let tripModel = tripService.tripModel(forTripName: tripName)
    // Create a journeySharingSession instance based on the tripModel
    let journeySharingSession = GMTCJourneySharingSession(tripModel: tripModel)
    // Add the journeySharingSession instance on the mapView for UI updating.
    strongSelf.mapView.show(journeySharingSession)
    // Register for the trip update events.
    tripModel.register(strongSelf)

    strongSelf.currentTripModel = tripModel
    strongSelf.currentJourneySharingSession = journeySharingSession
    strongSelf.hideLoadingView()
  }

  self.showLoadingView()
}

Objective-C

/*
 * MapViewController.m
 */
- (void)viewDidLoad {
  [super viewDidLoad];
  ...
  self.mapView = [[GMTCMapView alloc] initWithFrame:CGRectZero];
  self.mapView.delegate = self;
  [self.view addSubview:self.mapView];
}

// Handle the callback when the GMTCMapView did initialized.
- (void)mapViewDidInitializeCustomerState:(GMTCMapView *)mapview {
  self.mapView.pickupLocation = self.selectedPickupLocation;
  self.mapView.dropoffLocation = self.selectedDropoffLocation;

  __weak __typeof(self) weakSelf = self;
  [self startTripBookingWithPickupLocation:self.selectedPickupLocation
                           dropoffLocation:self.selectedDropoffLocation
                                completion:^(NSString *tripName, NSError *error) {
                                  __typeof(self) strongSelf = weakSelf;
                                  GMTCTripService *tripService = [GMTCServices sharedServices].tripService;
                                  // Create a tripModel instance for listening to updates to the trip specified by this trip name.
                                  GMTCTripModel *tripModel = [tripService tripModelForTripName:tripName];
                                  // Create a journeySharingSession instance based on the tripModel.
                                  GMTCJourneySharingSession *journeySharingSession =
                                    [[GMTCJourneySharingSession alloc] initWithTripModel:tripModel];
                                  // Add the journeySharingSession instance on the mapView for updating the UI.
                                  [strongSelf.mapView showMapViewSession:journeySharingSession];
                                  // Register for trip update events.
                                  [tripModel registerSubscriber:self];

                                  strongSelf.currentTripModel = tripModel;
                                  strongSelf.currentJourneySharingSession = journeySharingSession;
                                  [strongSelf hideLoadingView];
                                }];
    [self showLoadingView];
}

停止追蹤行程

行程結束或取消後,您就會停止追蹤行程。以下範例說明如何停止分享目前行程。

Swift

/*
 * MapViewController.swift
 */
func cancelCurrentActiveTrip() {
  // Stop the tripModel
  self.currentTripModel.unregisterSubscriber(self)

  // Remove the journey sharing session from the mapView's UI stack.
  self.mapView.hide(journeySharingSession)
}

Objective-C

/*
 * MapViewController.m
 */
- (void)cancelCurrentActiveTrip {
  // Stop the tripModel
  [self.currentTripModel unregisterSubscriber:self];

  // Remove the journey sharing session from the mapView's UI stack.
  [self.mapView hideMapViewSession:journeySharingSession];
}

更新行程進度

行程期間,您可以透過下列方式管理行程進度:

行程完成或取消後,請停止收聽更新。如需範例,請參閱「停止監聽更新示例」。

開始監聽更新的範例

以下範例說明如何註冊 tripModel 回呼。

Swift

/*
 * MapViewController.swift
 */
override func viewDidLoad() {
  super.viewDidLoad()
  // Register for trip update events.
  self.currentTripModel.register(self)
}

Objective-C

/*
 * MapViewController.m
 */
- (void)viewDidLoad {
  [super viewDidLoad];
  // Register for trip update events.
  [self.currentTripModel registerSubscriber:self];
  ...
}

停止監聽更新示例

以下範例說明如何取消註冊 tripModel 回呼。

Swift

/*
 * MapViewController.swift
 */
deinit {
  self.currentTripModel.unregisterSubscriber(self)
}

Objective-C

/*
 * MapViewController.m
 */
- (void)dealloc {
  [self.currentTripModel unregisterSubscriber:self];
  ...
}

處理行程更新範例

以下範例說明如何在行程狀態更新時,實作用於處理回呼的 GMTCTripModelSubscriber 通訊協定。

Swift

/*
 * MapViewController.swift
 */
func tripModel(_: GMTCTripModel, didUpdate trip: GMTSTrip?, updatedPropertyFields: GMTSTripPropertyFields) {
  // Update the UI with the new `trip` data.
  self.updateUI(with: trip)
}

func tripModel(_: GMTCTripModel, didUpdate tripStatus: GMTSTripStatus) {
  // Handle trip status did change.
}

func tripModel(_: GMTCTripModel, didUpdateActiveRouteRemainingDistance activeRouteRemainingDistance: Int32) {
  // Handle remaining distance of active route did update.
}

func tripModel(_: GMTCTripModel, didUpdateActiveRoute activeRoute: [GMTSLatLng]?) {
  // Handle trip active route did update.
}

func tripModel(_: GMTCTripModel, didUpdate vehicleLocation: GMTSVehicleLocation?) {
  // Handle vehicle location did update.
}

func tripModel(_: GMTCTripModel, didUpdatePickupLocation pickupLocation: GMTSTerminalLocation?) {
  // Handle pickup location did update.
}

func tripModel(_: GMTCTripModel, didUpdateDropoffLocation dropoffLocation: GMTSTerminalLocation?) {
  // Handle drop off location did update.
}

func tripModel(_: GMTCTripModel, didUpdatePickupETA pickupETA: TimeInterval) {
  // Handle the pickup ETA did update.
}

func tripModel(_: GMTCTripModel, didUpdateDropoffETA dropoffETA: TimeInterval) {
  // Handle the drop off ETA did update.
}

func tripModel(_: GMTCTripModel, didUpdateRemaining remainingWaypoints: [GMTSTripWaypoint]?) {
  // Handle updates to the pickup, dropoff or intermediate destinations of the trip.
}

func tripModel(_: GMTCTripModel, didFailUpdateTripWithError error: Error?) {
  // Handle the error.
}

func tripModel(_: GMTCTripModel, didUpdateIntermediateDestinations intermediateDestinations: [GMTSTerminalLocation]?) {
  // Handle the intermediate destinations being updated.
}

func tripModel(_: GMTCTripModel, didUpdateActiveRouteTraffic activeRouteTraffic: GMTSTrafficData?) {
  // Handle trip active route traffic being updated.
}

Objective-C

/*
 * MapViewController.m
 */
#pragma mark - GMTCTripModelSubscriber implementation

- (void)tripModel:(GMTCTripModel *)tripModel
            didUpdateTrip:(nullable GMTSTrip *)trip
    updatedPropertyFields:(enum GMTSTripPropertyFields)updatedPropertyFields {
  // Update the UI with the new `trip` data.
  [self updateUIWithTrip:trip];
  ...
}

- (void)tripModel:(GMTCTripModel *)tripModel didUpdateTripStatus:(enum GMTSTripStatus)tripStatus {
  // Handle trip status did change.
}

- (void)tripModel:(GMTCTripModel *)tripModel
    didUpdateActiveRouteRemainingDistance:(int32_t)activeRouteRemainingDistance {
   // Handle remaining distance of active route did update.
}

- (void)tripModel:(GMTCTripModel *)tripModel
    didUpdateActiveRoute:(nullable NSArray<GMTSLatLng *> *)activeRoute {
  // Handle trip active route did update.
}

- (void)tripModel:(GMTCTripModel *)tripModel
    didUpdateVehicleLocation:(nullable GMTSVehicleLocation *)vehicleLocation {
  // Handle vehicle location did update.
}

- (void)tripModel:(GMTCTripModel *)tripModel
    didUpdatePickupLocation:(nullable GMTSTerminalLocation *)pickupLocation {
  // Handle pickup location did update.
}

- (void)tripModel:(GMTCTripModel *)tripModel
    didUpdateDropoffLocation:(nullable GMTSTerminalLocation *)dropoffLocation {
  // Handle drop off location did update.
}

- (void)tripModel:(GMTCTripModel *)tripModel didUpdatePickupETA:(NSTimeInterval)pickupETA {
  // Handle the pickup ETA did update.
}

- (void)tripModel:(GMTCTripModel *)tripModel
    didUpdateRemainingWaypoints:(nullable NSArray<GMTSTripWaypoint *> *)remainingWaypoints {
  // Handle updates to the pickup, dropoff or intermediate destinations of the trip.
}

- (void)tripModel:(GMTCTripModel *)tripModel didUpdateDropoffETA:(NSTimeInterval)dropoffETA {
  // Handle the drop off ETA did update.
}

- (void)tripModel:(GMTCTripModel *)tripModel didFailUpdateTripWithError:(nullable NSError *)error {
  // Handle the error.
}

- (void)tripModel:(GMTCTripModel *)tripModel
    didUpdateIntermediateDestinations:
        (nullable NSArray<GMTSTerminalLocation *> *)intermediateDestinations {
  // Handle the intermediate destinations being updated.
}

- (void)tripModel:(GMTCTripModel *)tripModel
    didUpdateActiveRouteTraffic:(nullable GMTSTrafficData *)activeRouteTraffic {
  // Handle trip active route traffic being updated.
}

處理行程錯誤

如果您訂閱 tripModel 並發生錯誤,可以實作委派方法 tripModel(_:didFailUpdateTripWithError:) 來取得 tripModel 的回呼。錯誤訊息遵循 Google Cloud 錯誤標準。如需錯誤訊息定義和所有錯誤代碼的詳細資訊,請參閱 Google Cloud 錯誤說明文件

以下是行程監控期間可能發生的常見錯誤:

HTTP RPC 說明
400 INVALID_ARGUMENT 用戶端指定的旅遊名稱無效。行程名稱必須採用 providers/{provider_id}/trips/{trip_id} 格式。provider_id 必須是服務供應商擁有的 Cloud 專案 ID。
401 UNAUTHENTICATED 如果沒有有效的驗證憑證,就會收到這項錯誤訊息。例如,如果 JWT 憑證未經簽署就附上行程 ID,或是 JWT 憑證已過期。
403 PERMISSION_DENIED 如果用戶端沒有足夠的權限 (例如,具有消費者角色的使用者嘗試呼叫 updateTrip)、JWT 權杖無效,或是用戶端專案未啟用 API,就會收到這則錯誤訊息。JWT 權杖可能遺失,或是權杖使用與要求行程 ID 不符的行程 ID 進行簽署。
429 RESOURCE_EXHAUSTED 資源配額為零,或流量頻率超出限制。
503 無法使用 服務無法使用,通常是因伺服器停止運作所致。
504 DEADLINE_EXCEEDED 已超出要求期限。只有在呼叫者設定的期限短於方法的預設期限 (即要求的期限不夠讓伺服器處理要求),且要求未在期限內完成時,才會發生這項錯誤。

處理 Consumer SDK 錯誤

Consumer SDK 會使用回呼機制,將行程更新錯誤傳送至消費者應用程式。回呼參數是特定平台的傳回類型 (Android 上的 TripUpdateError,iOS 上的 NSError)。

擷取狀態碼

傳遞至回呼的錯誤通常是 gRPC 錯誤,您也可以從中擷取狀態碼形式的其他資訊。如需完整的狀態碼清單,請參閱「狀態碼及其在 gRPC 中的用法」。

Swift

NSError 會在 tripModel(_:didFailUpdateTripWithError:) 中回呼。

// Called when there is a trip update error.
func tripModel(_ tripModel: GMTCTripModel, didFailUpdateTripWithError error: Error?) {
  // Check to see if the error comes from gRPC.
  if let error = error as NSError?, error.domain == "io.grpc" {
    let gRPCErrorCode = error.code
    ...
  }
}

Objective-C

NSError 會在 tripModel:didFailUpdateTripWithError: 中回呼。

// Called when there is a trip update error.
- (void)tripModel:(GMTCTripModel *)tripModel didFailUpdateTripWithError:(NSError *)error {
  // Check to see if the error comes from gRPC.
  if ([error.domain isEqualToString:@"io.grpc"]) {
    NSInteger gRPCErrorCode = error.code;
    ...
  }
}

解讀狀態碼

狀態碼涵蓋兩種錯誤:伺服器和網路相關錯誤,以及用戶端錯誤。

伺服器和網路錯誤

以下狀態碼代表網路或伺服器錯誤,您不需要採取任何行動即可解決。Consumer SDK 會自動復原這些錯誤。

狀態碼說明
ABORTED 伺服器停止傳送回應。這通常是伺服器問題所致。
已取消 伺服器終止了傳出回應。這通常會發生在
應用程式傳送至背景時,或是
消費者應用程式發生狀態變更時。
INTERRUPTED
DEADLINE_EXCEEDED 伺服器的回應時間過長。
無法使用 伺服器無法使用。這通常是因為網路問題。

用戶端錯誤

下列狀態碼是用於用戶端錯誤,您必須採取行動來解決這些錯誤。在您結束行程分享前,Consumer SDK 會持續重試重新整理行程,但必須等到您採取行動,系統才會恢復。

狀態碼說明
INVALID_ARGUMENT 消費者應用程式指定的旅程名稱無效。旅程名稱必須採用 providers/{provider_id}/trips/{trip_id} 格式。
NOT_FOUND 系統從未建立行程。
PERMISSION_DENIED Consumer 應用程式的權限不足。發生以下狀況時會出現這個錯誤:
  • 消費者應用程式沒有權限
  • Google Cloud 控制台中的專案未啟用 Consumer SDK。
  • JWT 權杖遺失或無效。
  • JWT 權杖已使用不符於要求行程的行程 ID 簽署。
RESOURCE_EXHAUSTED 資源配額為零,或流量速率超出速度限制。
UNAUTHENTICATED 由於 JWT 權杖無效,因此要求驗證失敗。當 JWT 權杖未加上旅程 ID 就簽署,或是 JWT 權杖已過期時,就會發生這個錯誤。