Compartir vía


Mapas en Xamarin.iOS

Los mapas son una característica común en todos los sistemas operativos móviles modernos. iOS ofrece compatibilidad con la asignación de forma nativa a través del marco Map Kit. Con Map Kit, las aplicaciones pueden agregar mapas enriquecidos e interactivos fácilmente. Estos mapas se pueden personalizar de varias maneras, como agregar anotaciones para marcar ubicaciones en un mapa y superponer gráficos de formas arbitrarias. Map Kit tiene incluso compatibilidad integrada para mostrar la ubicación actual de un dispositivo.

Incorporación de un mapa

Para agregar un mapa a una aplicación, agregue una instancia de MKMapView a la jerarquía de vistas, como se muestra a continuación:

// map is an MKMapView declared as a class variable
map = new MKMapView (UIScreen.MainScreen.Bounds);
View = map;

MKMapView es una subclase UIView que muestra un mapa. Simplemente agregar el mapa mediante el código anterior genera un mapa interactivo:

Asignación de ejemplo

Estilo del mapa

MKMapView admite 3 estilos diferentes de mapas. Para aplicar un estilo de mapa, simplemente establezca la propiedad MapType en un valor de la enumeración MKMapType:

map.MapType = MKMapType.Standard; //road map
map.MapType = MKMapType.Satellite;
map.MapType = MKMapType.Hybrid;

En la captura de pantalla siguiente se muestran los diferentes estilos de mapa disponibles:

En esta captura de pantalla se muestran los diferentes estilos de asignación disponibles

Desplazamiento lateral y zoom

MKMapView incluye compatibilidad con características de interactividad de mapa como:

  • Zoom a través de un gesto de reducir
  • Desplazamiento lateral o a través de un gesto de desplazamiento

Estas características se pueden habilitar o deshabilitar simplemente estableciendo las propiedades ZoomEnabled y ScrollEnabled de la instancia de MKMapView, donde el valor predeterminado es true para ambos. Por ejemplo, para mostrar un mapa estático, simplemente establezca las propiedades adecuadas en false:

map.ZoomEnabled = false;
map.ScrollEnabled = false;

Ubicación del usuario

Además de la interacción del usuario, MKMapView también tiene compatibilidad integrada para mostrar la ubicación del dispositivo. Lo hace mediante el marco de Ubicación principal. Para poder acceder a la ubicación del usuario, debe preguntar al usuario. Para ello, cree una instancia de CLLocationManager y llame a RequestWhenInUseAuthorization.

CLLocationManager locationManager = new CLLocationManager();
locationManager.RequestWhenInUseAuthorization();
//locationManager.RequestAlwaysAuthorization(); //requests permission for access to location data while running in the background

Tenga en cuenta que en las versiones de iOS anteriores a la 8.0, al intentar llamar a RequestWhenInUseAuthorization, se producirá un error. Asegúrese de comprobar la versión de iOS antes de realizar esa llamada si piensa admitir versiones anteriores a 8.

El acceso a la ubicación del usuario también requiere modificaciones en Info.plist. Se deben establecer las siguientes claves relacionadas con los datos de ubicación:

  • NSLocationWhenInUseUsageDescription: Al obtener acceso a la ubicación del usuario mientras interactúa con la aplicación.
  • NSLocationAlwaysUsageDescription: Al obtener acceso la aplicación a la ubicación del usuario en segundo plano.

Puede agregar esas claves abriendo Info.plist y seleccionando Origen en la parte inferior del editor.

Una vez que haya actualizado Info.plist y haya pedido al usuario permiso para acceder a su ubicación, puede mostrar la ubicación del usuario en el mapa estableciendo la propiedad ShowsUserLocation en true:

map.ShowsUserLocation = true;

Alerta de acceso a la ubicación permitida

anotaciones

MKMapView también admite la visualización de imágenes, conocidas como anotaciones, en un mapa. Pueden ser imágenes personalizadas o alfileres definidos por el sistema de varios colores. Por ejemplo, en la captura de pantalla siguiente se muestra un mapa tanto con un alfiler como con una imagen personalizada:

En esta captura de pantalla se muestra una asignación, tanto con un anclaje como con una imagen personalizada

Incorporación de una anotación

