在地圖上選取目前位置並顯示相關詳細資料

本教學課程說明如何建構 iOS 應用程式,以執行下列操作:

  • 取得目前裝置位置。
  • 取得裝置可能所在的位置清單。
  • 提示使用者選出最合適的地點。
  • 在地圖上顯示標記。

請按照本教學課程,使用 Places SDK for iOSMaps SDK for iOSApple Core 位置架構建立 iOS 應用程式。

取得程式碼

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

設定開發專案

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

  1. 下載並安裝 Xcode 14.0 版或更新版本。
  2. 如果您還沒有 CocoaPods,請透過終端機執行下列指令,以便在 macOS 上安裝:
    sudo gem install cocoapods
  3. 在您儲存範例存放區 (取得程式碼) 的目錄中,前往 tutorials/current-place-on-map 目錄。
  4. 執行 pod install 指令。這樣做會安裝 Podfile 中指定的 API 及其所有依附元件。
  5. 執行 pod outdated,比較已安裝的 Pod 版本與任何新的更新。如果偵測到新版本,請執行 pod update 以更新 Podfile 並安裝最新的 SDK。詳情請參閱 CocoaPods 指南
  6. 開啟 (按兩下) 專案的 current-place-on-map.xcworkspace 後,以 Xcode 開啟專案。您必須使用 .xcworkspace 檔案開啟專案。

如需詳細的安裝操作說明,請參閱「開始使用 (地圖)」和「入門指南 (地點)」。

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

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

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

在應用程式中加入 API 金鑰

將 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")

建構並執行應用程式

  1. 將 iOS 裝置連結至電腦,或從 Xcode 配置彈出式視窗選取模擬工具
  2. 如果您使用裝置,請務必啟用定位服務。 如果您使用模擬工具,請從「Features」(功能) 選單中選取位置。
  3. 在 Xcode 中,按一下「Product/Run」選單選項 (或播放按鈕圖示),
    • Xcode 會建構應用程式,然後在裝置或模擬器上執行應用程式。
    • 您應該會看到一份地圖,上面有數個以您目前位置為中心的標記。

疑難排解:

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

瞭解程式碼

本教學課程這一段將說明current-place-on-map應用程式最重要的部分,協助您瞭解如何建構類似的應用程式。

「目前位置上」current-place-on-map應用程式有兩個檢視控制器:一個用來顯示使用者目前所選地點的地圖,另一個則用於向使用者顯示可能的地點清單。請注意,每個檢視畫面控制器都有相同的變數,用於追蹤可能位置清單 (likelyPlaces) 及表示使用者選擇項目 (selectedPlace)。檢視畫面之間的導覽功能會使用半個

要求位置存取權

應用程式必須提示使用者同意使用定位服務。方法是在應用程式的 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. 開啟新的檢視畫面,向使用者顯示可能的地點。當使用者輕觸「Get Place」時,我們就會將其視為新的檢視畫面,並向使用者顯示一個可能的地點清單。prepare 函式會依據目前可能的地點清單更新 PlacesViewController,並在執行單菜時自動呼叫。
  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 位置架構