開始使用 iOS 版 Consumer SDK

您可以使用 Consumer SDK 建構及執行與「隨選乘車」和「外送服務」解決方案後端服務整合的基本消費者應用程式。您可以建立一個行程和訂單進度應用程式,用於顯示進行中的行程、回應行程更新,以及處理行程錯誤。

由於 Consumer SDK 採用模組化架構,因此您可以使用想要用於特定應用程式的 API 部分,並整合到自己的 API、Fleet Engine 提供的後端服務,以及 Google 地圖平台的其他 API。

基本系統需求

  • 行動裝置必須搭載 iOS 14 以上版本。
  • Xcode 15 以上版本。
  • 專案設定

    Swift 套件管理工具

    您可以透過 Swift 套件管理工具安裝 Consumer SDK。如要新增 SDK,請確認您已移除所有現有的 Consumer SDK 依附元件。

    如要將 SDK 加入新專案或現有專案,請按照下列步驟操作:

    1. 開啟 Xcode projectworkspace,然後依序前往「File」>「Add Package Dependencies」
    2. 輸入 https://github.com/googlemaps/ios-consumer-sdk 做為網址,然後按下 Enter 鍵提取套件,然後按一下「Add Package」。
    3. 如要安裝特定的 version,請將「依附元件規則」欄位設為其中一個以版本為基礎的選項。如果是新專案,建議您指定最新版本並使用「精確版本」選項。完成後,按一下「新增套件」。
    4. 在「選擇套件產品」視窗中,確認 GoogleRidesharingConsumer 會新增至指定的 main 目標。完成後,按一下「新增套件」。
    5. 如要驗證安裝狀態,請前往目標的 General 窗格。在「Frameworks、Library and Embedded Content」中應會顯示已安裝的套件。您也可以查看「Project Navigator」的「Package Dependencies」部分,確認套件及其版本。

    如要更新現有專案的 package,請按照下列步驟操作:

    1. 如果您要從 9.0.0 以下版本升級,必須在升級後移除下列依附元件:GoogleMapsBaseGoogleMapsCoreGoogleMapsM4B。請勿移除 GoogleMaps 的依附元件。詳情請參閱 9.0.0 版本資訊

      在 Xcode 專案設定設定中,找到「Frameworks、Library 和 Embedded Content」。請使用減號(-) 移除下列架構:

      • GoogleMapsBase (僅適用於 9.0.0 以下版本的升級作業)
      • GoogleMapsCore (僅適用於 9.0.0 以下版本的升級作業)
      • GoogleMapsM4B (僅適用於 9.0.0 以下版本的升級作業)
    2. 在 Xcode 中,前往 [檔案] > [套件] > [更新至最新套件版本]。
    3. 如要驗證安裝狀態,請前往「Project Navigator」的「Package Dependencies」部分,檢查套件及其版本。

    如要移除使用 CocoaPods 新增的現有 Consumer SDK 依附元件,請按照下列步驟操作:

    1. 關閉 Xcode 工作區。開啟終端機並執行下列指令:
      sudo gem install cocoapods-deintegrate cocoapods-clean 
      pod deintegrate 
      pod cache clean --all
    2. 如果您未在 CocoaPods 以外的地方使用 PodfilePodfile.resolved 和 Xcode workspace,請移除這些項目。

    如要移除手動安裝的現有 Consumer SDK,請按照下列步驟操作:

    1. 在 Xcode 專案設定設定中,找到「Frameworks、Libraries and Embedded Content」,請使用減號(-) 移除下列架構:

      • GoogleRidesharingConsumer.xcframework
    2. 從 Xcode 專案的頂層目錄中,移除 GoogleRidesharingConsumer 軟體包。

    CocoaPods

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

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

      sudo gem install cocoapods
      

    詳情請參閱 CocoaPods 入門指南

    1. 建立 Consumer SDK 的 Podfile,並使用該 Pod 安裝 API 及其依附元件。首先,在專案目錄中建立名為 Podfile 的檔案這個檔案定義了專案的依附元件。接著編輯 Podfile 並新增依附元件。以下是包含依附元件的範例:

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

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

      pod install
      
    4. 關閉 Xcode,然後開啟 (按兩下) 專案的 .xcworkspace 檔案來啟動 Xcode。如要稍後再開啟專案,請使用 .xcworkspace 檔案。

    手動安裝程式庫

    XCFramework 是用於安裝 Consumer SDK 的二進位套件。這個套件可用於多個平台,包括使用 M1 晶片組的機器。本指南說明如何手動將包含 Consumer SDK 的 XCFramework 新增至專案,並在 Xcode 中完成建構設定。

    下載 SDK 二進位檔和資源:

    1. 將壓縮的檔案解壓縮,以存取 XCFramework 和資源。

    2. 啟動 Xcode,然後開啟現有專案,或建立新專案。如果您是 iOS 新手,請建立新專案並選取 iOS 應用程式範本。

    3. 如果專案群組不存在架構群組,請在專案群組下建立一個架構群組。

    4. 如要安裝 Consumer SDK,請將 GoogleRidesharingConsumer.xcframework 檔案拖曳到專案「Frameworks、Library and Embedded Content」下方。畫面上出現提示時,請視需要選取「複製項目」。

    5. 將下載的 GoogleRidesharingConsumer.bundle 拖曳至 Xcode 專案的頂層目錄。系統提示時,請選取 Copy items if needed

    6. 從專案導覽器中選取專案,然後選擇應用程式的目標。

    7. 開啟「建構階段」分頁,然後在「連結二進位檔和程式庫的連結二進位檔」中,加入下列架構和程式庫 (如果尚未加入):

      • Accelerate.framework
      • CoreData.framework
      • CoreGraphics.framework
      • CoreImage.framework
      • CoreLocation.framework
      • CoreTelephony.framework
      • CoreText.framework
      • GLKit.framework
      • ImageIO.framework
      • libc++.tbd
      • libz.tbd
      • Metal.framework
      • OpenGLES.framework
      • QuartzCore.framework
      • SystemConfiguration.framework
      • UIKit.framework
    8. 選擇您的專案 (而非特定目標),然後開啟「Build Settings」分頁。在「Other Linker Flags」部分中,為偵錯和發布版本新增 -ObjC。如果您沒有看到這些設定,請將「Build Settings」列中的篩選器從「Basic」變更為「All」

    新增 Apple 隱私權資訊清單檔案

    Apple 要求在 App Store 上架應用程式,要求取得應用程式隱私權詳細資訊。如需最新資訊和其他資訊,請前往 Apple App Store 隱私權詳細資料頁面

    1. 下載 Consumer SDK for iOS 的隱私權資訊清單套件:GoogleRidesharingConsumerPrivacy
    2. 解壓縮檔案以存取 GoogleRidesharingConsumerPrivacy.bundle
    3. 使用這些方法,將 GoogleRidesharingConsumerPrivacy.bundle 新增至 Xcode 專案導覽器。確認應用程式的目標已勾選「新增至目標」方塊。PrivacyInfo 檔案一經新增,就會顯示在專案導覽工具中,方便你查看。
    4. Xcode 隱私權資訊螢幕擷取畫面
    5. 建立應用程式的封存檔案,並透過封存檔案產生隱私權報告,確認您已新增隱私權資訊清單。

    應用程式整合

    提供驗證權杖

    消費者應用程式要求 Fleet Engine 更新行程時,要求必須包含有效的存取權杖。為了授權及驗證這些要求,Consumer SDK 會呼叫符合 GMTCAuthorization 通訊協定的物件。這個物件負責提供必要的存取權杖。

    應用程式開發人員可以選擇產生權杖的產生方式,您的實作結果應能夠執行下列操作:

    • 從 HTTPS 伺服器擷取存取權杖 (可能為 JSON 格式)。
    • 剖析及快取權杖。
    • 請在權杖過期時重新整理。

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

    提供者 ID 與 Google Cloud 專案 ID 相同。詳情請參閱開始使用 Fleet Engine 一文。

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

    Swift

    /*
    
        *   SampleAccessTokenProvider.swift
     */
    import GoogleRidesharingConsumer
    
    private let providerURL = "INSERT_YOUR_TOKEN_PROVIDER_URL"
    
    class SampleAccessTokenProvider: NSObject, GMTCAuthorization {
      private struct AuthToken {
        // The cached trip token.
        let token: String
        // Keep track of when the token expires for caching.
        let expiration: TimeInterval
        // Keep track of the trip ID the cached token is for.
        let tripID: String
      }
    
      enum AccessTokenError: Error {
        case missingAuthorizationContext
        case missingData
      }
    
      private var authToken: AuthToken?
    
      func fetchToken(
        with authorizationContext: GMTCAuthorizationContext?,
        completion: @escaping GMTCAuthTokenFetchCompletionHandler
      ) {
        // Get the trip ID from the authorizationContext. This is set by the Consumer SDK.
        guard let authorizationContext = authorizationContext else {
          completion(nil, AccessTokenError.missingAuthorizationContext)
          return
        }
        let tripID = authorizationContext.tripID
    
        // If appropriate, use the cached token.
        if let authToken = authToken,
          authToken.expiration > Date.now.timeIntervalSince1970 && authToken.tripID == tripID
        {
          completion(authToken.token, nil)
          return
        }
    
        // Otherwise, try to fetch a new token from your server.
        let request = URLRequest(url: URL(string: providerURL))
        let task = URLSession.shared.dataTask(with: request) { [weak self] data, _, error in
          guard let strongSelf = self else { return }
          guard error == nil else {
            completion(nil, error)
            return
          }
    
          // Replace the following key values with the appropriate keys based on your
          // server's expected response.
          let tripTokenKey = "TRIP_TOKEN_KEY"
          let tokenExpirationKey = "TOKEN_EXPIRATION"
          guard let data = data,
            let fetchData = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
            let token = fetchData[tripTokenKey] as? String,
            let expiration = fetchData[tokenExpirationKey] as? Double
          else {
            completion(nil, AccessTokenError.missingData)
            return
          }
    
          strongSelf.authToken = AuthToken(token: token, expiration: expiration, tripID: tripID)
          completion(token, nil)
        }
        task.resume()
      }
    }
    

    Objective-C

    /*
    
        *   SampleAccessTokenProvider.h
     */
    #import <Foundation/Foundation.h>
    #import <GoogleRidesharingConsumer/GoogleRidesharingConsumer.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface SampleAccessTokenProvider : NSObject <GMTCAuthorization>
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    /*
    
        *   SampleAccessTokenProvider.m
     */
    #import "SampleAccessTokenProvider.h"
    #import "GoogleRidesharingConsumer/GoogleRidesharingConsumer.h"
    
    static NSString *const PROVIDER_URL = @"INSERT_YOUR_TOKEN_PROVIDER_URL";
    
    // SampleAccessTokenProvider.m
    @implementation SampleAccessTokenProvider {
      // The cached token with claims to the current trip.
      NSString *_cachedTripToken;
      // Keep track of the Trip ID the cached token is for.
      NSString *_lastKnownTripID;
      // Keep track of when tokens expire for caching.
      NSTimeInterval _tokenExpiration;
    }
    
    -   (void)fetchTokenWithContext:(nullable GMTCAuthorizationContext *)authorizationContext
                       completion:(nonnull GMTCAuthTokenFetchCompletionHandler)completion {
      // Get the trip ID from the authorizationContext. This is set by the Consumer SDK.
      NSString *tripID = authorizationContext.tripID;
    
      // Clear cached trip token if trip ID has changed.
      if (![_lastKnownTripID isEqual:tripID]) {
        _tokenExpiration = 0.0;
        _cachedTripToken = nil;
      }
      _lastKnownTripID = tripID;
    
      // Clear cached tripToken if it has expired.
      if ([[NSDate date] timeIntervalSince1970] > _tokenExpiration) {
        _cachedTripToken = nil;
      }
    
      // If appropriate, use the cached token.
      if (_cachedTripToken) {
        completion(_cachedTripToken, 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 *tripTokenKey = @"TRIP_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->_cachedTripToken = JSONResponse[tripTokenKey];
              completion(JSONResponse[tripTokenKey], 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
    

    應用程式初始化

    Swift

    /*
    
        *   AppDelegate.swift
     */
    import GoogleRidesharingConsumer
    import GoogleMaps
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
      func application(_ application: UIApplication,
          didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Register your API key for GMSServices.
        GMSServices.provideAPIKey(yourMapsAPIKey)
    
        // Set the instance of the SampleAccessTokenProvider.
        GMTCServices.setAccessTokenProvider(SampleAccessTokenProvider(), providerID: yourProviderID)
    
        // Other initialization code ...
        return true
      }
    }
    

    Objective-C

    /*
    
        *   AppDelegate.m
     */
    #import <GoogleMaps/GoogleMaps.h>
    #import <GoogleRidesharingConsumer/GoogleRidesharingConsumer.h>
    
    @implementation AppDelegate
    
    -   (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
      //Register your API key for GMSServices.
      [GMSServices provideAPIKey:yourMapsAPIKey];
    
      //Set the instance of the AccessTokenFactory.
      [GMTCServices setAccessTokenProvider:[[SampleAccessTokenProvider alloc] init]
                                providerID:yourProviderID];
    
      // Other initialization code ...
      return YES;
    }
    
    @end
    

    地圖檢視整合

    初始化地圖檢視

    以下範例說明如何初始化 GMTCMapView

    Swift

    /*
    
        *   MapViewController.swift
     */
    class ViewController: UIViewController, GMTCMapViewDelegate {
      private var rideSharingMap: GMTCMapView?
    
      override func viewDidLoad() {
        super.viewDidLoad()
    
        self.rideSharingMap = GMTCMapView(frame: UIScreen.main.bounds)
        self.rideSharingMap.delegate = self
        self.rideSharingMap?.settings.myLocationButton = true
        self.view.addSubview(self.rideSharingMap!)
        ...
      }
    

    Objective-C

    /*
    
        *   MapViewController.h
     */
    @interface MapViewController : UIViewController<GMTCMapViewDelegate>
    ...
    @end
    
    /*
    
        *   MapViewController.m
     */
    @implementation MapViewController
    
    -   (void)viewDidLoad {
      [super viewDidLoad];
      ...
      self.mapView = [[GMTCMapView alloc] initWithFrame:CGRectZero];
      self.mapView.settings.myLocationButton = YES;
      self.mapView.delegate = self;
      ...
    }
    
    ...
    
    @end
    

    處理地圖檢視事件

    以下範例說明如何實作委派來處理客戶狀態事件。

    Swift

    func mapViewDidInitialize(_ mapview: GMTCMapView) {
      // Handle the update to the state of the map view to browsing.
    }
    
    func mapView(_ mapView: GMSMapView, didTapConsumerMarker mapMarker: GMSMarker, markerType: GMTCMapViewMarkerType) -> Bool {
      // Handle the mapView marker was tapped.
    }
    

    Objective-C

    /*
    
        *   MapViewController.m
     */
    #pragma mark - GMTCMapViewDelegate implementation
    
    // Handle state update of map view.
    
    -   (void)mapViewDidInitializeCustomerState:(GMTCMapView *)mapview {
      // Handle the update to the state of the map view to browsing.
    }
    
    -   (void)mapView:(GMSMapView *)mapView
        didTapConsumerMarker:(nonnull GMSMarker *)mapMarker
                  markerType:(GMTCMapViewMarkerType)markerType {
      // Handle the mapView marker was tapped.
    }
    

    旅程分享

    載入檢視畫面後開始新的行程

    以下範例說明如何在檢視畫面載入後,立即共用歷程。您可以從 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.
    }
    

    處理錯誤

    若您訂閱 TriModel 並發生錯誤,可以實作委派方法 tripModel(_:didFailUpdateTripWithError:),取得 tripModel 的回呼。Fleet Engine 會產生符合 Google Cloud Error 標準的錯誤訊息。如要進一步瞭解詳細錯誤訊息定義和所有錯誤代碼,請參閱 Google Cloud 錯誤說明文件

    具體來說,如要監控行程,必須提供有效的驗證權杖。如果沒有有效的驗證憑證 (例如權杖已過期),將產生 401 UNAUTHENTICATED。如果呼叫端沒有呼叫特定 API 的權限 (例如,具備消費者角色的使用者嘗試呼叫 updateTrip),或在 JWT 權杖中沒有有效的 car_id/trip_id 要求,系統將會產生 403 PERMISSION_DENIED

    詳情請參閱「消費者 SDK 錯誤處理」。

    使用者介面自訂

    取得及設定自訂折線 UI 選項

    以下範例說明如何設定折線的自訂 UI 選項。

    Swift

    /** MapViewController.swift */
    
    func updatePolylineUIOptions() {
      // The polyline type that you would like to set custom UI options for.
      let customizablePolylineType = GMTCPolylineType.activeRoute
    
      let polylineStyleOptions = GMTCMutablePolylineStyleOptions()
      polylineStyleOptions.strokeWidth = 8.0
      polylineStyleOptions.strokeColor = .blue
      polylineStyleOptions.isVisible = true
      polylineStyleOptions.zIndex = 1000
      polylineStyleOptions.isGeodesic = true
      let coordinator = self.mapView.consumerMapStyleCoordinator
      coordinator.setPolylineStyleOptions(polylineStyleOptions, polylineType:customizablePolylineType)
    }
    

    Objective-C

    /** MapViewController.m */
    
    -   (void)updatePolylineUIOptions {
      // The polyline type that you would like to set custom UI options for.
      GMTCPolylineType customizablePolylineType = GMTCPolylineTypeActiveRoute;
    
      GMTCMutablePolylineStyleOptions *polylineStyleOptions =
          [[GMTCMutablePolylineStyleOptions alloc] init];
      polylineStyleOptions.strokeWidth = 8.0;
      polylineStyleOptions.strokeColor = [UIColor blueColor];
      polylineStyleOptions.isVisible = YES;
      polylineStyleOptions.zIndex = 1000;
      polylineStyleOptions.isGeodesic = YES;
      [[_mapView consumerMapStyleCoordinator] setPolylineStyleOptions:polylineStyleOptions
                                                    polylineType:customizablePolylineType];
    }
    

    取得及設定自訂標記 UI 選項

    以下範例說明如何為標記設定自訂 UI 選項。

    Swift

    /** MapViewController.swift */
    
    func updateMarkerUIOptions() {
      let customizableMarkerType = GMTCCustomizableMarkerType.tripVehicle
      let markerStyleOptions = GMTCMutableMarkerStyleOptions()
      markerStyleOptions.groundAnchor = groundAnchor
      markerStyleOptions.isVisible = true
      markerStyleOptions.icon = icon
      markerStyleOptions.zIndex = 100
      markerStyleOptions.isFlat = false
      let coordinator = self.mapView.consumerMapStyleCoordinator
      coordinator.setMarkerStyleOptions(markerStyleOptions, markerType: customizableMarkerType)
    }
    

    Objective-C

    /** MapViewController.m */
    
    -   (void)updateMarkerUIOptions {
      // The marker type that you would like to set custom UI options for.
      GMTCCustomizableMarkerType customizableMarkerType = GMTCCustomizableMarkerTypeTripVehicle;
    
      GMTCMutableMarkerStyleOptions *markerStyleOptions =
          [[GMTCMutableMarkerStyleOptions alloc] init];
      markerStyleOptions.groundAnchor = groundAnchor;
      markerStyleOptions.isVisible = YES;
      markerStyleOptions.icon = icon;
      markerStyleOptions.zIndex = 100;
      markerStyleOptions.isFlat = NO;
    
      [[_mapView consumerMapStyleCoordinator] setMarkerStyleOptions:markerStyleOptions markerType:customizableMarkerType];
    }
    

    調整相機縮放功能

    Maps SDK for iOS 中的「我的位置」按鈕會將相機置於裝置位置中心。

    如果有執行中的旅程分享工作階段,您可以將相機鏡頭聚焦在旅程中,而不只是聚焦在裝置位置。

    Consumer SDK 提供自動相機功能,此功能預設為啟用。鏡頭會放大,將焦點放在旅程分享路線和下一個行程路線控點。

    AutoCamera

    若要進一步控管相機行為,可以使用 isAllowCameraAutoUpdate 屬性停用或啟用自動相機功能。

    如需更多相機自訂功能,請參閱「Maps SDK for iOS 移動攝影機」一文。