在地圖上選取「目前位置」和「顯示詳細資料」

本教學課程將說明如何建構 iOS 應用程式,以便:

  • 取得目前裝置的位置資訊。
  • 取得裝置可能所在的位置清單。
  • 提示使用者找到最符合的地點。
  • 在地圖上顯示標記。

按照本教學課程,使用 Places SDK for iOSMaps SDK for iOSApple Core Location Framework 建構 iOS 應用程式。

取得程式碼

從 GitHub 複製或下載 Google Maps SDK for iOS

設定您的開發專案

請按照下列步驟安裝 Places SDK for iOS 和 Maps SDK for iOS:

  1. 下載並安裝 Xcode 13.0 以上版本。
  2. 如果您尚未安裝 CocoaPods,請在終端機中執行下列指令,以便在 macOS 中安裝:
    sudo gem install cocoapods
  3. 在儲存範例存放區的目錄中 (取得程式碼),前往 tutorials/current-place-on-map 目錄。
  4. 執行 pod install 指令。這麼做會安裝在 Podfile 中指定的 API 及其所有依附元件。
  5. 開啟 (按兩下) 專案的 current-place-on-map.xcworkspace 以 Xcode 開啟專案。您必須使用 .xcworkspace 檔案才能開啟專案。

如需詳細的安裝操作說明,請參閱 入門指南 (地圖)入門指南 (地點)

啟用必要的 API 並取得 API 金鑰

如想完成本教學課程,您必須取得獲授權使用 Maps SDK for iOSPlaces API 的 Google API 金鑰。

  1. 請依照開始使用 Google 地圖平台一文中的指示,設定帳單帳戶和同時由這兩項產品啟用的專案。
  2. 按照取得 API 金鑰的操作說明,為您先前設定的開發專案建立 API 金鑰。

在應用程式中加入 API 金鑰

Swift

將 API 金鑰新增到您的 AppDelegate.swift 中,如下所示:

  1. 請注意,我們已將下列匯入陳述式新增至檔案:
    import GooglePlaces
    import GoogleMaps
  2. application(_:didFinishLaunchingWithOptions:) 方法中編輯以下這行程式碼,將 YOUR_API_KEY 替換為您的 API 金鑰:
    GMSPlacesClient.provideAPIKey("YOUR_API_KEY")
    GMSServices.provideAPIKey("YOUR_API_KEY")

Objective-C

將 API 金鑰新增到您的 AppDelegate.m 中,如下所示:

  1. 請注意,我們已將下列匯入陳述式新增至檔案:
    @import GooglePlaces;
    @import GoogleMaps;
  2. application(_:didFinishLaunchingWithOptions:) 方法中編輯以下這行程式碼,將 YOUR_API_KEY 替換為您的 API 金鑰:
    [GMSPlacesClient provideAPIKey: @"YOUR_API_KEY"]
    [GMSServices provideAPIKey: @"YOUR_API_KEY"]

建構並執行應用程式

  1. 將 iOS 裝置連接到電腦,或是從 Xcode 配置彈出式選單中選取模擬工具
  2. 如果您使用的是裝置,請確認已啟用定位服務。 如果使用的是模擬工具,請從「Features」(功能) 選單中選取所需的位置。
  3. 在 Xcode 中,按一下「Product/Run」(產品/執行) 選單選項 (或播放按鈕圖示),

Xcode 會建構應用程式,然後在裝置或模擬器上執行應用程式。

您應該會看到一份地圖,上面有數個以您目前位置為中心的標記,與本頁的圖片類似。

