Custom Map Renderer: iOS

It Finally Happened. I picked up my first Mac computer in January. I am now the proud owner of a MacBook Pro 13, no touch bar and the regular keyboard. I went into the local Apple Store and tried out the newer keyboard and the older keyboard and have to say that I liked the feel of the old one better. I really didn’t like how loud the new keyboard was. But the old keyboard was good, trackpad was good (but not as good as I thought it was going to be) and the screen was nice. Overall a nice computer if expensive. But the important thing is that I could finally install XCode and build iOS apps!

And that is the subject of the this blog post. In the previous blog post, we looked at creating a custom renderer for Android. Now we will look at doing the same with iOS. With iOS, things are simpler in at least one regard: you don’t have to go get an access key for your app.

One thing I want to make clear after some questions from others. This information isn’t a whole lot different than what you can get from developer.xamarin.com. But it builds on those examples by making the custom map renderer updatable from your view model … if you are using data binding of course. If you aren’t using data binding, you can skip all of this and just follow the guide in the above link.

If you are still here …

I didn’t change anything in my CustomMap class in the shared project. All updates are in the iOS project. In here I have defined two classes: CustomMKAnnotationView and CustomMapRenderer. Let’s talk about CustomMKAnnotationView class first: it is the simpler.

The CustomMKAnnotationView class derives from MKAnnotationView. It adds a couple of new properties for the Id of the marker and a URL associated with the marker. Otherwise there is not a lot to be excited about. So lets move onto the CustomMapRenderer class.

The CustomMapRenderer class derives from MapRenderer. The first thing is that we have to override the OnElementChanged method. Just like Android, this either hooks up the native map to the cross platform map class in the shared project or unhooks it.


protected override OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
{
    base.OnElementChanged(e);

    if (e.OldElement != null)
    {
        //... unhook events, native conrols etc
    }

    if (e.NewElement != null)
    {
        _customMap = (CustomMap) e.NewElement;
        _nativeMap = Control as MKMapView;
        _customPins = _customMap.CustomPins;

        _nativeMap.GetViewForAnnotation = GetViewForAnnotation;
        _nativeMap.CalloutAccessoryControlTapped +=
            OnCalloutAccessoryControlTapped;
        _nativeMap.DidSelectAnnotationView +=
            OnDidSelectAnnotationView;
        _nativeMap.DidDeselectAnnotationView +=
            OnDidDeselectAnnotationView;
    }
}

The most important things above are that you get a reference to our CustomMap object that was defined in the shared project; and also get a reference to the native map object (MKMapView).

Using the native map object from above, we can hook into events for it. We use these events to display the pins on the map and the info window when a user taps one of the pins.

Let’s take a look at the GetViewAnnotation method. This is the method that is used to show a custom marker on the map. In it we are passed in a reference to the marker. We use our GetCustomPin method to look up the information associated with the marker. In an effort to conserve memory, the map can reuse the view: and that is what is happening with the call to DequeueReusableAnnotation. If we can’t reuse, then we create a new one, specifying the image for the pin itself, and customizations for the callouts.


private MKAnnotationView GetViewForAnnotation(
    MKMapView mapView, IMKAnnotation annotation)
{
    if (annotation is MKUserLocation)
        return null;

    MKAnnotationView ret = null;

    var pointAnnotation = annotation as MKPointAnnotation;
    var customPin = GetCustomPin(pointAnnotation);
    if (customPin == null)
        throw new Exception("My pin not found");

    ret = mapView.DequeueReusableAnnotation(customPin.Id.ToString());
    if (ret == null)
    {
        ret = new CustomMKAnnotationView(
            annotation,
            customPin.Id.ToString(), customPin.Url);
        ret.Image = UIImage.FromFile("custompin2.png");
        ret.CalloutOffset = new CoreGraphics.CGPoint(0, 0);
        ret.LeftCalloutAccessoryView =
            new UIImageView(UIImage.FromFile("custompinimage.png"));
        ret.RightCalloutAccessoryView =
            UIButton.FromType(UIButtonType.DetailDisclosure);
        ((CustomMKAnnotationView) ret).Id = customPin.Id.ToString();
        ((CustomMKAnnotationView) ret).Url = customPin.Url;
    }

    ret.CanShowCallout = true;

    return ret;
}

Next we can take a quick look at the OnDidSelectAnnotationView. This is called when the user taps on the pin. You have the option of doing something custom … or just select the default that was setup in the GetAnnotationView function. In our case, we are going to see if the marker is associated with ID 1, and if it is, add in a custom image. Otherwise, we are just going to accept the default.


