How to Customize a Map

Maps are widely used in the Mobile Applications. But, If we talk about Xamarin, then it provide a Xamarin.Forms.Maps library for both Android & iOS Platforms. As this library is in Forms, it has only limited features such as loading a Map view, add markers etc. If you want to customize the Map according to your requirements, then you have to use Custom Renderer.

You can customize the Map in the following ways:

1: You can provide the marker icon on your own.
2: You can handle the click of the marker icon.
3: You can draw shapes on the Map for Highlighting a Route.

Now, we are implementing the various customizations that we discussed above. First, you have to create a class in Xamarin.Forms which extends the Xamarin.Forms.Maps.

public class ExtendedMap : Map
    {
         
        //This contains the Map instance
        IMap _map;
        //This contains the name of the marker icon
        public string ImageSource;
        //This contains your project package name
        public string PackageName;
        //This is a public event used to get the click of the marker pin
        public event EventHandler PinClicked;


        public ExtendedMap()
        {

        }

        #region Methods

        public void SetMarkerIcon(string resource, string packageName)
        {
            ImageSource = resource;
            PackageName = packageName;
        }

        public void HandleClick()
        {
            if(PinClicked!=null)
            {
                PinClicked.Invoke(this, EventArgs.Empty);
            }
        }

       #endregion

    }

Now, We'll figure out how we used this code from the Map Page in Forms.

public partial class MapPage: ContentPage
{
	public MapPage()
	{
                    
	    InitializeComponent ();
            var shape = new ExtendedMap();

            //Code for adding a pin on the map.
            shape.Pins.Add(new Pin()
            {
                Position = new Position(37.785559, -122.396728),
                Label = "Xamarians",
                Address = "E-29, Xamarians, Noida sec-11, India."
            });

            //Code for provide icon to marker
            shape.SetMarkerIcon("hotspot.png", "");
    
            //Code for creating the event handler when user clicked the pin.
            shape.PinClicked += Shape_PinClicked;
            Content = shape;
	}

        private void Shape_PinClicked(object sender, EventArgs e)
        {
            DisplayAlert("Success", "You just clicked a marker", "ok");
        }
    }

We have done with the Forms implementation. Now, we proceed to Custom Renderers.

Custom Renderer in Android

[assembly: ExportRenderer(typeof(ExtendedMap), typeof(ExtendedMapRenderer))]
namespace Xamarians.Maps.Droid
{
    public class ExtendedMapRenderer : MapRenderer, IOnMapReadyCallback, IMap
    {
        ExtendedMap element;
        GoogleMap map;

        protected override void OnMapReady(GoogleMap googleMap)
        {
            map = googleMap;
            map.MarkerClick += Map_MarkerClick;

            var bd = Forms.Context.Resources.GetDrawable(element.ImageSource) as BitmapDrawable;
            foreach (var pin in element.Pins)
            {
                var marker = new MarkerOptions();
                marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
                marker.SetTitle(pin.Label);
                marker.SetSnippet(pin.Address);
                if (bd != null && bd.Bitmap != null)
                    marker.SetIcon(BitmapDescriptorFactory.FromBitmap(bd.Bitmap));
                map.AddMarker(marker);
            }
            bd.Dispose();
        }

        private void Map_MarkerClick(object sender, GoogleMap.MarkerClickEventArgs e)
        {
            if (e.Marker == null)
                return;
            element.HandleClick();
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Map> e)
        {
            base.OnElementChanged(e);
            if (e.NewElement == null)
                return;
            element = Element as ExtendedMap;
        }

    }
}
Custom Renderer in iOS
[assembly: ExportRenderer(typeof(ExtendedMap), typeof(ExtendedMapRenderer))]
namespace Xamarians.Maps.iOS
{
    public class ExtendedMapRenderer : MapRenderer
    {
        List<MKAnnotation> annotations = new List<MKAnnotation>();
        static ExtendedMap element;
        MKMapView nativeMap;
        MapDelegate mapDelegate;

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

