iOS Maps
Using Map Kit in Xamarin.iOS Applications
Overview
Maps are a common feature in all modern mobile operating systems. iOS offers mapping support natively
through the Map Kit framework. With Map Kit, applications can easily add rich, interactive maps. These
maps can be customized in a variety of ways, such as adding annotations to mark locations on a map, and
overlaying graphics of arbitrary shapes. Map Kit even has built-in support for showing the current location
of a device.
iOS Maps
Adding a Map
Adding a map to an application is accomplished by adding an MKMapView instance to the view hierarchy,
as shown below:
// map is an MKMapView declared as a class variable
map = new MKMapView (UIScreen.MainScreen.Bounds);
View = map;
MKMapView is a UIView subclass that displays a map. Simply adding the map using the code above
produces an interactive map:
Map Style
MKMapView supports 3 different styles of maps. To apply a map style, simply set the MapType property to
a value from the MapType enumeration. The following screenshot show the different map styles that are
available:
Panning and Zooming
MKMapView includes support for map interactivity features such as:
Zooming via a pinch gesture
Panning via a pan gesture
These features can be enabled or disabled by simply setting the ZoomEnabled and ScrollEnabled
properties of the MKMapView instance, where the default value is true for both. For example, to display a
static map, simply set the appropriate properties to false:
map.ZoomEnabled = false;
map.ScrollEnabled = false;
User Location
In addition to user interaction, MKMapView also has built-in support for displaying the location of the device.
It does this internally by using the Core Location framework. However, enabling this is as simple as setting
the ShowsUserLocation property to true:
map.ShowsUserLocation = true;
When user location is enabled, the first time the application runs, the user will be prompted to allow location
services for the application, as shown below:
Annotations
MKMapView also supports displaying images, known as annotations, on a map. These can be either
custom images or system-defined pins of various colors. For example, the following screenshot shows a
map with a both a pin and a custom image:
Adding an annotation
An annotation itself has two parts:
The MKAnnotation object, which includes model data about the annotation, such as the title and
location of the annotation.
The MKAnnotationView , which contains the image to display and optionally a callout that is shown
when the user taps the annotation.
Map Kit uses the iOS delegation pattern to add annotations to a map, where the Delegate property of the
MKMapView is set to an instance of an MKMapViewDelegate. It is this delegate's implementation that is
responsible for returning the MKAnnotationView for an annotation.
To add an annotation, first the annotation is added by calling AddAnnotation on the MKMapView
instance:
// add an annotation
map.AddAnnotation (new MKPointAnnotation (){
Title="MyAnnotation",
Coordinate = new CLLocationCoordinate2D (42.364260, -71.120824)
});
When the location of the annotation becomes visible on the map, the MKMapView will call its delegate's
GetViewForAnnotation method to get the MKAnnotationView to display.
For example, the following code returns a system-provided MKPinAnnotationView:
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;
}
Reusing Annotations
To conserve memory, MKMapView allows annotation view's to be pooled for reuse, similar to the way table
cells are reused. Obtaining an annotation view from the pool is done with a call to
DequeueReusableAnnotation:
MKAnnotationView pinView =
(MKPinAnnotationView)mapView.DequeueReusableAnnotation (pId);
Showing Callouts
As mentioned earlier, an annotation can optionally show a callout. To show a callout simply set
CanShowCallout to true on the MKAnnotationView. This results in the annotation's title being
displayed when the annotation is tapped, as shown:
Customizing the Callout
The callout can also be customized to show left and right accessory views, as shown below:
pinView.RightCalloutAccessoryView = UIButton.FromType
(UIButtonType.DetailDisclosure);
pinView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile
("monkey.png"));
This code results in the following callout:
To handle the user tapping the right accessory, simply implement the
CalloutAccessoryControlTapped method in the MKMapViewDelegate:
public override void CalloutAccessoryControlTapped (MKMapView mapView,
MKAnnotationView view, UIControl control)
{
...
}
Overlays
Another way to layer graphics on a map is using overlays. Overlays support drawing graphical content that
scales with the map as it is zoomed. iOS provides support for several types of overlays, including:
Polygons - Commonly used to highlight some region on a map.
Polylines - Often seen when showing a route.
Circles - Used to highlight a circular area of a map.
Additionally, custom overlays can be created to show arbitrary geometries with granular, customized
drawing code. For example, weather radar would be a good candidate for a custom overlay.
Adding an Overlay
Similar to annotations, adding an overlay involves 2 parts:
Creating a model object for the overlay and adding it to the MKMapView .
Creating a view for the overlay in the MKMapViewDelagate .
The model for the overlay can be any MKShape subclass. Xamarin.iOS includes MKShape subclasses for
polygons, polylines and circles, via the MKPolygon, MKPolyline and MKCircle classes respectively.
For example, the following code is used to add an MKCircle:
var circleOverlay = MKCircle.Circle (mapCenter, 1000);
map.AddOverlay (circleOverlay);
The view for an overlay is an MKOverlayView instance that is returned by the GetViewForOverlay in
the MKMapViewDelegate. Each MKShape has a corresponding MKOverlayView that knows how to
display the given shape. For MKPolygon there is MKPolygonView. Similarly, MKPolyline corresponds
to MKPolylineView, and for MKCircle there is MKCircleView.
For example, the following code returns an MKCircleView for an 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;
}
This displays a circle on the map as shown:
Local Search
iOS 6.1 includes a local search API with Map Kit, which allows asynchronous searches for points of interest
in a specified geographic region.
To perform a local search, an application must follow these steps:
1. Create MKLocalSearchRequest object.
2. Create an MKLocalSearch object from the MKLocalSearchRequest .
3. Call the Start method on the MKLocalSearch object.
4. Retrieve the MKLocalSearchResponse object in a callback.
The local search API itself provides no user interface. It doesn’t even require a map to be used. However,
to make practical use of local search, an application needs to provide some way to specify a search query
and display results. Additionally, since the results will contain location data, it will often make sense to show
them on a map.
Adding a Local Search UI
For example, one way to accept search input is with a UISearchBar, which can be used in conjunction
with a UISearchDisplayController to display results in a table.
The following code adds a UISearchBar and UISearchController in the ViewDidLoad method of
MapDemoViewController:
// create search controller
searchBar = new UISearchBar (new RectangleF (0, 0, View.Frame.Width, 50)) {
Placeholder = "Enter a search query"
};
searchController = new UISearchDisplayController (searchBar, this);
searchController.Delegate = new SearchDelegate (map);
searchController.SearchResultsSource = new SearchSource (searchController,
map);
View.AddSubview (searchBar);
This results in a search bar displayed over the map as shown below (the SearchDelegate and
SearchSource will be implemented shortly):
Implementing a UISearchDisplayDelegate
When using a UISearchDisplayController, search input from the associated UISearchBar is
captured in a UISearchDisplayDelegate. For the case of a local search, this is where the search can
be initiated, as shown in the following code:
class SearchDelegate : UISearchDisplayDelegate
{
MKMapView map;
public SearchDelegate (MKMapView map)
{
this.map = map;
}
public override bool ShouldReloadForSearchString (UISearchDisplayController
controller, 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) {
((SearchSource)controller.SearchResultsSource).MapItems =
response.MapItems.ToList();
controller.SearchResultsTableView.ReloadData();
} else {
Console.WriteLine ("local search error: {0}", error);
}
});
return true;
}
}
Displaying the Search Results
After creating the MKLocalSearch object and using it to issue a search for an MKLocalSearchRequest,
the results are retrieved in a callback passed to the Start method of the MKLocalSearch object. The
results are returned in an MKLocalSearchResponse object containing an array of MKMapItem objects.
The UISearchDisplayController encapsulates a UITableView to display search results and
includes a SearchResultsSource property, which is a UITableViewSource.
The following code implements a UITableViewSource to show the map items in the UITableView of
the UISearchDisplayController:
class SearchSource : UITableViewSource
{
static readonly string mapItemCellId = "mapItemCellId";
UISearchDisplayController searchController;
MKMapView map;
public List<MKMapItem> MapItems { get; set; }
public SearchSource (UISearchDisplayController searchController, MKMapView
map)
{
this.searchController = searchController;
this.map = map;
MapItems = new List<MKMapItem> ();
}
public override int RowsInSection (UITableView tableview, int 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)
{
searchController.SetActive (false, true);
// add item to map
CLLocationCoordinate2D coord = MapItems
[indexPath.Row].Placemark.Location.Coordinate;
map.AddAnnotation (new MKPointAnnotation () {
Title = MapItems [indexPath.Row].Name,
Coordinate = coord
});
map.SetCenterCoordinate (coord, true);
}
}
The implementation above adds an annotation to the map when an item is selected from the results, as
shown below:
Summary
This article examined the Map Kit framework for iOS. First, it looked at how the MKMapView class allows
interactive maps to be included in an application. Then it demonstrated how to further customize maps
using annotations and overlays. Finally, it examined the local search capabilities that were added to Map
Kit with iOS 6.1, showing how to use perform location based queries for points of interest and add them to
a map.