private void OnDidSelectAnnotationView(
    object sender,
    MKAnnotationViewEventArgs e)
{
    var customView = e.View as CustomMKAnnotationView;
    _customPinView = new UIView();

    if (customView.Id == "1")
    {
        _customPinView.Frame = new CoreGraphics.CGRect(0, 0, 200, 84);
        var image = new UIImageView(new CoreGraphics.CGRect(0, 0, 200, 84));
        image.Image = UIImage.FromFile("custommapimage.png");
        _customPinView.AddSubview(image);
        _customPinView.Center =
            new CoreGraphics.CGPoint(0, -(e.View.Frame.Height + 75));
        e.View.AddSubview(_customPinView);
    }
}

The OnDidDeselectAnnotationView method is called when the extended annotation is displayed and the user taps somewhere on the map, it will get rid of the extended information and free up the resources.


private void OnDidDeselectAnnotationView(
    object sender,
    MKAnnotationViewEventArgs e)
{
    if (!e.View.Selected)
    {
        _customPinView.RemoveFromSuperview();
        _customPinView.Dispose();
        _customPinView = null;
    }
}

Finally, let’s take a look at OnCalloutAccessoryControlTapped. After the user taps on the pin, extra data is displayed about the pin. If you tap on the extra data, this function is called. You can look at the information associated with the pin and act on it. In our case, we are going to grab the url and navigate to it.


private void OnCalloutAccessoryControlTapped(
    object sender,
    MKMapViewAccessoryTappedEventArgs e)
{
    var customView = e.View as CustomMKAnnotationView;
    if (!String.IsNullOrWhiteSpace(customView.Url))
    {
        UIApplication.SharedApplication.OpenUrl(
            new Foundation.NSUrl(customView.Url));
    }
}

And now just for a recap. If we want to make the custom map respond to our view model we have to listen for changes from the data binding system. This is almost exactly the same as the Android implementation where we override the OnElementPropertyChanged and listen for our property.


protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    base.OnElementPropertyChanged(sender, e);

    if (Element == null || Control == null)
        return;

    if (e.PropertyName == View.CustomMap.CustomPinsProperty.PropertyName)
    {
        UpdatePins();
    }
}

private void UpdatePins()
{
    if (_nativeMap == null)
        return;

    foreach (var p in _customMap.CustomPins)
    {
        MKPointAnnotation pa = new MKPointAnnotation
        {
            /// check git hub
        }

        _nativeMap.AddAnnotation(pa);
    }
}

Don’t forget, the CustomPins is bound in our XAML page.

I find it pretty amazing that Xamarin Forms is able to share so much of the code. Really all we are doing in the separate projects is the presentation: all of the business logic is shared between all. Very cool.

Find all the code in my GitHub under the XamarinPrism repository. Find it under the MapRendererIos solution.

Custom Map Renderer on Android: Part 3

This is the final post in the series about custom map renderers on Android. To recap, we added a map page to our application and then customized the appearance of the map markers and the info window that is shown when the user taps a marker.

Now we are going to make the map a bit more usable by making it possible to update the pins from the view model, and making it easy to consume services in the Android specific project.

In the previous post, we defined a CustomMap control that only had one property:


public class CustomMap : Map
{
    public List<CustomPin> CustomPins { get; set; }
}

The property above is only available from the ContentPage code behind. We are going to change this to be a bindable property that can be bound in the view model itself.


public class CustomMap : Map
{
    public static readonly BindableProperty CustomPinsProperty =
        BindableProperty.Create(
            "CustomPins",
            typeof(List<CustomPin>),
            typeof(CustomMap),
            new List<CustomPin>(),
            BindingMode.TwoWay);

    public List<CustomPin> CustomPins
    {
        get { return (List<CustomPin>) GetValue(CustomPinsProperty); }
        set { SetValue(CustomPinsProperty, value);
    }
}

Now that CustomPins is a bindable property it can participate in the data binding system. We can setup a property in our view model and attach it in our XAML.


