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:
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:
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;
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:
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:
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:
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:
Búsqueda local
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:
- Cree un objeto
MKLocalSearchRequest
. - Crear un objeto
MKLocalSearch
a partir deMKLocalSearchRequest
. - Llamar al método
Start
en el objetoMKLocalSearch
. - 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:
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:
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 MapKit 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.