現在の場所を選択して地図上に詳細を表示する

このチュートリアルでは、iOS アプリを作成して次のことを行う方法を説明します。

  • 現在のデバイスの位置情報を取得します。
  • デバイスが配置されている可能性が高い場所のリストを取得します。
  • 最も一致する場所についてユーザーにプロンプトを表示します。
  • 地図上にマーカーを表示します。

Places SDK for iOSMaps SDK for iOSApple Core Location フレームワークを使用して iOS アプリを作成するには、このチュートリアルを参考にしてください。

コードを入手する

GitHub から Google Maps SDK for iOS をダウンロードするか、そのクローンを作成します。

開発用プロジェクトをセットアップする

Places SDK for iOS と Maps SDK for iOS をインストールする手順は次のとおりです。

  1. Xcode バージョン 14.0 以降をダウンロードしてインストールします。
  2. CocoaPods をまだインストールしていない場合は、ターミナルから
    sudo gem install cocoapods
    を実行して macOS にインストールします。
  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 Maps Platform スタートガイドの手順に沿って、この両方のサービスで請求先アカウントとプロジェクトを設定します。
  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. デバイスを使用している場合は、位置情報サービスが有効になっていることを確認します。 シミュレータを使用している場合は、[機能] メニューからロケーションを選択します。
  3. Xcode で [Product/Run] メニュー オプション(またはプレイボタン アイコン)をクリックします。
    • Xcode がアプリをビルドし、デバイスまたはシミュレータで実行します。
    • 現在地の周囲に多数のマーカーが立った地図が表示されます。

トラブルシューティング:

  • 地図が表示されない場合は、上記の手順どおりに API キーを取得してアプリに追加していることをご確認ください。Xcode のデバッグ コンソールで、API キーに関するエラー メッセージを確認します。
  • API キーを iOS バンドル ID で制限している場合は、キーを編集してアプリのバンドル ID(com.google.examples.current-place-on-map)を追加します。
  • 位置情報サービスの権限リクエストが拒否された場合、地図は適切に表示されません。
    • デバイスを使用している場合は、[設定] / [一般] / [プライバシー] / [位置情報サービス] に移動して、位置情報サービスを再度有効にしてください。
    • シミュレータを使用している場合は、[シミュレータ/コンテンツと設定のリセット...] に移動します。
    次回アプリを実行するときは、位置情報サービスのプロンプトを承諾してください。
  • Wi-Fi または GPS の接続状態が良好であることを確認します。
  • アプリが起動しても地図が表示されない場合は、適切な位置情報の利用許可でプロジェクトの Info.plist を更新していることを確認します。権限の処理について詳しくは、後述のアプリで位置情報の利用許可をリクエストするためのガイドをご覧ください。
  • ログを表示してアプリをデバッグするには、Xcode デバッグツールを使用します。

コードを理解する

このパートでは、同様のアプリの作成方法を理解するうえで、current-place-on-mapアプリの最も重要な部分について説明します。

current-place-on-map アプリは 2 つのビュー コントローラを備えています。1 つは、ユーザーが現在選択されている場所を示す地図を表示するためのものです。もう 1 つは、ユーザーに候補となる場所のリストを表示するためのものです。各ビュー コントローラには、可能性が高い場所のリストを追跡する変数(likelyPlaces)と、ユーザーの選択を示す変数(selectedPlace)が同じ変数があります。ビュー間のナビゲーションは、セグを使用して実現されます。

位置情報の利用許可をリクエストする

アプリは、位置情報サービスの使用についてユーザーに同意を求める必要があります。そのためには、アプリの Info.plist ファイルに NSLocationAlwaysUsageDescription キーを追加し、各キーの値に、アプリでの位置情報の使用方法を示す文字列を設定します。

ビジネス マネージャーの設定

CLLocationManager を使用すると、デバイスの現在地を確認し、デバイスが新しい場所に移動したときに定期的な更新をリクエストできます。このチュートリアルでは、デバイスの位置情報を取得するために必要なコードを提供します。詳しくは、Apple Developer Documentation のユーザーの位置情報の取得に関するガイドをご覧ください。

  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 を使用して、ユーザーの現在地に基づいて上位 5 つの場所の可能性を取得し、そのリストを 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];
}
      

これで完了です。ユーザーが現在地を選択し、その結果を Google マップに表示する iOS アプリが作成されました。この過程では、Places SDK for iOSMaps SDK for iOSApple Core Location Framework の使用方法を学びました。