	     if (e.NewElement == null)
	         return;
	     if (e.OldElement != null)
	     {
	         nativeMap = Control as MKMapView;
	     }
	     element = Element as ExtendedMap;
	     mapDelegate = new MapDelegate();
	     nativeMap = Control as MKMapView;
	     nativeMap.Delegate = null;
	     nativeMap.Delegate = mapDelegate;

	     var formsMap = (ExtendedMap)e.NewElement;
			
	     int index = 0;
             int idCounter = 1;
	     string icon = "";
	     icon = element.ImageSource;
			

	      foreach (var position in element.Pins)
	      {
		    var annot = new CustomAnnotation(new CLLocationCoordinate2D(position.Latitude, position.Longitude), 
                                    element.CustomPins.FirstOrDefault().Label, "", false, icon);
		    annot.Id = idCounter++;
		    nativeMap.AddAnnotation(annot);
		    //pinCollection.Add(annot.Id, item);
		    annotations.Add(annot);
		    coords[index] = new CLLocationCoordinate2D(position.Latitude, position.Longitude);
		    index++;
	       }
	}

	public class MapDelegate : MKMapViewDelegate
        {
            bool _regionChanged = false;
            public event EventHandler RegionChangedEvent;
            public event EventHandler<MKUserLocation> UserLocationChanged;
            protected string annotationId = "MapAnnotation";

            public override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
            {
                MKAnnotationView annotationView = null;
		MKPinAnnotationView annotationPinView = null;
                if (annotation is MKUserLocation)
                    return null;
                var cAnnotation = annotation as CustomAnnotation;
		if (annotation is CustomAnnotation)
		{

		    // show conference annotation

		    annotationView = mapView.DequeueReusableAnnotation(annotationId);
		    if (annotationView == null)
		    {
			annotationView = new MKAnnotationView(annotation, annotationId);
		    }
		    if (annotationPinView == null)
		    {
			annotationPinView = new MKPinAnnotationView(annotation, annotationId);
		    }

		    annotationView.CanShowCallout = true;

				
		    UIImage image = (annotationPinView as MKPinAnnotationView).Image;

		    if (!string.IsNullOrWhiteSpace(((CustomAnnotation)annotation).Icon))
		    {
			image = UIImage.FromBundle(((CustomAnnotation)annotation).Icon);
			annotationView.Image = image;
			annotationView.CenterOffset = new CGPoint(0, -image.Size.Height / 2);
						
        	     }
	             else
		     {
			annotationView.Image = image;
			annotationView.CenterOffset = new CGPoint(9, -image.Size.Height/3);
   		     }
                    var detailButton = UIButton.FromType(UIButtonType.InfoLight);
                    //// detailButton.SetImage(UIImage.FromFile("ic_lesson_hotspot_start.png"), UIControlState.Normal);
                    detailButton.TouchUpInside += (s, e) =>
                    {
                        if (cAnnotation.Id != 0)
                        {
                            element.HandleClick();
                        }

                    };

                    annotationView.RightCalloutAccessoryView = detailButton;
                }

                return annotationView;

            }
       }
   }

    public class CustomAnnotation : MKAnnotation
    {
            string title, subtitle;
            CLLocationCoordinate2D coordinate;

            public EventHandler<CLLocationCoordinate2D> DragEnd;
            public EventHandler Clicked;

            public bool IsLocationIcon { get; set; }

            #region Overridden Properties


            public override CLLocationCoordinate2D Coordinate { get { return coordinate; } }
            public override string Title { get { return title; } }
            public override string Subtitle { get { return subtitle; } }

            #endregion
            public int Id { get; set; }
            public string Icon { get; set; }
            public bool IsDraggable { get; set; }


            public CustomAnnotation(CLLocationCoordinate2D coordinate, string title, string subtitle,  bool locationIcon, string icon)
            {
                this.coordinate = coordinate;
                this.title = title;
                this.subtitle = subtitle;
                this.Icon = icon;
                this.IsLocationIcon = locationIcon;
            }

        }
}
You can download the sample from Here.
You can install the Nuget package from Here.
You can install the Component from Here.