開始使用 iOS 版 Driver SDK

Driver SDK 是可整合至驅動程式應用程式的程式庫,負責以驅動程式的位置、路線、剩餘距離和預計到達時間來更新 Fleet Engine。另外也可整合 Navigation SDK,為駕駛人提供即時路線導航指示。

最低系統需求

  • 行動裝置必須搭載 iOS 13 以上版本。
  • Xcode 14 版或以上版本。
  • 必要條件

    本指南假設您的應用程式已實作 Navigation SDK,且 Fleet Engine 後端已設定完成並可供使用。不過,範例程式碼提供如何設定 Navigation SDK 的範例。

    您也必須在 Google Cloud 專案中啟用 Maps SDK for iOS,並取得 API 金鑰

    取得使用權限

    如果您是 Google Workspace 客戶,請在新手上路期間建立 Workspace 群組 (例如 google-maps-platform-sdk-users@workspacedomain.com),並將名稱提供給 Google。這是建議做法。 系統會將您的 Workspace 群組加入許可清單,並授予正確 CocoaPods 存放區的存取權。確認這份清單包含需要存取權的使用者電子郵件和服務帳戶電子郵件。

    如果貴機構無法建立 Workspace 群組,請將需要存取這些構件的使用者和服務帳戶電子郵件地址傳送給 Google。

    本機開發

    如要進行本機開發,請使用 Cloud SDK 登入。

    gcloud

    gcloud auth login
    

    用來登入的電子郵件必須是 Workspace 群組的成員。

    自動化 (建構系統或持續整合)

    根據最佳做法設定自動化主機:

    • 如果程序是在 Google Cloud 環境中執行,請使用自動憑證偵測。

    • 否則,請將服務帳戶金鑰檔案儲存在主機檔案系統的安全位置,並設定 GOOGLE_APPLICATION_CREDENTIALS 環境變數。

    與憑證相關聯的服務帳戶電子郵件地址必須是 Workspace Goup 的成員。

    專案設定

    您可以使用 CocoaPods 設定 Driver SDK。

    使用 CocoaPods

    如要使用 CocoaPods 設定 Driver SDK,您需要下列項目:

    • CocoaPods 工具:如要安裝這項工具,請開啟終端機並執行下列指令。 shell sudo gem install cocoapods 詳情請參閱 CocoaPods 入門指南
    1. 為驅動程式 SDK 建立 Podfile,並使用該檔案安裝 API 及其依附元件:在專案目錄中建立名為 Podfile 的檔案。這個檔案定義了專案的依附元件。請編輯 Podfile 並新增依附元件。以下是包含依附元件的範例:

      source "https://github.com/CocoaPods/Specs.git"
      
      target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
        pod 'GoogleRidesharingDriver'
      end
      
    2. 儲存 Podfile。開啟終端機並前往包含 Podfile 的目錄:

      cd <path-to-project>
      
    3. 執行 pod install 指令。這項操作會安裝 Podfile 中指定的 API,以及這些 API 所擁有的任何依附元件。

      pod install
      
    4. 關閉 Xcode,然後開啟 (按兩下) 專案的 .xcworkspace 檔案以啟動 Xcode。從現在起,您必須使用 .xcworkspace 檔案才能開啟專案。

    Alpha/Beta 版 SDK 版本

    如要設定 iOS 版 Driver SDK 的 Alpha 版或 Beta 版,您必須具備下列項目:

    • CocoaPods 工具:如要安裝這項工具,請開啟終端機並執行下列指令。

      sudo gem install cocoapods
      

      詳情請參閱 CocoaPods 入門指南

    • Google 存取清單上的開發帳戶。SDK Alpha 和 Beta 版的 Pod 存放區並非公開來源。如要存取這些版本,請與 Google 客戶工程師聯絡。工程師會將您的開發帳戶新增至存取清單,然後設定 Cookie 以進行驗證。

    專案列入存取清單後,您便可存取 Pod。

    1. 為 iOS 版驅動程式 SDK 建立 Podfile,並使用該檔案安裝 API 及其依附元件:在專案目錄中建立名為 Podfile 的檔案。這個檔案定義了專案的依附元件。請編輯 Podfile 並新增依附元件。以下是包含依附元件的範例:

      source "https://cpdc-eap.googlesource.com/ridesharing-driver-sdk.git"
      source "https://github.com/CocoaPods/Specs.git"
      
      target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
        pod 'GoogleRidesharingDriver'
      end
      
    2. 儲存 Podfile。開啟終端機並前往包含 Podfile 的目錄:

      cd <path-to-project>
      
    3. 執行 pod install 指令。這項操作會安裝 Podfile 中指定的 API,以及這些 API 所擁有的任何依附元件。

      pod install
      
    4. 關閉 Xcode,然後開啟 (按兩下) 專案的 .xcworkspace 檔案以啟動 Xcode。從現在起,您必須使用 .xcworkspace 檔案才能開啟專案。

    實作授權和驗證

    當驅動程式應用程式產生更新並傳送至 Fleet Engine 後端時,要求必須包含有效的存取權杖。為授權及驗證這些要求,驅動程式 SDK 會呼叫符合 GMTDAuthorization 通訊協定的物件。物件負責提供必要的存取權杖。

    應用程式開發人員可以選擇產生權杖的方式。您的實作應提供:

    • 從 HTTPS 伺服器擷取存取權杖 (可能採用 JSON 格式)。
    • 剖析並快取權杖。
    • 權杖到期時,請重新整理。

    如要進一步瞭解 Fleet Engine 伺服器預期的權杖,請參閱建立 JSON Web Token (JWT) 以進行授權

    供應商 ID 與 Google Cloud 專案 ID 相同。詳情請參閱 Fleet Engine Deliveries API 使用手冊

    以下範例會實作存取權杖供應工具:

    #import "SampleAccessTokenProvider.h"
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    // SampleAccessTokenProvider.h
    @interface SampleAccessTokenProvider : NSObject<GMTDAuthorization>
    @end
    
    static NSString *const PROVIDER_URL = @"INSERT_YOUR_TOKEN_PROVIDER_URL";
    
    // SampleAccessTokenProvider.m
    @implementation SampleAccessTokenProvider{
      // The cached vehicle token.
      NSString *_cachedVehicleToken;
      // Keep track of the vehicle ID the cached token is for.
      NSString *_lastKnownVehicleID;
      // Keep track of when tokens expire for caching.
      NSTimeInterval _tokenExpiration;
    }
    
    - (void)fetchTokenWithContext:(nullable GMTDAuthorizationContext *)authorizationContext
                       completion:(nonnull GMTDAuthTokenFetchCompletionHandler)completion {
      if (!completion) {
        NSAssert(NO, @"%s encountered an unexpected nil completion.", __PRETTY_FUNCTION__);
        return;
      }
    
      // Get the vehicle ID from the authorizationContext. This is set by the Driver SDK.
      NSString *vehicleID = authorizationContext.vehicleID;
      if (!vehicleID) {
        NSAssert(NO, @"Vehicle ID is missing from authorizationContext.");
        return;
      }
    
    // Clear cached vehicle token if vehicle ID has changed.
      if (![_lastKnownVehicleID isEqual:vehicleID]) {
        _tokenExpiration = 0.0;
        _cachedVehicleToken = nil;
      }
      _lastKnownVehicleID = vehicleID;
    
      // Clear cached vehicle token if it has expired.
      if ([[NSDate date] timeIntervalSince1970] > _tokenExpiration) {
        _cachedVehicleToken = nil;
      }
    
      // If appropriate, use the cached token.
      if (_cachedVehicleToken) {
        completion(_cachedVehicleToken, nil);
        return;
      }
      // Otherwise, try to fetch a new token from your server.
      NSURL *requestURL = [NSURL URLWithString:PROVIDER_URL];
      NSMutableURLRequest *request = 
                              [[NSMutableURLRequest alloc] initWithURL:requestURL];
      request.HTTPMethod = @"GET";
      // Replace the following key values with the appropriate keys based on your
      // server's expected response.
      NSString *vehicleTokenKey = @"VEHICLE_TOKEN_KEY";
      NSString *tokenExpirationKey = @"TOKEN_EXPIRATION";
      __weak typeof(self) weakSelf = self;
      void (^handler)(NSData *_Nullable data, NSURLResponse *_Nullable response,
                      NSError *_Nullable error) =
          ^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
            typeof(self) strongSelf = weakSelf;
            if (error) {
              completion(nil, error);
              return;
            }
    
            NSError *JSONError;
            NSMutableDictionary *JSONResponse =
                [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&JSONError];
    
            if (JSONError) {
              completion(nil, JSONError);
              return;
            } else {
              // Sample code only. No validation logic.
              id expirationData = JSONResponse[tokenExpirationKey];
              if ([expirationData isKindOfClass:[NSNumber class]]) {
                NSTimeInterval expirationTime = ((NSNumber *)expirationData).doubleValue;
                strongSelf->_tokenExpiration = [[NSDate date] timeIntervalSince1970] + expirationTime;
              }
              strongSelf->_cachedVehicleToken = JSONResponse[vehicleTokenKey];
              completion(JSONResponse[vehicleTokenKey], nil);
            }
        };
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *mainQueueURLSession =  
           [NSURLSession  sessionWithConfiguration:config delegate:nil
    delegateQueue:[NSOperationQueue mainQueue]];
    NSURLSessionDataTask *task = [mainQueueURLSession dataTaskWithRequest:request completionHandler:handler];
    [task resume];
    }
    
    @end
    

    建立 DeliveryDriverAPI 執行個體

    如要取得 GMTDDeliveryVehicleReporter 執行個體,您必須先使用 providerID、vehicleID、 DriverContext 和 accessTokenProvider 建立 GMTDDeliveryDriverAPI 執行個體。providerID 和 Google Cloud 專案 ID 相同。您可以直接透過驅動程式 API 存取 GMTDDeliveryVehicleReporter 執行個體。

    以下範例會建立 GMTDDeliveryDriverAPI 執行個體:

    #import “SampleViewController.h”
    #import “SampleAccessTokenProvider.h”
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
    
    @implementation SampleViewController {
     GMSMapView *_mapView;
    }
    
    - (void)viewDidLoad {
      NSString *vehicleID = @"INSERT_CREATED_VEHICLE_ID";
      SampleAccessTokenProvider *accessTokenProvider = 
                                    [[SampleAccessTokenProvider alloc] init];
      GMTDDriverContext *driverContext = 
         [[GMTDDriverContext alloc] initWithAccessTokenProvider:accessTokenProvider
                                                     providerID:PROVIDER_ID 
                                                  vehicleID:vehicleID 
          navigator:_mapView.navigator];
    
      GMTDDeliveryDriverAPI *deliveryDriverAPI = [[GMTDDeliveryDriverAPI alloc] initWithDriverContext:driverContext];
    }
    

    可視需要監聽 VehicleReporter 事件

    locationTrackingEnabled 為 YES 時,GMTDDeliveryVehicleReporter 會定期更新車輛。如要回應這些定期更新,任何物件只要符合 GMTDVehicleReporterListener 通訊協定,即可訂閱 GMTDDeliveryVehicleReporter 事件。

    您可以處理下列事件:

    • vehicleReporter:didSucceedVehicleUpdate

      通知駕駛人應用程式已成功收到車輛位置和狀態更新。

    • vehicleReporter:didFailVehicleUpdate:withError

      通知事件監聽器有車輛更新失敗。只要啟用位置追蹤功能,GMTDDeliveryVehicleReporter 就會繼續將最新資料傳送至 Fleet Engine 後端。

    以下範例處理這些事件:

    SampleViewController.h
    @interface SampleViewController : UIViewController<GMTDVehicleReporterListener>
    @end
    
    SampleViewController.m
    #import “SampleViewController.h”
    #import “SampleAccessTokenProvider.h”
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
    
    @implementation SampleViewController {
     GMSMapView *_mapView;
    }
    
    
    - (void)viewDidLoad {
      // ASSUMES YOU IMPLEMENTED HAVE THE SAMPLE CODE UP TO THIS STEP.
      [ridesharingDriverAPI.vehicleReporter addListener:self];
    }
    
    - (void)vehicleReporter:(GMTDDeliveryVehicleReporter *)vehicleReporter didSucceedVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate {
      // Handle update succeeded.
    }
    
    - (void)vehicleReporter:(GMTDDeliveryVehicleReporter *)vehicleReporter didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate withError:(NSError *)error {
      // Handle update failed.
    }
    
    @end
    

    啟用位置追蹤功能

    如要啟用位置追蹤功能,應用程式可在 GMTDDeliveryVehicleReporterlocationTrackingEnabled 設為 YES。然後 GMTDDeliveryVehicleReporter = 自動傳送位置更新資訊。當 GMSNavigator 處於導航模式 (當目的地透過 setDestinations 設定) 且 locationTrackingEnabled 設為 YES 時,GMTDDeliveryVehicleReporter 也會自動傳送路線和預計到達時間的更新資訊。

    在這些更新期間設定的路徑將與駕駛人在導航工作階段中前往的路徑相同。因此,為了讓運送追蹤功能正確運作,透過 -setDestinations:callback: 設定的路線控點應與 Fleet Engine 後端中設定的目的地相符。

    下例會啟用位置追蹤功能:

    SampleViewController.m
    #import “SampleViewController.h”
    #import “SampleAccessTokenProvider.h”
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
    
    @implementation SampleViewController {
     GMSMapView *_mapView; 
    }
    
    - (void)viewDidLoad {
      // ASSUMES YOU IMPLEMENTED HAVE THE SAMPLE CODE UP TO THIS STEP.
      deliveryDriverAPI.vehicleReporter.locationTrackingEnabled = YES;
    }
    
    @end
    

    根據預設,報表間隔為 10 秒,但報表間隔可以使用 locationUpdateInterval 變更。支援的更新間隔下限為 5 秒。支援的更新間隔時間上限為 60 秒。頻繁的更新可能會導致要求速度變慢,以及發生錯誤。

    停用位置更新功能

    應用程式可停用車輛的位置更新功能。舉例來說,當駕駛員的班表結束時,應用程式可以將 locationTrackingEnabled 設為 NO

      _vehicleReporter.locationTrackingEnabled = NO
    

    處理 update_mask 錯誤

    GMTDDeliveryVehicleReporter 傳送車輛更新時,當遮罩空白時,可能會發生 update_mask 錯誤,且通常是在啟動後第一次更新時發生。以下範例說明如何處理這項錯誤:

    Swift

    import GoogleRidesharingDriver
    
    class VehicleReporterListener: NSObject, GMTDVehicleReporterListener {
      func vehicleReporter(
        _ vehicleReporter: GMTDVehicleReporter,
        didFail vehicleUpdate: GMTDVehicleUpdate,
        withError error: Error
      ) {
        let fullError = error as NSError
        if let innerError = fullError.userInfo[NSUnderlyingErrorKey] as? NSError {
          let innerFullError = innerError as NSError
          if innerFullError.localizedDescription.contains("update_mask cannot be empty") {
            emptyMaskUpdates += 1
            return
          }
        }
        failedUpdates += 1
      }
    
      override init() {
        emptyMaskUpdates = 0
        failedUpdates = 0
      }
    }
    
    

    Objective-C

    #import "VehicleReporterListener.h"
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    @implementation VehicleReporterListener {
      NSInteger emptyMaskUpdates = 0;
      NSInteger failedUpdates = 0;
    }
    
    - (void)vehicleReporter:(GMTDVehicleReporter *)vehicleReporter
      didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate
                 withError:(NSError *)error {
      for (NSError *underlyingError in error.underlyingErrors) {
        if ([underlyingError.localizedDescription containsString:@"update_mask cannot be empty"]) {
          emptyMaskUpdates += 1;
          return;
        }
      }
      failedUpdates += 1
    }
    
    @end