Xamarin.Forms MAP Renderer For iOS And Android

Project structure,
  • PCL (With Xaml Views) 
  • .Android project (Targeting android platform) 
  • .iOS (Targeting iOS platform) 
The objective of this project is to use Xamarin.Forms MAP on different platforms with the help of renderer (native code).
  1. Create a custom Map Control in PCL.
  2. Now, there is VisibleRegion property for Xamarin.Forms map and we can detect visible region changed in Xaml.CS.
  3. But, as soon as I added renderer, I got the visible region null. I have added one function in the custom map for visible region changed, in order to get a call back from the renderer.
  4. On Marker and pin tap, there is no event available at XAML level because we are writing the renderer.
In order to overcome this, we have to add one function in CustomMap (PinClicked).
  1. public class CustomMap : Map  
  2.   {  
  3.       //This bindable property will provide Track information for map   
  4.       //Will be accessible from Xaml view for binding  
  5.       public static readonly BindableProperty RouteCoordinatesProperty =  
  6.         BindableProperty.Create("RouteCoordinates"typeof(List<TrackInfo>), typeof(CustomMap), null, BindingMode.TwoWay);  
  7.       public List<TrackInfo> RouteCoordinates  
  8.       {  
  9.           get { return (List<TrackInfo>)GetValue(RouteCoordinatesProperty); }  
  10.           set { SetValue(RouteCoordinatesProperty, value); }  
  11.       }  
  12.   
  13.       //This bindable property will contain collection of pins  
  14.       //CustomPin Model will have Pin Metadata  
  15.       public static readonly BindableProperty CustomPinsProperty =  
  16.        BindableProperty.Create("CustomPins"typeof(List<CustomPin>), typeof(CustomMap), null, BindingMode.TwoWay);  
  17.   
  18.       public List<CustomPin> CustomPins  
  19.       {  
  20.           get { return (List<CustomPin>)GetValue(CustomPinsProperty); }  
  21.           set  
  22.           {  
  23.               SetValue(CustomPinsProperty, value);  
  24.           }  
  25.       }  
  26.   
  27.       public Action<SignDetails> PinClicked;   
  28.   
  29.       public Func<string,string,Task> OnVisibleRegionChanged;  
  30.   
  31.       public CustomMap()  
  32.       {  
  33.   
  34.       }  
  35.   
  36.         
  37.   
  38.       public async Task<GeoPosition> GetGeolocation()  
  39.       {  
  40.           var result = await App.Container.GetInstance<IGeolocationService>().GetCurrentLocation();  
  41.           return result;  
  42.       }  
  43.   }  
This CustomMap in PCL contains several binding properties in order to bind Pins and Tracks to the map from XAML.
 
CustomMap class uses CustomPin (pin details) and TrackInfo Model,
  1. public class CustomPin  
  2.   {  
  3.       //This Xamarin forms Pin Class  
  4.       //This pin Class contains several   
  5.       //Properties like Position.Latitude   
  6.       //Position.Longitude etc  
  7.       public Pin pin { getset; }  
  8.       public string pinImage { getset; }  
  9.       public string PinPosition { getset; }  
  10.      
  11.   }  
TrackInfo
  1. public class GeoPosition  
  2. {  
  3.     public double Latitude { getset; }  
  4.   
  5.     public double Longitude { getset; }  
  6. }  
  7.   
  8. public class PositionEstimateList  
  9. {  
  10.     public PositionEstimateList()  
  11.     {  
  12.         actualPosition = new List<GeoPosition>();  
  13.     }  
  14.     public List<GeoPosition> actualPosition { getset; }  
  15.   
  16. }  
  17. public class TrackInfo  
  18. {  
  19.     public string SubmitterName { getset; }  
  20.     public string trackColor { getset; }  
  21.     public List<PositionEstimateList> PositionEstimateList { getset; }  
  22. }  