Una anotación tiene dos partes:

  • El objeto MKAnnotation, que incluye datos del modelo sobre la anotación, como el título y la ubicación de esta.
  • MKAnnotationView, que contiene la imagen que se va a mostrar y, opcionalmente, una llamada que se muestra cuando el usuario pulsa la anotación.

Map Kit usa el patrón de delegación de iOS para agregar anotaciones a un mapa, donde la propiedad Delegate de MKMapView se establece en una instancia de MKMapViewDelegate. Es la implementación de este delegado responsable de devolver MKAnnotationView de una anotación.

Para agregar una anotación, dicha anotación primero se agrega llamando a AddAnnotations en la instancia de MKMapView:

// add an annotation
map.AddAnnotations (new MKPointAnnotation (){
    Title="MyAnnotation",
    Coordinate = new CLLocationCoordinate2D (42.364260, -71.120824)
});

Cuando la ubicación de la anotación sea visible en el mapa, MKMapView llamará al método GetViewForAnnotation de su delegado para que MKAnnotationView se muestre.

Por ejemplo, el código siguiente devuelve MKPinAnnotationView proporcionado por el sistema:

string pId = "PinAnnotation";

public override MKAnnotationView GetViewForAnnotation (MKMapView mapView, NSObject annotation)
{
    if (annotation is MKUserLocation)
        return null;

    // create pin annotation view
    MKAnnotationView pinView = (MKPinAnnotationView)mapView.DequeueReusableAnnotation (pId);

    if (pinView == null)
        pinView = new MKPinAnnotationView (annotation, pId);

    ((MKPinAnnotationView)pinView).PinColor = MKPinAnnotationColor.Red;
    pinView.CanShowCallout = true;

    return pinView;
}

Reutilización de anotaciones

Para conservar la memoria, MKMapView permite agrupar las vistas de anotación para su reutilización, de forma similar a la forma en que se reutilizan las celdas de tabla. La obtención de una vista de anotación del grupo se realiza con una llamada a DequeueReusableAnnotation:

MKAnnotationView pinView = (MKPinAnnotationView)mapView.DequeueReusableAnnotation (pId);

Visualización de llamadas

Como se mencionó anteriormente, una anotación puede mostrar opcionalmente una llamada. Para mostrar una llamada, simplemente establezca CanShowCallout en true en MKAnnotationView. Esto hace que el título de la anotación se muestre cuando se pulsa la anotación, como se muestra:

Título de anotaciones que se muestra

Personalización de la llamada

La llamada también se puede personalizar para mostrar las vistas del accesorio izquierdo y derecho, como se muestra a continuación:

pinView.RightCalloutAccessoryView = UIButton.FromType (UIButtonType.DetailDisclosure);
pinView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile ("monkey.png"));

Este código genera la siguiente llamada:

Llamada de ejemplo

Para controlar el usuario que pulsa el accesorio adecuado, simplemente implemente el método CalloutAccessoryControlTapped en MKMapViewDelegate:

public override void CalloutAccessoryControlTapped (MKMapView mapView, MKAnnotationView view, UIControl control)
{
    ...
}

Superposiciones

Otra manera de superponer gráficos en un mapa es usar superposiciones. Las superposiciones admiten dibujar contenido gráfico que se escala con el mapa a medida que se amplía. iOS proporciona compatibilidad con varios tipos de superposiciones, entre los que se incluyen:

  • Polígonos: se usan normalmente para resaltar alguna región en un mapa.
  • Polilíneas: suelen verse al mostrar una ruta.
  • Círculos: se usan para resaltar un área circular de un mapa.

Además, se pueden crear superposiciones personalizadas para mostrar geometrías arbitrarias con código de dibujo granular y personalizado. Por ejemplo, el radar meteorológico sería un buen candidato para una superposición personalizada.

Incorporación de una superposición

De forma similar a las anotaciones, agregar una superposición implica 2 partes:

  • Crear un objeto de modelo para la superposición y agregarlo a MKMapView.
  • Crear una vista para la superposición en MKMapViewDelegate.

El modelo de la superposición puede ser cualquier subclase MKShape. Xamarin.iOS incluye subclasesMKShape para polígonos, polilíneas y círculos, a través de las clases MKPolygon, MKPolyline y MKCircle, respectivamente.

Por ejemplo, se usa el código siguiente para agregar MKCircle:

var circleOverlay = MKCircle.Circle (mapCenter, 1000);
map.AddOverlay (circleOverlay);