疑難排解:

  • 如未看到地圖,請檢查您是否已按照上述步驟取得 API 金鑰,並將其加入應用程式。查看 Xcode 的偵錯控制台,瞭解 API 金鑰的錯誤訊息。
  • 如果您已透過 iOS 軟體包 ID 限制 API 金鑰,請編輯金鑰以新增應用程式的軟體包 ID:com.google.examples.current-place-on-map
  • 如果定位服務的權限要求遭拒,地圖就無法正確顯示。
    • 如果您使用的是裝置,請前往設定/一般/隱私權/定位服務,然後重新啟用定位服務。
    • 如果您使用的是模擬工具,請參閱模擬工具/重設內容和設定...
    下次執行應用程式時,請務必接受定位服務提示。
  • 確認你的 Wi-Fi 或 GPS 連線狀況良好。
  • 如果應用程式已啟動,但未顯示地圖,請務必將專案的 Info.plist 更新為適當的位置存取權。如要進一步瞭解權限處理機制,請參閱下方的在應用程式中要求位置存取權指南。
  • 使用 Xcode 偵錯工具查看記錄檔並為應用程式偵錯。

瞭解程式碼

本教學課程這一段將說明目前地圖在地圖上應用程式最重要的部分,協助您瞭解如何建構類似的應用程式。

目前的地點地圖應用程式提供兩個檢視畫面控制器:一個顯示使用者目前選取的地圖,另一個則顯示使用者可能會選擇的地點清單。請注意,每個檢視畫面控制器都具有相同的變數,用於追蹤可能的地點清單 (likelyPlaces) 以及使用者選擇 (selectedPlace)。不同檢視畫面的導覽是使用 segues 完成。

要求位置存取權

應用程式必須提示使用者同意使用定位服務。做法是在應用程式的 Info.plist 檔案中納入 NSLocationAlwaysUsageDescription 鍵,並將每個金鑰的值設為字串,說明應用程式預計如何使用位置資料。

設定位置管理員