Here is the XAML snippet from Home.Xaml (Where I am using my CustomMap),
  1. <local:CustomMap RouteCoordinates="{Binding RouteCoordinates}"  
  2. CustomPins="{Binding CustomPins}"  
  3. VerticalOptions="FillAndExpand"  
  4. x:Name="TrackingMap"/>    
Android renderer
  1. public class CustomMapRenderer : MapRenderer, IOnMapReadyCallback, IOnMarkerClickListener  
  2.   {  
  3.       public string TopLeft { getset; }  
  4.       public string BottomRight { getset; }  
  5.       List<Marker> markers;  
  6.       GoogleMap map;  
  7.       Polyline polyline;  
  8.       public double CacheLat { getset; }  
  9.       public double CacheLong { getset; }  
  10.       List<Polyline> PolylineBuffer = new List<Polyline>();  
  11.       bool isDrawn;  
  12.       public static Random rand = new Random();  
  13.       public static Android.Graphics.Color GetRandomColor()  
  14.       {  
  15.           int hue = rand.Next(255);  
  16.           Android.Graphics.Color color = Android.Graphics.Color.HSVToColor(  
  17.               new[] {  
  18.           hue,  
  19.           1.0f,  
  20.           1.0f,  
  21.               }  
  22.           );  
  23.           return color;  
  24.       }  
  25.       protected override void OnElementChanged(ElementChangedEventArgs<Map> e)  
  26.       {  
  27.           base.OnElementChanged(e);  
  28.           if (e.OldElement != null)  
  29.           {  
  30.               // Unsubscribe  
  31.           }  
  32.   
  33.           if (e.NewElement != null)  
  34.           {  
  35.               ((MapView)Control).GetMapAsync(this);  
  36.           }  
  37.       }  
  38.   
  39.       /// <summary>  
  40.       /// this event notifies camerachange to viewModel by using OnVisibleRegionChanged delegate  
  41.       /// </summary>  
  42.       /// <param name="sender"></param>  
  43.       /// <param name="e"></param>  
  44.       private async void Map_CameraChange(object sender, CameraChangeEventArgs e)  
  45.       {  
  46.           map = sender as GoogleMap;  
  47.           var zoom = e.Position.Zoom;  
  48.           if (((CustomMap)this.Element) != null && map != null)  
  49.           {  
  50.               var projection = map.Projection.VisibleRegion;  
  51.   
  52.               var far_right_lat = Math.Round(projection.FarLeft.Latitude, 2).ToString();  
  53.               var far_right_long = Math.Round(projection.FarLeft.Longitude, 2).ToString();  
  54.   
  55.               var near_left_lat = Math.Round(projection.NearRight.Latitude, 2).ToString();  
  56.               var near_left_long = Math.Round(projection.NearRight.Longitude, 2).ToString();  
  57.   
  58.               var centerLatLong = projection.LatLngBounds.Center;  
  59.               if (centerLatLong != null)  
  60.               {  
  61.                   App.CurrentLat = centerLatLong.Latitude;  
  62.                   App.CurrentLong = centerLatLong.Longitude;  
  63.                   App.CurrentZoomLevel = zoom;  
  64.               }  
  65.               var near_left = near_left_lat + "," + near_left_long;  
  66.               var near_Right = far_right_lat + "," + far_right_long;  
  67.               await ((CustomMap)this.Element).OnVisibleRegionChanged(near_left, near_Right);  
  68.           }  
  69.       }  
  70.   
  71.       /// <summary>  
  72.       /// This method will detect which colllection is changed at runtime  
  73.       /// and will help to redraw map accordingly.  
  74.       /// RouteCoordinatesProperty is used to draw tracks as and when information of track changes from HomeViewModel.  
  75.       /// CustomPin property is used to redraw pins as and when collection of custom pin changes from HomeViewModel.  
  76.       /// </summary>  
  77.       /// <param name="sender"></param>  
  78.       /// <param name="e"></param>  
  79.       protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)  
  80.       {  
  81.           base.OnElementPropertyChanged(sender, e);  
  82.           if (this.Element == null || this.Control == null)  
  83.               return;  
  84.           if (e.PropertyName == CustomMap.RouteCoordinatesProperty.PropertyName)  
  85.           {  
  86.               var nativeMap = Control as MapView;  
  87.   
  88.               if (((CustomMap)this.Element).RouteCoordinates == null)  
  89.               {  
  90.                   RemovePlolyline();  
  91.               }  
  92.               else  
  93.                   if (((CustomMap)this.Element).RouteCoordinates.Count == 0)  
  94.               {  
  95.                   RemovePlolyline();  
  96.               }  
  97.               else  
  98.               {  
  99.                   UpdatePolyLine();  
  100.               }  
  101.           }  
  102.           if (e.PropertyName == CustomMap.CustomPinsProperty.PropertyName)  
  103.           {  
  104.               var nativeMap = Control as MapView;  
  105.               if (markers != null)  
  106.               {  
  107.                   if (markers.Count > 0)  
  108.                       markers.ForEach(x => x.Remove());  
  109.               }  
  110.               SetMapMarkers();  
  111.           }  
  112.       }  
  113.       private void SetMapMarkers()  
  114.       {  
  115.           markers = new List<Marker>();  
  116.           if (((CustomMap)this.Element).CustomPins == null)  
  117.               return;  
  118.           foreach (var custompin in ((CustomMap)this.Element).CustomPins)  
  119.           {  
  120.               var marker = new MarkerOptions();  
  121.               marker.SetPosition(new LatLng(custompin.pin.Position.Latitude, custompin.pin.Position.Longitude));  
  122.               marker.SetTitle(custompin.pin.Label);  
  123.               marker.SetSnippet(custompin.pin.Address);  
  124.               var resource = typeof(Resource.Drawable).GetField(custompin.pinImage);  
  125.               int resourceId = 0;  
  126.               if (resource != null)  
  127.               {  
  128.                   resourceId = (int)resource.GetValue(custompin.pinImage);  
  129.               }  
  130.               if (resourceId != 0)  
  131.               {  
  132.                   marker.SetIcon(BitmapDescriptorFactory.FromResource(resourceId));  
  133.                   map.SetOnMarkerClickListener(this);  
  134.                   Marker m = map.AddMarker(marker);  
  135.                   markers.Add(m);  
  136.               }  
  137.           }  
  138.           isDrawn = true;  
  139.       }  
  140.   
  141.       /// <summary>  
  142.       /// this is OnMarker click event it occurs when pin is tapped  
  143.       /// There is PinClicked Action which is implemented in ViewModel.  
  144.       /// this provision allows us to do whatever we want from viewModel after the event   
  145.       /// Occurs.  
  146.       /// </summary>  
  147.       /// <param name="marker"></param>  
  148.       /// <returns></returns>  
  149.       public bool OnMarkerClick(Marker marker)  
  150.       {  
  151.           SignDetails _markerDetails = new SignDetails();  
  152.           foreach (var pin in ((CustomMap)this.Element).CustomPins)  
  153.           {  
  154.               if (pin.pin.Position.Latitude == marker.Position.Latitude)  
  155.               {  
  156.                   if (pin.pin.Position.Longitude == marker.Position.Longitude)  
  157.                   {  
  158.                       if (!string.IsNullOrEmpty(pin.pin.Label))  
  159.                       {  
  160.                           if (pin.pin.Label == marker.Title)  
  161.                           {  
  162.                               _markerDetails.SignLat = pin.pin.Position.Latitude.ToString();  
  163.                               _markerDetails.SignLong = pin.pin.Position.Longitude.ToString();  
  164.                               _markerDetails.SignImage = pin.pinImage;                              
  165.                           }  
  166.                       }  
  167.                   }  
  168.               }  
  169.           }  
  170.               ((CustomMap)this.Element).PinClicked(_markerDetails);  
  171.           return true;  
  172.       }  
  173.       protected override void OnLayout(bool changed, int l, int t, int r, int b)  
  174.       {  
  175.           base.OnLayout(changed, l, t, r, b);  
  176.           if (changed)  
  177.           {  
  178.               isDrawn = false;  
  179.           }  
  180.       }  
  181.   
  182.       /// <summary>  
  183.       /// this method will draw polyline on Map  
  184.       /// </summary>  
  185.       private void UpdatePolyLine()  
  186.       {  
  187.           try  
  188.           {  
  189.               RemovePlolyline();  
  190.               foreach (var routeCordinates in ((CustomMap)this.Element).RouteCoordinates)  
  191.               {  
  192.                   foreach (var positionEstimate in routeCordinates.PositionEstimateList)  
  193.                   {  
  194.                       if (routeCordinates.SubmitterName == null)  
  195.                           return;  
  196.                       var color = routeCordinates.trackColor;  
  197.                       if (!string.IsNullOrEmpty(color))  
  198.                       {  
  199.                           var trackColor = GetColor(color);  
  200.                           var polylineOptions = new PolylineOptions();  
  201.                           polylineOptions.InvokeColor(trackColor);  
  202.                           foreach (var position in positionEstimate.actualPosition)  
  203.                           {  
  204.                               polylineOptions.Add(new LatLng(position.Latitude, position.Longitude));  
  205.                           }  
  206.                           polyline = map.AddPolyline(polylineOptions);  
  207.                           PolylineBuffer.Add(polyline);  
  208.                       }  
  209.                   }  
  210.               }  
  211.           }  
  212.           catch (Exception ex)  
  213.           {  
  214.             //Log exception to hockey app
  215.           }  
  216.       }  
  217.   
  218.       /// <summary>  
  219.       /// here it is restricted to show the 5 tracks only so 5 colors are shown for 5 tracks  
  220.       /// one can easily modify by using random color and showing legends accordingly for tracks.  
  221.       /// </summary>  
  222.       /// <param name="color"></param>  
  223.       /// <returns></returns>  
  224.       private Android.Graphics.Color GetColor(string color)  
  225.       {  
  226.           Android.Graphics.Color trackColor = Android.Graphics.Color.Orange;  
  227.           switch (color)  
  228.           {  
  229.               case "1":  
  230.                   trackColor = Android.Graphics.Color.Rgb(77, 123, 224);  
  231.                   break;  
  232.               case "2":  
  233.                   trackColor = Android.Graphics.Color.Rgb(50, 193, 214);  
  234.                   break;  
  235.               case "3":  
  236.                   trackColor = Android.Graphics.Color.Rgb(163, 178, 78);  
  237.                   break;  
  238.   
  239.               case "4":  
  240.                   trackColor = Android.Graphics.Color.Rgb(187, 93, 153);  
  241.                   break;  
  242.               case "5":  
  243.                   trackColor = Android.Graphics.Color.Rgb(175, 98, 46);  
  244.                   break;  
  245.           }  
  246.           return trackColor;  
  247.       }  
  248.   
  249.       /// <summary>  
  250.       /// Removing multiple polylines at once  
  251.       /// </summary>  
  252.       private void RemovePlolyline()  
  253.       {  
  254.           foreach (Polyline line in PolylineBuffer)  
  255.           {  
  256.               line.Remove();  
  257.           }  
  258.           PolylineBuffer.Clear();  
  259.       }  
  260.       bool _isReady;  
  261.       public async void OnMapReady(GoogleMap googleMap)  
  262.       {  
  263.           try  
  264.           {  
  265.               if (_isReady) return;  
  266.               _isReady = true;  
  267.               map = googleMap;  
  268.               var bounds = googleMap.Projection.VisibleRegion;  
  269.               if (App.CurrentLat == 0 && App.CurrentLong == 0)  
  270.               {  
  271.                   var CurrentPosition = await ((CustomMap)this.Element).GetGeolocation();  
  272.                   if (CurrentPosition != null)  
  273.                   {  
  274.                       App.CurrentLat = CurrentPosition.Latitude;  
  275.                       App.CurrentLong = CurrentPosition.Longitude;  
  276.                       map.MoveCamera(CameraUpdateFactory.NewLatLngZoom(new LatLng(App.CurrentLat, App.CurrentLong),  
  277.                           App.CurrentZoomLevel));  
  278.                   }  
  279.                   else  
  280.                   {  
  281.                       map.MoveCamera(CameraUpdateFactory.NewLatLngZoom(new LatLng(52.52, 13.40), App.CurrentZoomLevel));  
  282.                   }  
  283.               }  
  284.               else  
  285.               {  
  286.                   map.MoveCamera(CameraUpdateFactory.NewLatLngZoom(new LatLng(App.CurrentLat, App.CurrentLong), App.CurrentZoomLevel));  
  287.               }  
  288.               map.CameraChange += Map_CameraChange;  
  289.               await Task.Run(() =>  
  290.               {  
  291.                   if (((CustomMap)this.Element).RouteCoordinates != null)  
  292.                   {  
  293.                       if (((CustomMap)this.Element).RouteCoordinates.Count > 0)  
  294.                       {  
  295.                           UpdatePolyLine();  
  296.                       }  
  297.                   }  
  298.               });  
  299.               await Task.Run(() =>  
  300.               {  
  301.                   if (((CustomMap)this.Element).CustomPins != null)  
  302.                   {  
  303.                       if (((CustomMap)this.Element).CustomPins.Count > 0)  
  304.                       {  
  305.                           SetMapMarkers();  
  306.                       }  
  307.                   }  
  308.               });  
  309.           }  
  310.           catch (Exception ex)  
  311.           {  
  312.             //Log exception to hockey app
  313.           }  
  314.       }  
  315.   }  