    <view:CustomMap x:Name="cusmap"
IsShowingUser="True" MapType="Street"
CustomPins="{Binding CustomPins, Mode=TwoWay}" />

And here is the property in the view model:


/// other properties
private List<CustomPin> _customPins = new List<CustomPin>();
public List<CustomPin> CustomPins
{
    get { return _customPins; }
    set { SetProperty<List<CustomPin>>(ref _customPins, value); }
}

Finally, we need to get our CustomMapRenderer to react to these changes. The way we do that is to override the OnElementPropertyChangedEvent to list for changes in the CustomPins property.


public class CustomMapRenderer : MapRenderer, GoogleMap.IInfowindowAdapter, IOnMapReadyCallback
{
    // .... all the other code

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (Element == null || Control == null)
            return;

        if (e.PropertyName == CustomMap.CustomPinsProperty.PropertyName)
        {
            UpdatePins();
        }
    }
}

Finally, I want to show how you can use that data binding technique to pass some services through to your Android project. For example, when a user taps a marker on the map, the info window will show. We can listen for when the user taps on that info window and use our Prism navigation service to pull up a new page. Let’s add the new bindable property to the CustomMap class.


public class CustomMap : Map
{
    public static readonly BindableProperty CustomPinsProperty =
        BindableProperty.Create(
            "CustomPins",
            typeof(List<CustomPin>),
            typeof(CustomMap),
            new List<CustomPin>(),
            BindingMode.TwoWay);

    public List<CustomPin> CustomPins
    {
        get { return (List<CustomPin>) GetValue(CustomPinsProperty); }
        set { SetValue(CustomPinsProperty, value); }
    }

    public static readonly BindableProperty NavigationServiceProperty =
        BindableProperty.Create(
            "NavigationService",
            typeof(Prism.Navigation.INavigationService),
            typeof(CustomMap),
            null);

    public Prism.Navigation.INavigationService NavigationService
    {
        get { return (Prism.Navigation.INavigationService) GetValue(NavigationServiceProperty); }
        set { SetValue(NavigationServiceProperty, value); }
    }
}

Let’s make sure the navigation service is exposed in our view model and then bind it in XAML. Here’s the view model:


public class MapViewModel : BaseViewModel
{
    public MapViewModel(INavigationService navigationService)
        : base()
    {
        NavService = navigationService;
    }

    /// other properties, such as CustomPins

    private INavigationService _navService = null;
    public INavigationService NavService
    {
        get { return _navService; }
        set { SetProperty<INavigationService>(ref _navService, value); }
    }

    /// other code is here
}

And now the XAML:


    <view:CustomMap x:Name="cusmap"
IsShowingUser="True" MapType="Street"
CustomPins="{Binding CustomPins, Mode=TwoWay}"
NavigationService="{Binding NavService}" />

And now we can get use the navigation service from our CustomMapRenderer like this by adding in our InfoWindowClick handler. Remember that we wired it up in the OnMapReady function. Now let’s put some functionality in the handler:


private void OnMapInfoWindowClick(object sender, GoogleMap.InfoWindowClickEventArgs e)
{
    if (_customMap.NavigationService != null)
    {
        _customMap.NavigationService.NavigateAsync(DevDaysSpeakers.PageKeys.Speakers);
    }
}

You can find all of this code in my GitHub repo in the MapRenderer solution. If you run the project, you can tap on the “Map” button on the main page. It will bring up the map and display a marker. If you tap the test button, it will update the marker to a new location. Tapping on the marker will show a custom info window, and tapping that window will use the Prism navigation service to navigate back to the main page.

Sounds like I am finally going to pick up a Mac laptop, so I should be able to expand these posts to the iOS platform. Looking forward to that.

Lots of stuff! Hope you find some of it useful.

Custom Map Renderer on Android: Part 2

In the previous post, we setup our DevDaysSpeakers app to include a page that displays a map. To get the map to display our information, we have a few things we have to do. Let’s start with subclassing the Xamarin Forms Map class with our own class and create our own pin data object.


public class CustomPin
{
    public Pin Pin{ get; set; }
    public int Id { get; set; }
    public string Url { get; set; }
}

public class CustomMap : Map
{
    public List<CustomPin> CustomPins { get; set; }
}

And now we will use our new CustomMap class in our XAML. Make sure that you have the namespace declared (see view namespace below) and then you declare it as below. Note that I am just using some of the properties that are declared in the base Map class.


<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:view="clr-namespace:DevDaysSpeakers.View"
    xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
    prism:ViewModelLocator.AutowireViewModel="True"
    xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
    Title="Map"
    x:Class="DevDaysSpeakers.View.MapPage">
    <Grid>
        <!-- lets reference our custom map now -->
        <view:CustomMap x:Name="cusmap"
            IsShowingUser="True"
            MapType="Street" />
    </Grid>
