Xamarin Diaries – Mapas

En Solid GEAR son varios años ya los que llevamos construyendo aplicaciones multiplataforma con Xamarin. Todo empezó al descubrir que era una plataforma fantástica para ayudarnos a los desarrolladores a construir aplicaciones para iOS y Android. La verdad es que hasta ahí, no os descubrimos nada nuevo.

En mi opinión, lo que realmente marcó la diferencia  e hizo que estandarizáramos Xamarin como plataforma de desarrollo fue cuando interiorizamos su arquitectura desacoplada, el patrón MVVM y cuando, ayudados por ámbos, empezamos a proporcionar a nuestras apps con funcionamiento offline por defecto.

Las consecuencias directas de estas prácticas han sido unos desarrollos más rápidos y productos finales más robustos y fiables.

Además, Xamarin se ha convertido, gracias a nuget, en una plataforma fantástica de colaboración donde la comunidad comparte sus desarrollos estandarizados, en forma de plugins, de manera que otros desarrolladores pueden beneficiarse de su uso. Normalmente además estos plugins son liberados bajo licencia MIT.

En línea con la esencia de Xamarin, por tanto, desde SG hemos decidido compartir algunas de las soluciones que hemos ido encontrando para proporcionar a nuestros clientes con las mejores UX posibles, siempre intentando mantener los costes bajo control. Abrimos aquí lo que hemos tenido a bien llamar “Xamarin Diaries”.

Trabajando con Mapas en Xamarin Forms

Si te planteas añadir algún tipo de mapa a tu aplicación Xamarin, lo más probable es que tu primer intento sea con el plugin Xamarin.Forms.Maps.

Perfecto, pero este plugin simplemente te permite configurar una región con una posición central y crear pins estáticos, prácticamente nada más.

Todas sus características están bien explicadas aquí: https://developer.xamarin.com/guides/xamarin-forms/user-interface/map/ así que no merece la pena que las repitamos nosotros.

Entonces, qué pasa si lo que quiero es un mapa interactivo? Tengo que pelearme con custom renderer, siempre un camino oscuro y difícil?

El uso de custom renderers es perfecto cuando no tenemos más alternativa, pero para que duplicar esfuerzos si ya hay alguien que lo ha hecho por nosotros: https://github.com/TorbenK/TK.CustomMap Extended Map Control for Xamarin.Forms.Maps.

TK.CustomMap es un esfuerzo de la comunidad para dotar a las aplicaciones Xamarin de un Mapa con funcionalidades avanzadas como:

  • Touch events
  • Barra de búsquedas
  • MVVM compliance
  • UI responsiva
  • Multiplataforma

Lo que encaja perfectamente con lo que necesitamos. Nuestra necesidad era la de proporcionar al usuario un mapa y una barra de búsqueda de direcciones. Cuando el usuario encuentra la dirección, el mapa le posiciona en esa dirección y permite al usuario seleccionar un punto sobre el mapa.

¡Comencemos!

xamarin diaries - pin xamarin diaries - search

En una nueva página xaml, por ejemplo MapPage.xaml, añadimos un nuevo RelativeLayout:

<ContentPage.Content>
       <RelativeLayout x:Name="relativeLayout" VerticalOptions="FillAndExpand" />
</ContentPage.Content>

En este caso, hacemos casi toda la inicialización en el code behind (es la forma recomendada de hacerlo según la documentación del autor):

public MapPage(NewPlotViewModel vm)
{
            InitializeComponent();

            ViewModel.NewPlot = vm;

            var madrid = new Position(40.4381307, -3.8199654);

            var map = new TKCustomMap();

            map.IsShowingUser = true;
            map.HasZoomEnabled = true;
            map.MapType = MapType.Hybrid;
            map.SetBinding(TKCustomMap.MapLongPressCommandProperty, "MapLongPressCommand");
            map.SetBinding(TKCustomMap.MapCenterProperty, "MapCenter", BindingMode.TwoWay);
            map.SetBinding(TKCustomMap.MapRegionProperty, "MapRegion", BindingMode.TwoWay);
            map.SetBinding(TKCustomMap.CustomPinsProperty, "Pins", BindingMode.TwoWay);
            map.IsRegionChangeAnimated = true;

      ViewModel.MapCenter = madrid;
      ViewModel.MapRegion = MapSpan.FromCenterAndRadius(madrid, Distance.FromKilometers(2));

            var autoComplete = new PlacesAutoComplete(true)
            {
                ApiToUse = PlacesAutoComplete.PlacesApi.Native,
            };

            autoComplete.SetBinding(PlacesAutoComplete.BoundsProperty, "MapRegion", BindingMode.TwoWay);
            autoComplete.SetBinding(PlacesAutoComplete.PlaceSelectedCommandProperty, "PlaceSelectedCommand");

            relativeLayout.Children.Add(
          map,
          Constraint.RelativeToView(autoComplete, (r, v) => v.X),
          Constraint.RelativeToView(autoComplete, (r, v) => autoComplete.HeightOfSearchBar),
          heightConstraint: Constraint.RelativeToParent((r) => r.Height - autoComplete.HeightOfSearchBar * 2),
          widthConstraint: Constraint.RelativeToView(autoComplete, (r, v) => v.Width)
);


            relativeLayout.Children.Add(
                autoComplete,
                Constraint.Constant(0),
                Constraint.Constant(0)
            );
}