La vista de una superposición es una instancia de MKOverlayView que GetViewForOverlay devuelve en MKMapViewDelegate. Cada MKShape tiene un MKOverlayView correspondiente que sabe cómo mostrar la forma dada. Para MKPolygon hay MKPolygonView. Del mismo modo, MKPolyline corresponde a MKPolylineView, y para MKCircle hay MKCircleView.

Por ejemplo, el código siguiente devuelve MKCircleView para MKCircle:

public override MKOverlayView GetViewForOverlay (MKMapView mapView, NSObject overlay)
{
    var circleOverlay = overlay as MKCircle;
    var circleView = new MKCircleView (circleOverlay);
    circleView.FillColor = UIColor.Blue;
    return circleView;
}

Esto muestra un círculo en el mapa como se muestra:

Círculo mostrado en la asignación

iOS incluye una API de búsqueda local con Map Kit, que permite búsquedas asincrónicas de puntos de interés en una región geográfica especificada.

Para realizar una búsqueda local, una aplicación debe seguir estos pasos:

  1. Cree un objeto MKLocalSearchRequest .
  2. Crear un objeto MKLocalSearch a partir de MKLocalSearchRequest.
  3. Llamar al método Start en el objeto MKLocalSearch.
  4. Recuperar el objeto MKLocalSearchResponse en una devolución de llamada.

La propia API de búsqueda local no proporciona ninguna interfaz de usuario. Ni siquiera requiere que se use un mapa. Sin embargo, para hacer un uso práctico de la búsqueda local, una aplicación debe proporcionar alguna manera de especificar una consulta de búsqueda y mostrar los resultados. Además, dado que los resultados contendrán datos de ubicación, a menudo tendrá sentido mostrarlos en un mapa.

Incorporación de una interfaz de usuario de búsqueda local

Una manera de aceptar la entrada de búsqueda es con UISearchBar, que UISearchController proporciona y mostrará los resultados en una tabla.

El código siguiente agrega UISearchController (que tiene una propiedad de barra de búsqueda) en el método ViewDidLoad de MapViewController:

//Creates an instance of a custom View Controller that holds the results
var searchResultsController = new SearchResultsViewController (map);

//Creates a search controller updater
var searchUpdater = new SearchResultsUpdator ();
searchUpdater.UpdateSearchResults += searchResultsController.Search;

//add the search controller
searchController = new UISearchController (searchResultsController) {
                SearchResultsUpdater = searchUpdater
            };

//format the search bar
searchController.SearchBar.SizeToFit ();
searchController.SearchBar.SearchBarStyle = UISearchBarStyle.Minimal;
searchController.SearchBar.Placeholder = "Enter a search query";

//the search bar is contained in the navigation bar, so it should be visible
searchController.HidesNavigationBarDuringPresentation = false;

//Ensure the searchResultsController is presented in the current View Controller
DefinesPresentationContext = true;

//Set the search bar in the navigation bar
NavigationItem.TitleView = searchController.SearchBar;

Tenga en cuenta que es responsable de incorporar el objeto de la barra de búsqueda en la interfaz de usuario. En este ejemplo, lo asignamos a TitleView de la barra de navegación, pero si no usa un controlador de navegación en la aplicación, tendrá que encontrar otro lugar para mostrarlo.

En este fragmento de código, creamos otro controlador de vista personalizado (searchResultsController) que muestra los resultados de la búsqueda y, a continuación, usamos este objeto para crear nuestro objeto de controlador de búsqueda. También hemos creado un nuevo actualizador de búsqueda, que se activa cuando el usuario interactúa con la barra de búsqueda. Recibe notificaciones sobre las búsquedas con cada pulsación de tecla y es responsable de actualizar la interfaz de usuario. Veremos cómo implementar tanto searchResultsController como searchResultsUpdater más adelante en esta guía.

Esto genera una barra de búsqueda que se muestra sobre el mapa, como se muestra a continuación:

Barra de búsqueda mostrada sobre la asignación

Visualización de los resultados de la búsqueda

Para mostrar los resultados de la búsqueda, es necesario crear un controlador de vista personalizado; normalmente UITableViewController. Como se muestra anteriormente, searchResultsController se pasa al constructor de searchController cuando se crea. El código siguiente es un ejemplo de cómo crear este controlador de vista personalizado:

public class SearchResultsViewController : UITableViewController
{
    static readonly string mapItemCellId = "mapItemCellId";
    MKMapView map;

