Xamarin.forms.Maps - Tap to get a position on the map...
Xamarin.Forms.Map component
Xamarin.Forms.Maps is a great component. It gives you the possibility to deal with maps and geolocation problematics in a few lines of code.This "Map" component internally use native map controls for iOS, Android and Windows Phone:
You can display and locate "Pins" with custom information on the map. I suggest you to read the reference links above.
Display pins with custom information
The problem
But actually, in my mind, the Map control miss a very important feature:
--> Tap on the map to get the relative location (to put a new pin, to get the relative address...)
The workaround
Use renderers
Actually, to solve this problem, we need to implement custom renderers for iOS and Android. Maybe this feature will be implemented later by Xamarin...
So here is my solution (3 code files below):
- ExtMap.cs: overloaded map control that will contain our "Tapped" event to get the tapped location
- ExtMapRenderer (iOS): renderer for the iOS platform
- ExtMapRenderer (Android): renderer for the android platform
Each renderer will catch and manage the TAP event for us.
ExtMap control
This is the Xamarin forms map control that will override the Xamarin Forms Maps control. We just add the 'Tapped' event and logic to this implementation:
/// <summary> /// Extended map: /// allow user to tap a point on the map to get the relative position. /// </summary> public class ExtMap : Map { /// <summary> /// Event thrown when the user taps on the map /// </summary> public event EventHandler<MapTapEventArgs> Tapped; #region Constructors /// <summary> /// Default constructor /// </summary> public ExtMap() { } /// <summary> /// Constructor that takes a region /// </summary> /// <param name="region"></param> public ExtMap(MapSpan region) : base(region) { } #endregion public void OnTap(Position coordinate) { OnTap(new MapTapEventArgs { Position = coordinate }); } protected virtual void OnTap(MapTapEventArgs e) { var handler = Tapped; if (handler != null) handler(this, e); } } /// <summary> /// Event args used with maps, when the user tap on it /// </summary> public class MapTapEventArgs : EventArgs { public Position Position { get; set; } }
iOS Renderer
[assembly: ExportRenderer(typeof(ExtMap), typeof(ExtMapRenderer))] namespace MyApp.iOS.CustomRenderers { /// <summary> /// Renderer for the xamarin ios map control /// </summary> public class ExtMapRenderer : MapRenderer { private readonly UITapGestureRecognizer _tapRecogniser; public ExtMapRenderer() { _tapRecogniser = new UITapGestureRecognizer(OnTap) { NumberOfTapsRequired = 1, NumberOfTouchesRequired = 1 }; } private void OnTap(UITapGestureRecognizer recognizer) { var cgPoint = recognizer.LocationInView(Control); var location = ((MKMapView)Control).ConvertPoint(cgPoint, Control); ((ExtMap)Element).OnTap(new Position(location.Latitude, location.Longitude)); } protected override void OnElementChanged(ElementChangedEventArgs<View> e) { if (Control != null) Control.RemoveGestureRecognizer(_tapRecogniser); base.OnElementChanged(e); if (Control != null) Control.AddGestureRecognizer(_tapRecogniser); } } }
Android Renderer
[assembly: ExportRenderer(typeof(ExtMap), typeof(ExtMapRenderer))] namespace MyApp.Android.CustomRenderers { /// <summary> /// Renderer for the xamarin map. /// Enable user to get a position by taping on the map. /// </summary> public class ExtMapRenderer : MapRenderer, IOnMapReadyCallback { // We use a native google map for Android private GoogleMap _map; public void OnMapReady(GoogleMap googleMap) { _map = googleMap; if (_map != null) _map.MapClick += googleMap_MapClick; } protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e) { if (_map != null) _map.MapClick -= googleMap_MapClick; base.OnElementChanged(e); if (Control != null) ((MapView)Control).GetMapAsync(this); } private void googleMap_MapClick(object sender, GoogleMap.MapClickEventArgs e) { ((ExtMap)Element).OnTap(new Position(e.Point.Latitude, e.Point.Longitude)); } } }
So we can now easily pick a location and get the relative address thanks to a geocoding service. You can use Google Apis or use the GeoCoder class provided by Xamarin (see references below):
Tap on the map to get the street address.
References:
Xamarin Forms Map control overview:http://developer.xamarin.com/guides/cross-platform/xamarin-forms/user-interface/map/
Xamarin Forms Geocoding, GeoCoder class
http://developer.xamarin.com/recipes/cross-platform/xamarin-forms/reverse-geocode/
http://developer.xamarin.com/recipes/cross-platform/xamarin-forms/geocode/
Hi. Thank you for this very usefull tutorial. But how about the UWP solution. Do you have any hint for me?
ReplyDeleteThank you!
I dont get it. You should have finished the GUI part, that would be great,
ReplyDeleteAndroid renderer stopped working on Xamarin.Forms 2.3.4
ReplyDeleteI get a exception in ((ExtMap)Element).OnTap(new Position(e.Point.Latitude, e.Point.Longitude)); call System.InvalidCastException: Specified cast is not valid.
ReplyDeleteits great i dont know why they missed this important functionality
ReplyDeleteThanks, saved me much effort to see how to organize this.
ReplyDeleteExcellent !! Tanks you, Work and Run
ReplyDeleteThank you for taking the time to write such an informative post. Your blog is not only informative, but it is also very creative.
ReplyDeleteiOS app development company