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

このチュートリアルでは、iOS アプリをビルドする方法について説明します。

  • デバイスの現在の位置情報を取得します。
  • デバイスがある可能性が高い場所のリストを取得します。
  • 最適な場所が提示されます。
  • 地図上にマーカーを表示します。

Places SDK for iOSMaps SDK for iOSApple Core Location フレームワークを使用して iOS アプリを作成します。

コードの取得

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

開発プロジェクトを設定する

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. デバイスを使用している場合は、位置情報サービスが有効になっていることを確認します。 シミュレータを使用している場合は、[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 アプリには 2 つのビュー コントローラがあります。一つはユーザーが現在選択した場所を表示する地図を表示するもので、もう一つは選択できる場所のリストをユーザーに表示するコントローラです。各ビュー コントローラには、可能性の高い場所のリストをトラッキングするため(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 フレームワークの使い方を学びました。