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

このチュートリアルでは、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 をまだお持ちでない場合は、ターミナルから次のコマンドを実行して 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 Maps Platform スタートガイドの手順に沿って、請求先アカウントとプロジェクトを設定し、両方のプロダクトで有効にします。
  2. API キーを取得するの手順に沿って、先ほどセットアップした開発プロジェクト用の API キーを作成します。

アプリケーションに API キーを追加する

次のように、API キーを AppDelegate.swift に追加します。

  1. 次の import ステートメントがファイルに追加されていることに注意してください。
    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)を追加します。
  • 位置情報サービスに関する権限リクエストが拒否された場合、地図は正しく表示されません。
    • デバイスを使用している場合は、[設定] / [全般] / [プライバシー] / [位置情報サービス] に移動し、位置情報サービスを再度有効にします。
    • シミュレータを使用している場合は、[Simulator/Reset Content and Settings...] に移動します。
    アプリを次回実行したときに、位置情報サービスのプロンプトに同意してください。
  • 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 ドキュメントのユーザーの位置情報の取得に関するガイドをご覧ください。

  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. 新しいビューを開いて、考えられる場所をユーザーに表示します。ユーザーが [場所を取得] をタップすると、新しいビューに移動し、選択できる場所のリストがユーザーに表示されます。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 フレームワークの使用方法を学びました。