    public List<MKMapItem> MapItems { get; set; }

    public SearchResultsViewController (MKMapView map)
    {
        this.map = map;

        MapItems = new List<MKMapItem> ();
    }

    public override nint RowsInSection (UITableView tableView, nint section)
    {
        return MapItems.Count;
    }

    public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
    {
        var cell = tableView.DequeueReusableCell (mapItemCellId);

        if (cell == null)
            cell = new UITableViewCell ();

        cell.TextLabel.Text = MapItems [indexPath.Row].Name;
        return cell;
    }

    public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
    {
        // add item to map
        CLLocationCoordinate2D coord = MapItems [indexPath.Row].Placemark.Location.Coordinate;
        map.AddAnnotations (new MKPointAnnotation () {
            Title = MapItems [indexPath.Row].Name,
            Coordinate = coord
        });

        map.SetCenterCoordinate (coord, true);

        DismissViewController (false, null);
    }

    public void Search (string forSearchString)
    {
        // create search request
        var searchRequest = new MKLocalSearchRequest ();
        searchRequest.NaturalLanguageQuery = forSearchString;
        searchRequest.Region = new MKCoordinateRegion (map.UserLocation.Coordinate, new MKCoordinateSpan (0.25, 0.25));

        // perform search
        var localSearch = new MKLocalSearch (searchRequest);

        localSearch.Start (delegate (MKLocalSearchResponse response, NSError error) {
            if (response != null && error == null) {
                this.MapItems = response.MapItems.ToList ();
                this.TableView.ReloadData ();
            } else {
                Console.WriteLine ("local search error: {0}", error);
            }
        });

    }
}

Actualización de los resultados de la búsqueda

SearchResultsUpdater actúa como mediador entre la barra de búsqueda de searchController y los resultados de búsqueda.

En este ejemplo, primero tenemos que crear el método de búsqueda en SearchResultsViewController. Para ello, debemos crear un objeto MKLocalSearch y usarlo para emitir una búsqueda de MKLocalSearchRequest. Los resultados se recuperan en una devolución de llamada que se pasa al método Start del objeto MKLocalSearch. A continuación, los resultados se devuelven en un objeto MKLocalSearchResponse que contiene una matriz de objetos MKMapItem:

public void Search (string forSearchString)
{
    // create search request
    var searchRequest = new MKLocalSearchRequest ();
    searchRequest.NaturalLanguageQuery = forSearchString;
    searchRequest.Region = new MKCoordinateRegion (map.UserLocation.Coordinate, new MKCoordinateSpan (0.25, 0.25));

    // perform search
    var localSearch = new MKLocalSearch (searchRequest);

    localSearch.Start (delegate (MKLocalSearchResponse response, NSError error) {
        if (response != null && error == null) {
            this.MapItems = response.MapItems.ToList ();
            this.TableView.ReloadData ();
        } else {
            Console.WriteLine ("local search error: {0}", error);
        }
    });

}

A continuación, en nuestro MapViewController crearemos una implementación personalizada de UISearchResultsUpdating, que se asigna a la propiedad SearchResultsUpdater de nuestro searchController en la sección Incorporación de una interfaz de usuario de búsqueda local:

public class SearchResultsUpdator : UISearchResultsUpdating
{
    public event Action<string> UpdateSearchResults = delegate {};

    public override void UpdateSearchResultsForSearchController (UISearchController searchController)
    {
        this.UpdateSearchResults (searchController.SearchBar.Text);
    }
}

La implementación anterior agrega una anotación al mapa cuando se selecciona un elemento de los resultados, como se muestra a continuación:

Anotación agregada a la asignación cuando se selecciona un elemento de los resultados

Importante

UISearchController se implementó en iOS 8. Si desea admitir dispositivos anteriores a este, deberá usar UISearchDisplayController.

Resumen

En este artículo se ha examinado el marco del Kit de asignación para iOS. En primer lugar, se ha examinado cómo la clase MKMapView permite incluir mapas interactivos en una aplicación. A continuación, se mostró cómo personalizar aún más los mapas mediante anotaciones y superposiciones. Por último, se examinaron las funcionalidades de búsqueda local que se agregaron a Map Kit con iOS 6.1, que muestran cómo usar consultas basadas en ubicaciones para puntos de interés y agregarlas a un mapa.