iOS Renderer [I got the wiered issue that mkMapview automatically resets its location to initial position if we move anywhere in map].
  1. public class CustomMapRenderer : MapRenderer  
  2.     {  
  3.         List<string> tempImages = new List<string>();  
  4.         public string TopLeft { getset; }  
  5.         public string BottomRight { getset; }  
  6.         public bool IsRegionChange = true;  
  7.         List<MKPolyline> _mkPolyLineList = new List<MKPolyline>();  
  8.         MKPolylineRenderer polylineRenderer;  
  9.         MKPolyline routeOverlay;  
  10.         CustomMap map;  
  11.         MKMapView nativeMap;  
  12.         public CustomMapRenderer()  
  13.         {  
  14.   
  15.         }  
  16.         protected override async void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)  
  17.         {  
  18.             base.OnElementPropertyChanged(sender, e);  
  19.             if (nativeMap == null)  
  20.             {  
  21.                 nativeMap = Control as MKMapView;  
  22.                 nativeMap.ZoomEnabled = true;  
  23.                 nativeMap.ScrollEnabled = true;  
  24.                 map = (CustomMap)sender;  
  25.   
  26.             }  
  27.             if (nativeMap != null)  
  28.                 if (IsRegionChange)  
  29.                 {  
  30.                     await InitializeMap(sender);  
  31.                     ReDraw();  
  32.                     IsRegionChange = false;  
  33.                 }  
  34.             if ((this.Element == null) || (this.Control == null))  
  35.                 return;  
  36.   
  37.             if (e.PropertyName == CustomMap.RouteCoordinatesProperty.PropertyName)  
  38.             {  
  39.                 map = (CustomMap)sender;  
  40.   
  41.                 if (nativeMap == null)  
  42.                     return;  
  43.   
  44.                 if (map.RouteCoordinates.Count > 0)  
  45.                     UpdatePolyLine();  
  46.                 else  
  47.                     RemovePolyline();  
  48.             }  
  49.             if (e.PropertyName == CustomMap.CustomPinsProperty.PropertyName)  
  50.             {  
  51.                 map = (CustomMap)sender;  
  52.                 map.Pins.Clear();  
  53.                 if (map.CustomPins != null)  
  54.                 {  
  55.                     foreach (var customPin in map.CustomPins)  
  56.                     {  
  57.                         map.Pins.Add(customPin.pin);  
  58.                     }  
  59.                 }  
  60.                 InvokePlotPins();  
  61.             }  
  62.         }  
  63.   
  64.         private void RemovePolyline()  
  65.         {  
  66.             try  
  67.             {  
  68.                 if (_mkPolyLineList != null)  
  69.                 {  
  70.                     if (_mkPolyLineList.Count > 0)  
  71.                     {  
  72.                         var overlays = nativeMap.Overlays;  
  73.                         nativeMap.RemoveOverlays(overlays);                         
  74.                         _mkPolyLineList.Clear();  
  75.                     }  
  76.   
  77.   
  78.                 }  
  79.             }  
  80.             catch (Exception ex)  
  81.             {  
  82.                 //Log Exception to hockey app  
  83.             }  
  84.         }  
  85.   
  86.         private void ReDraw()  
  87.         {  
  88.             if (map == null)  
  89.                 return;  
  90.             if (map.RouteCoordinates != null)  
  91.                 if (map.RouteCoordinates.Count > 0)  
  92.                     UpdatePolyLine();  
  93.             if (map.CustomPins != null)  
  94.                 if (map.CustomPins.Count > 0)  
  95.                     InvokePlotPins();  
  96.         }  
  97.   
  98.         private async System.Threading.Tasks.Task InitializeMap(object sender)  
  99.         {  
  100.             try  
  101.             {  
  102.                 map = (CustomMap)sender;  
  103.                 nativeMap.RegionChanged += NativeMap_RegionChanged;  
  104.   
  105.                 if (App.CurrentLat != 0 && App.CurrentLong != 0)  
  106.                     map.MoveToRegion(new MapSpan(new Position(App.CurrentLat, App.CurrentLong), App.LatitudeDelta, App.LongitudeDelta));  
  107.                 else  
  108.                 {  
  109.                     var location = await map.GetGeolocation();  
  110.                     if (location != null)  
  111.                     {  
  112.                         map.MoveToRegion(new MapSpan(new Position(location.Latitude,  
  113.                             location.Longitude), 0.01, 0.01)); // where the map should start  
  114.                     }  
  115.                     else  
  116.                     {  
  117.                         map.MoveToRegion(new MapSpan(new Position  
  118.                             (52.5200, 13.4050), 0.01, 0.01)); // where the map should start  
  119.                     }  
  120.                 }  
  121.             }  
  122.             catch (Exception ex)  
  123.             {  
  124.                 //Log Exception to hockey app  
  125.             }  
  126.   
  127.         }  
  128.   
  129.   
  130.         private async void NativeMap_RegionChanged(object sender, MKMapViewChangeEventArgs e)  
  131.         {  
  132.             try  
  133.             {  
  134.                 if (Element == null)  
  135.                     return;  
  136.                 var position = nativeMap.VisibleMapRect;  
  137.                 CalculateBoundingCoordinates(nativeMap);  
  138.                 nativeMap.ZoomEnabled = true;  
  139.                 nativeMap.ScrollEnabled = true;  
  140.                 nativeMap.RegionChanged -= NativeMap_RegionChanged;  
  141.                 //This solves the problem of MKMAPVIEW to Reset its view to FirstLocation  
  142.                 map.MoveToRegion(new MapSpan(new Position(nativeMap.Region.Center.Latitude, nativeMap.Region.Center.Longitude), nativeMap.Region.Span.LatitudeDelta, nativeMap.Region.Span.LongitudeDelta));  
  143.                 nativeMap.RegionChanged += NativeMap_RegionChanged;  
  144.                 if (BottomRight != null && TopLeft != null)  
  145.                 {  
  146.                     App.CurrentLat = nativeMap.Region.Center.Latitude;  
  147.                     App.CurrentLong = nativeMap.Region.Center.Longitude;  
  148.                     App.LatitudeDelta = nativeMap.Region.Span.LatitudeDelta;  
  149.                     App.LongitudeDelta = nativeMap.Region.Span.LongitudeDelta;  
  150.                     await map.OnVisibleRegionChanged(BottomRight, TopLeft);  
  151.                 }  
  152.             }  
  153.             catch (Exception ex)  
  154.             {  
  155.                 //log Exception to hockey app  
  156.             }  
  157.         }  
  158.   
  159.         /// <summary>  
  160.         /// Calulates Top-Left and Right-Bottom of Map  
  161.         /// </summary>  
  162.         /// <param name="region"></param>  
  163.         /// <returns></returns>    
  164.         public void CalculateBoundingCoordinates(MKMapView map)  
  165.         {  
  166.             var center = map.Region.Center;  
  167.             var halfheightDegrees = map.Region.Span.LatitudeDelta / 2;  
  168.             var halfwidthDegrees = map.Region.Span.LongitudeDelta / 2;  
  169.   
  170.   
  171.             var left = Math.Round(center.Longitude - halfwidthDegrees, 2);  
  172.             var right = Math.Round(center.Longitude + halfwidthDegrees, 2);  
  173.             var top = Math.Round(center.Latitude + halfheightDegrees, 2);  
  174.             var bottom = Math.Round(center.Latitude - halfheightDegrees, 2);  
  175.   
  176.             if (left < -180) left = 180 + (180 + left);  
  177.             if (right > 180) right = (right - 180) - 180;  
  178.             TopLeft = top.ToString() + "," + left.ToString();  
  179.             BottomRight = bottom.ToString() + "," + right.ToString();  
  180.         }  
  181.         private void InvokePlotPins()  
  182.         {  
  183.             if (nativeMap == null)  
  184.                 return;  
  185.   
  186.             nativeMap.GetViewForAnnotation = GetViewForAnnotation;  
  187.         }  
  188.         private MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)  
  189.         {  
  190.             MKAnnotationView annotationView = null;  
  191.             if (annotation is MKUserLocation)  
  192.                 return null;  
  193.             var mkAnnotation = annotation as MKPointAnnotation;  
  194.             if (mkAnnotation == null)  
  195.                 return null;  
  196.             var customPin = GetCustomPin(mkAnnotation);  
  197.             if (customPin == null)  
  198.             {  
  199.                 return null;  
  200.             }  
  201.             SignDetails _signDetails = new SignDetails();  
  202.             _signDetails.SignLat = customPin.pin.Position.Latitude.ToString();  
  203.             _signDetails.SignLong = customPin.pin.Position.Longitude.ToString();  
  204.             _signDetails.SignImage = customPin.pinImage;  
  205.             
  206.           
  207.                 annotationView = new CustomMKAnnotationView(annotation, customPin.pinImage)  
  208.                 {  
  209.                     CurrentSignDetails = new SignDetails(_signDetails),  
  210.                     PinTouch = map.PinClicked  
  211.                 };  
  212.                 annotationView.Image = UIImage.FromFile(customPin.pinImage + ".png");  
  213.   
  214.            
  215.             return annotationView;  
  216.         }  
  217.         private CustomPin GetCustomPin(MKPointAnnotation mkAnnotation)  
  218.         {  
  219.             var position = new Position(mkAnnotation.Coordinate.Latitude, mkAnnotation.Coordinate.Longitude);  
  220.             foreach (var pin in map.CustomPins)  
  221.             {  
  222.                 if (pin.pin.Position == position)  
  223.                 {  
  224.                     if (!string.IsNullOrEmpty(pin.pin.Label))  
  225.                     {  
  226.                         if (pin.pin.Label == mkAnnotation.Title)  
  227.                             return pin;  
  228.                     }  
  229.                 }  
  230.             }  
  231.             return null;  
  232.         }  
  233.   
  234.         [Foundation.Export("mapView:rendererForOverlay:")]  
  235.         MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlay)  
  236.         {  
  237.             if (polylineRenderer == null)  
  238.             {  
  239.                 var o = ObjCRuntime.Runtime.GetNSObject(overlay.Handle) as MKPolyline;  
  240.                 polylineRenderer = new MKPolylineRenderer(o);  
  241.                 polylineRenderer.FillColor = UIColor.Blue;  
  242.                 polylineRenderer.StrokeColor = UIColor.Blue;  
  243.                 polylineRenderer.LineWidth = 2;  
  244.                 polylineRenderer.Alpha = 0.4f;  
  245.             }  
  246.             return polylineRenderer;  
  247.         }  
  248.         private void UpdatePolyLine()  
  249.         {  
  250.             if (nativeMap == null)  
  251.                 return;  
  252.             try  
  253.             {  
  254.                 RemovePolyline();  
  255.                 nativeMap.OverlayRenderer = GetOverlayRenderer;  
  256.                 foreach (var routeCordinates in map.RouteCoordinates)  
  257.                 {  
  258.   
  259.                     foreach (var positionEstimate in routeCordinates.PositionEstimateList)  
  260.                     {  
  261.                         int index = 0;  
  262.                         CLLocationCoordinate2D[] coords = new CLLocationCoordinate2D[positionEstimate.actualPosition.Count];  
  263.                         foreach (var position in positionEstimate.actualPosition)  
  264.                         {  
  265.                             coords[index] = new CLLocationCoordinate2D(position.Latitude, position.Longitude);  
  266.                             index++;  
  267.                         }  
  268.                         routeOverlay = MKPolyline.FromCoordinates(coords);  
  269.                         var o = ObjCRuntime.Runtime.GetNSObject(routeOverlay.Handle) as MKPolyline;  
  270.                         polylineRenderer = new MKPolylineRenderer(o);  
  271.                         var trackColor = GetColor(routeCordinates.trackColor);  
  272.                         polylineRenderer.StrokeColor = trackColor;  
  273.                         polylineRenderer.LineWidth = 1;  
  274.                         nativeMap.AddOverlay(routeOverlay);  
  275.                         _mkPolyLineList.Add(routeOverlay);  
  276.                     }  
  277.                 }  
  278.             }  
  279.             catch (Exception ex)  
  280.             {  
  281.                 //Log exception to hockey app  
  282.             }  
  283.         }  
  284.   
  285.         private UIColor GetColor(string color)  
  286.         {  
  287.             UIColor trackColor = UIColor.Orange;  
  288.             switch (color)  
  289.             {  
  290.                 case "1":  
  291.                     trackColor = UIColor.FromRGB(77, 123, 224);  
  292.                     break;  
  293.                 case "2":  
  294.                     trackColor = UIColor.FromRGB(50, 193, 214);  
  295.                     break;  
  296.                 case "3":  
  297.                     trackColor = UIColor.FromRGB(163, 178, 78);  
  298.                     break;  
  299.   
  300.                 case "4":  
  301.                     trackColor = UIColor.FromRGB(187, 93, 153);  
  302.                     break;  
  303.                 case "5":  
  304.                     trackColor = UIColor.FromRGB(175, 98, 46);  
  305.                     break;  
  306.             }  
  307.             return trackColor;  
  308.         }  
  309.     }  
Now, we have everything in place. We can use TrackingMap.PinClicked and TrackingMap.OnVisibleRegionChanged in Xaml.cs to get the visibleRegion changed. And, we can bind our collections from ViewModel to the map so as to show pins and track information. It also allows redrawing the content as the collection changes. 
Ebook Download
View all
Learn
View all