Selecciona el lugar actual y muestra información en un mapa

En este instructivo, se muestra cómo compilar una app para iOS con los siguientes objetivos:

  • Obtén la ubicación actual del dispositivo.
  • Obtén una lista de los lugares donde es probable que se encuentre el dispositivo.
  • Solicita al usuario la mejor coincidencia de lugar.
  • Muestra un marcador en el mapa.

Sigue este instructivo y compila una app para iOS con el SDK de Places para iOS, el SDK de Maps para iOS y el framework de ubicación de Apple Core.

Obtén el código

Clona o descarga el SDK de Google Maps para iOS desde GitHub.

Configura tu proyecto de desarrollo

Sigue estos pasos a fin de instalar el SDK de Places para iOS y el SDK de Maps para iOS:

  1. Descarga y, luego, instala la versión 14.0 o una posterior de Xcode.
  2. Si aún no tienes CocoaPods, instálalo en macOS ejecutando el siguiente comando desde la terminal:
    sudo gem install cocoapods
    .
  3. En el directorio en el que guardaste el repositorio de muestra (Obtén el código), navega hasta el directorio tutorials/current-place-on-map.
  4. Ejecuta el comando pod install. De esta manera, se instalan las APIs especificadas en Podfile junto con las dependencias que puedan tener.
  5. Ejecuta pod outdated para comparar la versión del Pod instalado con las actualizaciones nuevas. Si se detecta una versión nueva, ejecuta pod update para actualizar Podfile y, luego, instalar el SDK más reciente. Para obtener más detalles, consulta la guía de CocoaPods.
  6. Abre (con doble clic) el current-place-on-map.xcworkspace del proyecto para abrirlo en Xcode. Debes usar el archivo .xcworkspace para abrir el proyecto.

Para obtener instrucciones de instalación detalladas, consulta Cómo comenzar (Maps) y Cómo comenzar (Places).

Cómo habilitar las APIs necesarias y obtener una clave de API

Si deseas completar este instructivo, necesitarás una clave de API de Google autorizada para utilizar el SDK de Maps para iOS y la API de Places.

  1. Sigue las instrucciones que se indican en Cómo comenzar a utilizar Google Maps Platform para configurar una cuenta de facturación y un proyecto habilitado con ambos productos.
  2. Sigue las instrucciones que se indican en Cómo obtener una clave de API para crear una clave de API para el proyecto de desarrollo que configuraste anteriormente.

Agrega la clave de API a tu aplicación

Agrega tu clave de API a AppDelegate.swift de la siguiente manera:

  1. Ten en cuenta que se agregó la siguiente sentencia de importación al archivo:
    import GooglePlaces
    import GoogleMaps
  2. Edita la siguiente línea en el método application(_:didFinishLaunchingWithOptions:) y reemplaza YOUR_API_KEY por tu clave de API:
    GMSPlacesClient.provideAPIKey("YOUR_API_KEY")
    GMSServices.provideAPIKey("YOUR_API_KEY")

Compila y ejecuta tu app

  1. Conecta un dispositivo iOS a tu computadora o selecciona un simulador en el menú emergente del esquema de Xcode.
  2. Si usas un dispositivo, asegúrate de que los servicios de ubicación estén habilitados. Si usas un simulador, selecciona una ubicación en el menú Funciones.
  3. En Xcode, haz clic en la opción Product/Run del menú (o en el ícono del botón de reproducción).
    • Xcode compila la app y la ejecuta en el dispositivo o en el simulador.
    • Deberías ver un mapa con varios marcadores centrados cerca de tu ubicación actual.