使用 CLLocationManager 找出裝置目前的位置,並在裝置移至新位置時要求定期更新。本教學課程會提供取得裝置位置所需的程式碼。詳情請參閱 Apple 開發人員說明文件中的取得使用者位置指南。

  1. 在類別層級宣告位置管理員、目前位置、地圖檢視、地點用戶端和預設縮放等級。
  2. Swift

    var locationManager: CLLocationManager!
    var currentLocation: CLLocation?
    var mapView: GMSMapView!
    var placesClient: GMSPlacesClient!
    var preciseLocationZoomLevel: Float = 15.0
    var approximateLocationZoomLevel: Float = 10.0
          

    Objective-C

    CLLocationManager *locationManager;
    CLLocation * _Nullable currentLocation;
    GMSMapView *mapView;
    GMSPlacesClient *placesClient;
    float preciseLocationZoomLevel;
    float approximateLocationZoomLevel;
          
  3. 初始化 viewDidLoad() 中的位置管理員和 GMSPlacesClient
  4. Swift

    // Initialize the location manager.
    locationManager = CLLocationManager()
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.requestWhenInUseAuthorization()
    locationManager.distanceFilter = 50
    locationManager.startUpdatingLocation()
    locationManager.delegate = self
    
    placesClient = GMSPlacesClient.shared()
          

    Objective-C

    // Initialize the location manager.
    locationManager = [[CLLocationManager alloc] init];
    locationManager.desiredAccuracy = kCLLocationAccuracyBest;
    [locationManager requestWhenInUseAuthorization];
    locationManager.distanceFilter = 50;
    [locationManager startUpdatingLocation];
    locationManager.delegate = self;
    
    placesClient = [GMSPlacesClient sharedClient];
          
  5. 宣告變數來存放可能的地點清單,以及使用者選取的地點。
  6. Swift

    // An array to hold the list of likely places.
    var likelyPlaces: [GMSPlace] = []
    
    // The currently selected place.
    var selectedPlace: GMSPlace?
          

    Objective-C

    // An array to hold the list of likely places.
    NSMutableArray<GMSPlace *> *likelyPlaces;
    
    // The currently selected place.
    GMSPlace * _Nullable selectedPlace;
          
  7. 使用擴充功能子句新增委派,以處理位置管理員的事件。
  8. Swift

    // Delegates to handle events for the location manager.
    extension MapViewController: CLLocationManagerDelegate {
    
      // Handle incoming location events.
      func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let location: CLLocation = locations.last!
        print("Location: \(location)")
    
        let zoomLevel = locationManager.accuracyAuthorization == .fullAccuracy ? preciseLocationZoomLevel : approximateLocationZoomLevel
        let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude,
                                              longitude: location.coordinate.longitude,
                                              zoom: zoomLevel)
    
        if mapView.isHidden {
          mapView.isHidden = false
          mapView.camera = camera
        } else {
          mapView.animate(to: camera)
        }
    
        listLikelyPlaces()
      }
    
      // Handle authorization for the location manager.
      func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        // Check accuracy authorization
        let accuracy = manager.accuracyAuthorization
        switch accuracy {
        case .fullAccuracy:
            print("Location accuracy is precise.")
        case .reducedAccuracy:
            print("Location accuracy is not precise.")
        @unknown default:
          fatalError()
        }
    
        // Handle authorization status
        switch status {
        case .restricted:
          print("Location access was restricted.")
        case .denied:
          print("User denied access to location.")
          // Display the map using the default location.
          mapView.isHidden = false
        case .notDetermined:
          print("Location status not determined.")
        case .authorizedAlways: fallthrough
        case .authorizedWhenInUse:
          print("Location status is OK.")
        @unknown default:
          fatalError()
        }
      }
    
      // Handle location manager errors.
      func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        locationManager.stopUpdatingLocation()
        print("Error: \(error)")
      }
    }
          

    Objective-C

    // Delegates to handle events for the location manager.
    #pragma mark - CLLocationManagerDelegate
    
    // Handle incoming location events.
    - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
    {
      CLLocation *location = locations.lastObject;
      NSLog(@"Location: %@", location);
    
      float zoomLevel = locationManager.accuracyAuthorization == CLAccuracyAuthorizationFullAccuracy ? preciseLocationZoomLevel : approximateLocationZoomLevel;
      GMSCameraPosition * camera = [GMSCameraPosition cameraWithLatitude:location.coordinate.latitude
                                                               longitude:location.coordinate.longitude
                                                                    zoom:zoomLevel];
    
      if (mapView.isHidden) {
        mapView.hidden = NO;
        mapView.camera = camera;
      } else {
        [mapView animateToCameraPosition:camera];
      }
    
      [self listLikelyPlaces];
    }
    
    // Handle authorization for the location manager.
    - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
    {
      // Check accuracy authorization
      CLAccuracyAuthorization accuracy = manager.accuracyAuthorization;
      switch (accuracy) {
        case CLAccuracyAuthorizationFullAccuracy:
          NSLog(@"Location accuracy is precise.");
          break;
        case CLAccuracyAuthorizationReducedAccuracy:
          NSLog(@"Location accuracy is not precise.");
          break;
      }
    
      // Handle authorization status
      switch (status) {
        case kCLAuthorizationStatusRestricted:
          NSLog(@"Location access was restricted.");
          break;
        case kCLAuthorizationStatusDenied:
          NSLog(@"User denied access to location.");
          // Display the map using the default location.
          mapView.hidden = NO;
        case kCLAuthorizationStatusNotDetermined:
          NSLog(@"Location status not determined.");
        case kCLAuthorizationStatusAuthorizedAlways:
        case kCLAuthorizationStatusAuthorizedWhenInUse:
          NSLog(@"Location status is OK.");
      }
    }
    
    // Handle location manager errors.
    - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
    {
      [manager stopUpdatingLocation];
      NSLog(@"Error: %@", error.localizedDescription);
    }
          

新增地圖

建立地圖,然後將該地圖新增至主要檢視畫面控制器的 viewDidLoad() 中的檢視畫面。在收到位置更新之前,地圖會一直處於隱藏狀態 (系統會在 CLLocationManagerDelegate 擴充功能中處理位置更新)。

Swift

// A default location to use when location permission is not granted.
let defaultLocation = CLLocation(latitude: -33.869405, longitude: 151.199)

// Create a map.
let zoomLevel = locationManager.accuracyAuthorization == .fullAccuracy ? preciseLocationZoomLevel : approximateLocationZoomLevel
let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude,
                                      longitude: defaultLocation.coordinate.longitude,
                                      zoom: zoomLevel)
mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
mapView.settings.myLocationButton = true
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.isMyLocationEnabled = true

// Add the map to the view, hide it until we've got a location update.
view.addSubview(mapView)
mapView.isHidden = true
      

Objective-C

// A default location to use when location permission is not granted.
CLLocationCoordinate2D defaultLocation = CLLocationCoordinate2DMake(-33.869405, 151.199);

// Create a map.
float zoomLevel = locationManager.accuracyAuthorization == CLAccuracyAuthorizationFullAccuracy ? preciseLocationZoomLevel : approximateLocationZoomLevel;
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:defaultLocation.latitude
                                                        longitude:defaultLocation.longitude
                                                             zoom:zoomLevel];
mapView = [GMSMapView mapWithFrame:self.view.bounds camera:camera];
mapView.settings.myLocationButton = YES;
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
mapView.myLocationEnabled = YES;

// Add the map to the view, hide it until we've got a location update.
[self.view addSubview:mapView];
mapView.hidden = YES;
      

提示使用者選取目前位置

使用 Places SDK for iOS 根據使用者目前位置取得前五名可能的可能性,並在 UITableView 中顯示清單。當使用者選取地點時,在地圖中加入標記。

  1. 取得用來填入 UITableView 的可能地點清單,讓使用者可以選取其目前所在的位置。
  2. Swift

    // Populate the array with the list of likely places.
    func listLikelyPlaces() {
      // Clean up from previous sessions.
      likelyPlaces.removeAll()
    
      let placeFields: GMSPlaceField = [.name, .coordinate]
      placesClient.findPlaceLikelihoodsFromCurrentLocation(withPlaceFields: placeFields) { (placeLikelihoods, error) in
        guard error == nil else {
          // TODO: Handle the error.
          print("Current Place error: \(error!.localizedDescription)")
          return
        }
    
        guard let placeLikelihoods = placeLikelihoods else {
          print("No places found.")
          return
        }
    
        // Get likely places and add to the list.
        for likelihood in placeLikelihoods {
          let place = likelihood.place
          self.likelyPlaces.append(place)
        }
      }
    }
          

    Objective-C

    // Populate the array with the list of likely places.
    - (void) listLikelyPlaces
    {
      // Clean up from previous sessions.
      likelyPlaces = [NSMutableArray array];
    
      GMSPlaceField placeFields = GMSPlaceFieldName | GMSPlaceFieldCoordinate;
      [placesClient findPlaceLikelihoodsFromCurrentLocationWithPlaceFields:placeFields callback:^(NSArray<GMSPlaceLikelihood *> * _Nullable likelihoods, NSError * _Nullable error) {
        if (error != nil) {
          // TODO: Handle the error.
          NSLog(@"Current Place error: %@", error.localizedDescription);
          return;
        }
    
        if (likelihoods == nil) {
          NSLog(@"No places found.");
          return;
        }
    
        for (GMSPlaceLikelihood *likelihood in likelihoods) {
          GMSPlace *place = likelihood.place;
          [likelyPlaces addObject:place];
        }
      }];
    }
          
  3. 開啟新的檢視畫面,以向使用者顯示可能的地點。使用者輕觸「取得地點」時,我們會向您顯示新的檢視畫面,並向使用者顯示可供選擇的地點清單。prepare 函式會將 PlacesViewController 更新為目前可能地點清單,並在執行 Segue 時自動呼叫。
  4. Swift

    // Prepare the segue.
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
      if segue.identifier == "segueToSelect" {
        if let nextViewController = segue.destination as? PlacesViewController {
          nextViewController.likelyPlaces = likelyPlaces
        }
      }
    }
          

    Objective-C

    // Prepare the segue.
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
      if ([segue.identifier isEqualToString:@"segueToSelect"]) {
        if ([segue.destinationViewController isKindOfClass:[PlacesViewController class]]) {
          PlacesViewController *placesViewController = (PlacesViewController *)segue.destinationViewController;
          placesViewController.likelyPlaces = likelyPlaces;
        }
      }
    }
          
  5. PlacesViewController 中,使用 UITableViewDataSource 委派擴充功能,以最有可能出現的地點清單填入資料表。
  6. Swift

    // Populate the table with the list of most likely places.
    extension PlacesViewController: UITableViewDataSource {
      func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return likelyPlaces.count
      }
    
      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
        let collectionItem = likelyPlaces[indexPath.row]
    
        cell.textLabel?.text = collectionItem.name
    
        return cell
      }
    }
          

    Objective-C

    #pragma mark - UITableViewDataSource
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
      return self.likelyPlaces.count;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
      return [tableView dequeueReusableCellWithIdentifier:cellReuseIdentifier forIndexPath:indexPath];
    }
    @end
          
  7. 使用 UITableViewDelegate 委派擴充功能處理使用者的選擇。
  8. Swift

    class PlacesViewController: UIViewController {
    
      // ...
    
      // Pass the selected place to the new view controller.
      override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "unwindToMain" {
          if let nextViewController = segue.destination as? MapViewController {
            nextViewController.selectedPlace = selectedPlace
          }
        }
      }
    }
    
    // Respond when a user selects a place.
    extension PlacesViewController: UITableViewDelegate {
      func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        selectedPlace = likelyPlaces[indexPath.row]
        performSegue(withIdentifier: "unwindToMain", sender: self)
      }
    
      // Adjust cell height to only show the first five items in the table
      // (scrolling is disabled in IB).
      func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return self.tableView.frame.size.height/5
      }
    
      // Make table rows display at proper height if there are less than 5 items.
      func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        if (section == tableView.numberOfSections - 1) {
          return 1
        }
        return 0
      }
    }
          

    Objective-C

    @interface PlacesViewController () <UITableViewDataSource, UITableViewDelegate>
    // ...
    
    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
    
    }
    
    #pragma mark - UITableViewDelegate
    
    // Respond when a user selects a place.
    -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
      self.selectedPlace = [self.likelyPlaces objectAtIndex:indexPath.row];
      [self performSegueWithIdentifier:@"unwindToMain" sender:self];
    }
    
    // Adjust cell height to only show the first five items in the table
    // (scrolling is disabled in IB).
    -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
      return self.tableView.frame.size.height/5;
    }
    
    // Make table rows display at proper height if there are less than 5 items.
    -(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
    {
      if (section == tableView.numberOfSections - 1) {
        return 1;
      }
      return 0;
    }
          

在地圖中加入標記

當使用者做出選擇時,使用旋開式雪橇返回上一個檢視畫面,並將標記新增至地圖。返回主要檢視控制器後,系統會自動呼叫 unwindToMain IBAction。

Swift

// Update the map once the user has made their selection.
@IBAction func unwindToMain(segue: UIStoryboardSegue) {
  // Clear the map.
  mapView.clear()

  // Add a marker to the map.
  if let place = selectedPlace {
    let marker = GMSMarker(position: place.coordinate)
    marker.title = selectedPlace?.name
    marker.snippet = selectedPlace?.formattedAddress
    marker.map = mapView
  }

  listLikelyPlaces()
}
      

Objective-C

// Update the map once the user has made their selection.
- (void) unwindToMain:(UIStoryboardSegue *)segue
{
  // Clear the map.
  [mapView clear];

  // Add a marker to the map.
  if (selectedPlace != nil) {
    GMSMarker *marker = [GMSMarker markerWithPosition:selectedPlace.coordinate];
    marker.title = selectedPlace.name;
    marker.snippet = selectedPlace.formattedAddress;
    marker.map = mapView;
  }

  [self listLikelyPlaces];
}
      

恭喜!您已經建構了一個 iOS 應用程式,可讓使用者選擇目前所在位置,並在 Google 地圖上顯示結果。在這個步驟中,您已學會如何使用 Places SDK for iOSMaps SDK for iOSApple Core Location Framework