Selecionar o lugar atual e exibir detalhes em um mapa

Este tutorial mostra como criar um aplicativo iOS para:

  • Ver a localização atual do dispositivo.
  • Veja uma lista dos lugares em que o dispositivo provavelmente está.
  • Solicitar ao usuário a melhor correspondência de lugar.
  • Mostrar um marcador no mapa.

Siga este tutorial para criar um app iOS usando o SDK do Places para iOS, o SDK do Maps para iOS e o framework de localização principal da Apple.

Como conseguir o código

Clone ou faça o download do SDK do Google Maps para iOS no GitHub.

Como configurar o projeto de desenvolvimento

Siga estas etapas para instalar o SDK do Places para iOS e o SDK do Maps para iOS:

  1. Faça o download e instale o Xcode versão 14.0 (link em inglês) ou mais recente.
  2. Se você ainda não tiver o CocoaPods, instale-o no macOS executando o seguinte comando no terminal:
    sudo gem install cocoapods
  3. No diretório em que você salvou o repositório de amostra (Acessar o código), acesse o diretório tutorials/current-place-on-map.
  4. Execute o comando pod install. Isso instala as APIs especificadas no Podfile, com todas as dependências que elas possam ter.
  5. Execute pod outdated para comparar a versão do pod instalada com as novas atualizações. Se uma nova versão for detectada, execute pod update para atualizar o Podfile e instalar o SDK mais recente. Para mais detalhes, consulte o Guia do CocoaPods (em inglês).
  6. Abra (clique duas vezes) o current-place-on-map.xcworkspace do projeto para abri-lo no Xcode. Use o arquivo .xcworkspace para abrir o projeto.

Para instruções detalhadas de instalação, consulte Primeiros passos (Maps) e Primeiros passos (Places).

Como ativar as APIs necessárias e gerar uma chave de API

Para concluir este tutorial, você precisa de uma chave de API do Google que possa usar o SDK do Maps para iOS e a API Places.

  1. Siga as instruções em Primeiros passos na Plataforma Google Maps para configurar uma conta de faturamento e um projeto ativado para esses dois produtos.
  2. Siga as instruções em Conseguir uma chave de API para criar uma chave de API para o projeto de desenvolvimento que você configurou anteriormente.

Como adicionar a chave de API ao aplicativo

Inclua sua chave de API ao AppDelegate.swift da seguinte maneira:

  1. Observe que a seguinte instrução de importação foi adicionada ao arquivo:
    import GooglePlaces
    import GoogleMaps
  2. Edite a seguinte linha no método application(_:didFinishLaunchingWithOptions:), substituindo YOUR_API_KEY pela chave de API:
    GMSPlacesClient.provideAPIKey("YOUR_API_KEY")
    GMSServices.provideAPIKey("YOUR_API_KEY")

Como criar e executar o app

  1. Conecte um dispositivo iOS ao computador ou selecione um simulador no menu pop-up do esquema do Xcode.
  2. Se você estiver usando um dispositivo, verifique se os serviços de localização estão ativados. Se você estiver usando um simulador, selecione um local no menu Recursos.
  3. No Xcode, clique na opção de menu Product/Run (ou no ícone do botão de reprodução).
    • O Xcode cria o app e o executa no dispositivo ou no simulador.
    • Você verá um mapa com vários marcadores centralizados ao redor do seu local atual.

Solução de problemas:

  • Se o mapa não aparecer, verifique se você recebeu uma chave de API e a incluiu no app, conforme descrito acima. Verifique se há mensagens de erro no console de depuração do Xcode.
  • Se você restringiu a chave de API pelo identificador do pacote do iOS, edite a chave para adicionar o identificador do pacote ao app: com.google.examples.current-place-on-map.
  • O mapa não será exibido corretamente se a solicitação de permissões para serviços de localização for recusada.
    • Se você estiver usando um dispositivo, acesse Configurações/Geral/Privacidade/Serviços de localização e reative os Serviços de localização.
    • Se você estiver usando um simulador, acesse Simulator/Redefinir conteúdo e configurações...
    Na próxima vez que o app for executado, aceite o prompt de serviços de localização.
  • Verifique se você tem uma boa conexão Wi-Fi ou GPS.
  • Se o app for iniciado, mas nenhum mapa for exibido, verifique se você atualizou o arquivo Info.plist do seu projeto com as permissões de localização adequadas. Para mais informações sobre o gerenciamento de permissões, consulte o guia abaixo sobre como solicitar permissões de localização no app.
  • Use as ferramentas de depuração de Xcode para visualizar registros e depurar o app.