Solución de problemas:

  • Si no ves un mapa, verifica si obtuviste una clave de API y la agregaste a la app, como se describió anteriormente. Consulta la consola de depuración de Xcode para ver si hay mensajes de error sobre la clave de API.
  • Si restringiste la clave de API mediante el identificador de paquete de iOS, edita la clave para agregar el identificador de paquete de la app: com.google.examples.current-place-on-map.
  • El mapa no se mostrará correctamente si se rechaza la solicitud de permisos para los servicios de ubicación.
    • Si utilizas un dispositivo, ve a Configuración/General/Privacidad/Servicios de ubicación y vuelve a habilitar los servicios de ubicación.
    • Si usas un simulador, ve a Simulator/Reset Content and Settings...
    La próxima vez que se ejecute la app, asegúrate de aceptar la solicitud de servicios de ubicación.
  • Asegúrate de tener una buena conexión de Wi-Fi o GPS.
  • Si se inicia la app, pero no se muestra ningún mapa, asegúrate de haber actualizado el archivo Info.plist de tu proyecto con los permisos de ubicación adecuados. Para obtener más información sobre el control de permisos, consulta la guía para solicitar permisos de ubicación en tu app a continuación.
  • Utiliza las herramientas de depuración de Xcode para ver los registros y depurar la app.

Comprende el código

En esta sección del instructivo, se explican los componentes más importantes de la app current-place-on-map para ayudarte a comprender cómo compilar una app similar.

La app de lugar actual en el mapa cuenta con dos controladores de vista: uno para mostrar un mapa con el lugar seleccionado por el usuario y otro para mostrarle al usuario una lista de los lugares que puede elegir. Ten en cuenta que cada controlador de vista tiene las mismas variables para realizar un seguimiento de la lista de lugares probables (likelyPlaces) y para indicar la selección del usuario (selectedPlace). La navegación entre vistas se realiza con segues.

Cómo solicitar el permiso de ubicación

Tu app debe solicitar el consentimiento del usuario para usar los servicios de ubicación. Para ello, incluye la clave NSLocationAlwaysUsageDescription en el archivo Info.plist de la app y establece el valor de cada clave en una string que describa el modo en que la app pretende usar los datos de ubicación.

Cómo configurar el administrador de ubicaciones

Usa CLLocationManager para encontrar la ubicación actual del dispositivo y solicitar actualizaciones periódicas cuando este se traslade a una nueva ubicación. En este instructivo, se proporciona el código necesario para obtener la ubicación del dispositivo. Para obtener más detalles, consulta la guía Obtén la ubicación del usuario en la documentación para desarrolladores de Apple.

  1. A nivel de la clase, declara el administrador de ubicaciones, la ubicación actual, la vista de mapa, el cliente de Places y el nivel de zoom predeterminado.
  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. Inicializa el administrador de ubicaciones y GMSPlacesClient en 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. Declara variables para contener la lista de lugares probables y el lugar seleccionado por el usuario.
  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. Agrega delegados que controlen eventos para el administrador de ubicaciones mediante una cláusula de extensión.
  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);
    }
          

Cómo agregar un mapa

Crea un mapa y agrégalo a la vista de viewDidLoad() en el controlador de vista principal. El mapa permanece oculto hasta que se recibe una actualización de ubicación (estas actualizaciones se controlan en la extensión 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;
      

Pedirle al usuario que seleccione el lugar actual

Utiliza el SDK de Places para iOS para obtener las cinco probabilidades principales de lugares según la ubicación actual del usuario y presenta la lista en un UITableView. Cuando el usuario seleccione un lugar, agrega un marcador al mapa.

  1. Obtén una lista de los lugares probables para propagar un UITableView, desde el cual el usuario puede seleccionar el lugar en el que se encuentra.
  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. Abre una vista nueva para mostrarle los lugares probables al usuario. Cuando el usuario presiona "Obtener lugar", se pasa a una vista nueva y le mostramos una lista de posibles lugares para elegir. La función prepare actualiza PlacesViewController con la lista de los lugares posibles actuales, y se llama automáticamente cuando se realiza una transición.
  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. En PlacesViewController, completa la tabla con la lista de los lugares más probables, con la extensión de delegado 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. Controla la selección del usuario con la extensión 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;
    }
          

Cómo agregar un marcador al mapa

Cuando el usuario realice una selección, usa una transición de desaceleración para volver a la vista anterior y agrega el marcador al mapa. Se llama automáticamente a la IBAction unwindToMain cuando se regresa al controlador de vista 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];
}
      

¡Felicitaciones! Compilaste una app para iOS que permite al usuario elegir su lugar actual y mostrar el resultado en un mapa de Google. En el curso de esto, aprendiste a usar el SDK de Places para iOS, el SDK de Maps para iOS y el framework de ubicación de Apple Core.