Estás pocas líneas de código hacen la magia que necesitamos. En él se inicialización dos cosas: el Mapa y la barra de búsquedas. A la barra de búsquedas le decimos que utilice la api nativa. Hay otras opciones de búsqueda pero la nativa es suficientemente sencilla y funcional. Eso sí, para Android recordad que necesitamos una key especial que conseguiremos en la consola de developers de Google y que deberemos introducir en el archivo AndroidManifest.xml:

<application android:label=”MyApp">
       <meta-data android:name="com.google.android.geo.API_KEY" android:value="{API_KEY}" />
</application>

Volviendo a fijarnos en la inicialización, vemos que las propiedades del mapa están bindeadas al view model. Concretamente bindeamos las propiedades MapRegion y MapCenter.

Ya en el ViewModel, usamos un nuevo plugin: Geolocator https://github.com/jamesmontemagno/GeolocatorPlugin para conseguir la posición actual y poder posicionar el mapa inicialmente.

private async Task GetCurrentLocationAsync()
{
               var position = await geolocator.GetPositionAsync();

               if (position != null)
               {
                   MapCenter = new Position(position.Latitude, position.Longitude);
                   MapRegion = MapSpan.FromCenterAndRadius(MapCenter, Distance.FromKilometers(2));
               }
}

También, como funcionalidad avanzada, usamos esa posición actual para añadir al mapa de ios el típico botón “Llevame a la posición actual” habitual en los mapas de Google.

Cuando el usuario realiza una búsqueda y selecciona una de las sugerencias, recuperamos ese valor devuelto y modificamos la propiedad MapCenter del ViewModel:

private async Task SelectPlaceAsync(IPlaceResult place)
{
           if (Device.RuntimePlatform == Device.Android)
           {
               var prediction = (TKNativeAndroidPlaceResult)place;

               var details = await TKNativePlacesApi.Instance.GetDetails(prediction.PlaceId);
               if (details != null)
               {
                   MapCenter = details.Coordinate;
               }
           }
           else if (Device.RuntimePlatform == Device.iOS)
           {
               var prediction = (TKNativeiOSPlaceResult)place;

               if (prediction != null)
               {
                   MapCenter = prediction.Details.Coordinate;
               }
           }
}

Lo que provoca que automáticamente, gracias al bindeo la posición del mapa cambia a la dirección buscada.

Una vez allí, haciendo uso de la funcionalidad de longpress seleccionamos un punto sobre el mapa, que añadiéndolo a la colección Pins, mostrará un Pin sobre el mapa en el lugar seleccionado.

private async Task SelectPositionAsync(Position position)
{
           var pin = new TKCustomMapPin
           {
               Position = position,
               Title = string.Empty,
               ShowCallout = false
           };
           Pins.Clear();
           Pins.Add(pin);
}

En Resumen

El uso del plugin TK.CustomMap en unas pocas líneas de código nos permite construir un mapa completamente funcional con una barra de búsqueda de direcciones, reconocimiento del evento longpress y otras funcionalidades avanzadas que nos ayudarán a mejorar la UX de nuestras aplicaciones Xamarin multiplataforma.

Artículos Relacionados

Desarrollo multiplataforma con Xamarin

Desarrollo de aplicaciones híbridas con Ionic

Acerca de Daniel Hompanera

I'm SW Architect and Team Leader at Solid Gear. Passionated about technology and methodology, what actually makes me happy is to work with People day by day and everything that can make things get better. I don't have favourite platform or technology, although I've been working with .NET technologies for all these almost 10 years and I feel an special appeal to it. I've worked Agile, not so Agile and Fragile environments but if I could choose, of course I would choose Solid Gear's Agile Method with no doubts

Share on LinkedInTweet about this on TwitterShare on FacebookShare on Google+Buffer this page

Deja un comentario

By completing the form you agree to the Privacy Policy