コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

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

このチュートリアルでは、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 バージョン 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 Maps Platform スタートガイドの手順に沿って、請求先アカウントと、これら 2 つのプロダクトを有効にしたプロジェクトを設定します。
  2. API キーを取得するの手順に沿って、以前に設定した開発プロジェクト用の API キーを作成します。

アプリケーションへの API キーの追加

Swift

次のように、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")

Objective-C

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

  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. デバイスを使用している場合は、位置情報サービスが有効になっていることを確認します。 シミュレータを使用している場合は、[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 デバッグツールを使用します。

コードについて

チュートリアルのこのパートでは、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 を使用して、ユーザーの現在地を基におすすめの場所の候補の上位 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 フレームワークを使用する方法を学びました。