Entender o código

Nesta parte do tutorial, explicamos as partes mais importantes do app current-place-on-map para ajudar você a entender como criar um app semelhante.

O app current-place-on-map apresenta dois controladores de visualização: um para exibir um mapa que mostra o lugar atualmente selecionado pelo usuário e outro para apresentar ao usuário uma lista de possíveis lugares para escolher. Cada controlador de visualização tem as mesmas variáveis para rastrear a lista de lugares prováveis (likelyPlaces) e para indicar a seleção do usuário (selectedPlace). A navegação entre visualizações é realizada usando segues.

Solicitando permissão de localização

O app precisa solicitar ao usuário consentimento para usar os Serviços de localização. Para fazer isso, inclua a chave NSLocationAlwaysUsageDescription no arquivo Info.plist do app e defina o valor de cada chave como uma string que descreve como o app pretende usar os dados de localização.

Como configurar o administrador de local

Use CLLocationManager para encontrar a localização atual do dispositivo e solicitar atualizações regulares quando o dispositivo se mover para um novo local. Neste tutorial, fornecemos o código necessário para saber a localização do dispositivo. Para mais detalhes, consulte o guia sobre Como conseguir a localização do usuário na Documentação do desenvolvedor da Apple.

  1. Declare o gerenciador de localização, a localização atual, a visualização de mapa, o cliente do Places e o nível de zoom padrão no nível da classe.
  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. Inicialize o gerenciador de local e o GMSPlacesClient em viewDidLoad().
  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. Declare variáveis para manter a lista de locais prováveis e o local selecionado pelo usuário.
  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. Adicione delegados para gerenciar eventos do gerenciador de local usando uma cláusula de extensão.
  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);
    }
          

Adicionar um mapa

Crie um mapa e adicione-o à visualização em viewDidLoad() no controlador de visualização principal. O mapa fica oculto até que uma atualização de local seja recebida (as atualizações de local são tratadas na extensão 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;
      

Solicitar que o usuário selecione o lugar atual

Use o SDK do Places para iOS para ver as cinco principais probabilidades de localização com base na localização atual do usuário e apresentar a lista em uma UITableView. Quando o usuário selecionar um lugar, adicione um marcador ao mapa.

  1. Receba uma lista de lugares prováveis para preencher um UITableView, em que o usuário pode selecionar o lugar em que está localizado no momento.
  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. Abra uma nova visualização para apresentar lugares prováveis ao usuário. Quando o usuário toca em "Get Place", passamos para uma nova visualização e mostramos a ele uma lista de possíveis lugares para escolher. A função prepare atualiza PlacesViewController com a lista de lugares prováveis atuais e é chamada automaticamente quando uma execução é realizada.
  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. Em PlacesViewController, preencha a tabela usando a lista de locais mais prováveis, usando a extensão delegada 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. Gerencie a seleção do usuário usando a extensão delegada 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;
    }
          

Como adicionar um marcador ao mapa

Quando o usuário fizer uma seleção, use uma sequência de desfazer para retornar à visualização anterior e adicione o marcador ao mapa. A IBAction unwindToMain é chamada automaticamente ao retornar ao controlador de visualização principal.

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];
}
      

Parabéns! Você criou um app para iOS que permite ao usuário escolher o lugar atual e mostra o resultado em um mapa do Google. Ao fazer isso, você aprendeu a usar o SDK do Places para iOS, o SDK do Maps para iOS e o framework de localização principal da Apple.