</ContentPage>

So if we were to run the app at this point, we would see the exact same thing as before. To add the custom behavior, we need to add in our custom map renderer in the Android project. So let’s get started.

In our droid project, let’s create a new class called CustomMapRenderer in the root of the project. The very first thing that we see is that we have to decorate the code at the namespace level to tell Xamarin that whenever we see the CustomMap class in our shared XAML, we should implement using this class. It will look like the following:


/// usings are here

[assembly: ExportRenderer(typeof(DevDaySpeakers.View.CustomMap), typeof(DevDaySpeakers.Droid.CustomMapRenderer)]
namespace DevDaySpeakers.Droid
{
    public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter, IOnMapReadyCallback
    {
        /// ... implementation
    }
}

Included above is the class declaration and you can see that it derives from MapRenderer class and implements a pair of interfaces that are used to display the map in Android.

In the CustomMapRenderer, we want to keep track of a couple of instances, the first being our subclasssed Map (CustomMap) and the actual GoogleMap object is in Android. These objects are picked up in a couple of different places.

First, the CustomMap object is captured when the CustomMapRenderer instance is assigned and you can capture that by overriding OnElementChanged. You also use this method to clean up your wired up event handler for when a user taps on the info window associated with a map marker.

Secondly, in our implementation of the IOnMapReadyCallback interface, our implementation of the OnMapReady function saves the instance of the GoogleMap.


/// usings are here

[assembly: ExportRenderer(typeof(View.CustomMap), typeof(Droid.CustomMapRenderer)]
namespace DevDaySpeakers.Droid
{
    public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter, IOnMapReadyCallback
    {
        private GoogleMap _map = null;
        private DevDaySpeakers.View.CustomMap _customMap = null;

        protected override void OnElementChanged(ElementChangedEventArgs<Map> e)
        {
            base.OnElementChanged(e);
            if (e.OldElement != null)
            {
                // remove the event handler for the info window tap
                _map.InfoWindowClick -= OnMapInfoWindowClick;
            }

            if (e.NewElement != null)
            {
                // save reference to the CustomMap that was
                // in the XAML declaration
                _customMap  = (View.CustomMap) e.NewElement;
                ((MapView) Control).GetMapAsync(this);
            }
        }

        public void OnMapReady(GoogleMap googleMap)
        {
            /// called when the GoogleMap is ready, we need to save the
            /// instance, wire up the event handler for the info window tap,
            /// tell the map to use this class for handling the info window
            /// and finally load the markers.
            _map = googleMap;
            _map.InfoWindowClick += OnMapInfoWindowClick;
            _map.SetInfoWindowAdapter(this);

            /// load the pins
            UpdatePins();
        }

        private void OnMapInfoWindowClick(object s, GoogleMap.InfoWindowClickEventArgs e)
        {
            // event handler stub
        }

        public Android.Views.View GetInfoContents(Marker marker)
        {
            /// for GoogleMap.IInfoWindowAdapter implementation
            /// will just do default
            return null;
        }

        public Android.Views.View GetInfoWindow(Marker marker)
        {
            /// for GoogleMap.IInfoWindowAdapter implementation
            /// will just do default
            return null;
        }

        private void UpdatePins()
        {
            /// stub for updating pins on the map
        }
    }
}

Most of that is pretty straight forward I think. We are implementing our interfaces and saving instances to the Map objects so that we can use them later. We are also wiring up the event handler so that can capture when the user taps the information window that appears after tapping on a map marker.

Next we will handle adding the pins to the map by implementing the UpdatePins method. The interesting thing in this is that our CustomMap instance (_customMap) contains the business data of the app, while the GoogleMap (_map) instance does all the presentation.


private void UpdatePins()
{
    if (_map == null)
        return;

    _map.Clear();
    foreach (var pin in _customMap.CustomPins)
    {
        var marker = new MarkerOptions();
        marker.SetPosition(
            new LatLng(pin.Pin.Position.Latitude,
            pin.Pin.Position.Longitude));
        marker.SetTitle(pin.Pin.Label);
        marker.SetAddress(pin.Pin.Address);
        marker.SetIcon(BitmapDescriptorFactory.DefaultMarker(210));
        _map.AddMarker(marker);
    }
}

From above you can see that we are creating markers, setting the title property and the address property, and also changing the look of the pin itself. In this case we are only changing the color from default to blue. But you can also add in your own bitmaps if you want.

Let’s talk a bit about customizing the info window. In case you aren’t sure about the info window, this is what is pop’d up on the map when you tap on the marker. We have the ability to customize it and this happens in the GetInfoContents method in the code fragment above. By returning null, you will just get the default contents.

If you want to do something custom, you will need to define your own layout in the Resources\layout namespace. If you look at the sample code, there is nothing special there, I simply used some random template I saw somewhere to learn some syntax and made one. I am more familiar with XAML and just tried to match up the containers and controls.

But to me, the more important code is overriding the GetInfoContents method to put useful information in the popup.

public Android.Views.View GetInfoContents(Marker marker
{
    var inflater =
        Android.App.Application.Context.GetSystemService(Context.LayoutInflaterService)
        as Android.Views.LayoutInflater;

    if (inflater != null)
    {
        Android.Views.View view = null;
        var pin = GetCustomPin(marker);
        if (pin != null)
        {
            view = inflater.Inflate(Resource.Layout.CustomInfoWindow, null);

            // find the controls in the layout for the title and subtitle.
            // if we find them, we can populate them with our own info
            var infoTitle = view.FindViewById<TextView>(Resource.Id.InfoWindowTitle);
            var infoSubTitle = view.FindViewById<TextView>(Resource.Id.InfoWindowSubtitle);

            if (infoTitle != null)
                infoTitle.Text = pin.Pin.Label;

            if (infoSubTitle != null)
                infoSubTitle.Text = pin.Pin.Address;

            return view;
        }
    }

    return null;
}

At this point, if the map were to load up, we should see a pin on the map and be able to tap on it, and show some of our own custom information.

Next, we will talk about how to update the map, and better, do it from the view model.

Custom Map Renderer on Android: Getting Started

I am going to do a small series of posts on Xamarin Forms custom map renderers. Xamarin Forms comes with a stock Map control that you can add to your page, and it works pretty well if you are ok with just using the stock map pins. But if you want to do something a little bit different, or you want some kind of interaction, you will need to build up a custom renderer for each platform.

In this post, we will talk about getting started with your custom renderer on Android. You will need to use nuget to add the Xamarin.Forms.Maps package to your projects. Remember, you will also need to request an API key from Google to use Google Maps. To do that, follow these instructions. Once you have your key, you will need to add it to the AndroidManifest.xml file and and declare your app capabilities. For this, follow these instructions. Don’t forget to add in the Maps initializations to the MainActivity file on your droid project.

[Activity(
Label = "Dev Days Speakers",
Icon = "@drawable/icon",
Theme = "@style/MainTheme",
MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity
: global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    protected override void OnCreate(Bundle bundle)
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;

        base.OnCreate(bundle);
        Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();

        global::Xamarin.Forms.Forms.Init(this, bundle);
        Xamarin.FormsMaps.Init(this, bundle);

        LoadApplication(new App());
    }
}

At this point, we should be able to add in the Map control to our Page as follows:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
	xmlns="http://xamarin.com/schemas/2014/forms"
	xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
	xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
	prism:ViewModelLocator.AutowireViewModel="True"
	xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
	Title="Map"
	x:Class="DevDaysSpeakers.View.MapPage">

	<Grid>
		<maps:Map IsShowingUser="True" MapType="Street" />
	</Grid>

</ContentPage>

Let’s not forget, our app is using the Prism framework, so we need to add our new page to the navigation stack. Let’s head over to the App class and add in our new page:

protected override void RegisterTypes()
{
    // ... other registrations were here

    // our page registrations
    Container.RegisterTypeForNavigation<View.SpeakersPage,ViewModel.SpeakersViewModel>(PageKeys.Speakers);
    Container.RegisterTypeForNavigation<View.DetailsPage, ViewModel.DetailsViewModel>(PageKeys.Details);
    Container.RegisterTypeForNavigation<View.MapPage, ViewModel.MapViewModel>(PageKeys.Map);
}

To get to this page, I cheated and added a “Map” button to the SpeakersPage which will navigate over to the Map page. In the ContentPage constructor, I also set the map view region to a specific area.

If you run the app now, tapping the map button should navigate you over to the new MapPage and display a google map. If for some reason the app is crashing when starting up, try cleaning your solution and rebuilding it.

At this point, we now have standard Xamarin Forms map being displayed in our app. Next we will look at changing the look of the pins.

I should have some code up in GitHub soon, I would just like to get a bit closer to finish the final posts in the series before posting everything.