简体   繁体   中英

Maps in Xamarin Forms and MVVM architecture

How can I show maps in Xamarin Forms and MVVM architecture.How can I create a postion on a map in ModelView and show this in PageView?

View

<?xml version="1.0" encoding="UTF-8"?>
<d:ExtendedContentPage xmlns:d="clr-namespace:TestApp;assembly=TestApp"
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="TestApp.MapPageView">
    <ContentPage.Content>
 <StackLayout VerticalOptions="StartAndExpand" Padding="30">
        <maps:Map WidthRequest="320" HeightRequest="200"
            Text="{Binding MapModel.MyMap]"
            IsShowingUser="true"
            MapType="Hybrid"/>
  </StackLayout>
 </ContentPage.Content>
</d:ExtendedContentPage>

ModelView

public class MapViewModel: BaseViewModel
{
    private MapModel _mapModel = null;
    private MapModel MapModel
    {
        get
        {
            return _mapModel;
        }
        set
        {
            if (_mapModel != value)
            {
                _mapModel = value;
                OnPropertyChanged();
            }
        }
    }

    public MapViewModel(INavigation navigation) : base(navigation)
    {
        InitializeMapsPosition();
    }

    private  void InitializeMapsPosition()
    {

    }

    public override void RemoveHandlers()
    {
        throw new NotImplementedException();
    }
}

Model

public class MapModel
{
    public string MyMap { set; get; }

    public MapModel(String myMap) 
    {
        MyMap = myMap;
    }
}

In my scenario I needed to show position of some places and current location in map. I used Xamarin.Forms.Maps to show map and for locations in map : I added a CustomMap class and write two property injection to show pins in map.One property (CenterRegion) for showing current location and another one (CustomPins) for showing places location in map.

CustomMap Class

  public class CustomMap : Map
{
    private static IList<Pin> AllPins;

    public Location CenterRegion
    {
        get { return (Location)GetValue(CenterRegionProperty); }
        set { SetValue(CenterRegionProperty, value); }
    }


    public static readonly BindableProperty CenterRegionProperty =
       BindableProperty.Create(propertyName: nameof(CenterRegion), returnType: 
       typeof(Location), declaringType: typeof(CustomMap), defaultValue: null, 
       propertyChanged: (sender, oldValue, newValue) =>
       {
           CustomMap map = (CustomMap)sender;

           if (newValue is Location location)
           {
               map.MoveToRegion(MapSpan.FromCenterAndRadius(new 
               Position(location.Latitude, location.Longitude), 
               Distance.FromMiles(3)));
           }
       });


    public IEnumerable CustomPins
    {
        get { return (IEnumerable)GetValue(CustomPinsProperty); }
        set { SetValue(CustomPinsProperty, value); }
    }

    public static readonly BindableProperty CustomPinsProperty =
        BindableProperty.Create(propertyName: nameof(CustomPins), returnType: 
        typeof(IEnumerable), declaringType: typeof(CustomMap), defaultValue: null, 
        propertyChanged: (sender, oldValue, newValue) =>
        {
            CustomMap map = (CustomMap)sender;

            AllPins = new List<Pin>();

            map.Pins.Clear();

            if (newValue is IEnumerable spaces)
            {
                foreach (Pin pin in ConvertSpacesToPins(spaces))
                       map.Pins.Add(pin);
            }
            AllPins = map.Pins;
            map.OnPropertyChanged("Pins");
        });


    public static List<Pin> ConvertSpacesToPins(IEnumerable spaces)
    {
        if (spaces == null)
            return null;

        List<Pin> result = new List<Pin>();

        foreach (SpaceDto space in spaces.OfType<SpaceDto>())
        {
            double latitude = space.Latitude;
            double longitude = space.Longitude;
            string spaceTitle = space.Title;
            Position position = new Position(latitude, longitude);

            Pin pin = new Pin
            {
                Position = position,
                Label = spaceTitle
            };
                result.Add(pin);
        }
        return result;
    }
}

Xaml page

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:Map="clr-namespace:XamarinFormsPrismMapSample.Views"
                 x:Class="XamarinFormsPrismMapSample.Views.CustomMapSampleView">
        <Map:CustomMap
                    CenterRegion="{Binding CurrentLocation}"
                    CustomPins="{Binding Spaces}"
                    HorizontalOptions="FillAndExpand"
                    IsShowingUser="true"
                    MapType="Street"
                    VerticalOptions="FillAndExpand" />
    </ContentPage>

ViewModel:

public class CustomMapSampleViewModel : BindableBase
    {
        public Location CurrentLocation { get; set; }
        public SpaceDto Spaces { get; set; }

        public CustomMapSampleViewModel()
        {

        }

        public async Task OnNavigatedToAsync(NavigationParameters parameters)
        {
            Location CurrentLocation = await Geolocation.GetLastKnownLocationAsync();
            List<SpaceDto> Spaces = new List<SpaceDto> { 
            new SpaceDto { Latitude = 45.6892 , Longitude = 51.3890, Title = "X"  }, 
            new SpaceDto { Latitude = 45.6891, Longitude = 51.3890, Title = "Y" }, 
            new SpaceDto { Latitude = 45.6888, Longitude = 51.3890, Title = "Z" } };
        }
    }

    public class SpaceDto
    {
        public string Title { get; set; }
        public virtual double Latitude { get; set; }
        public virtual double Longitude { get; set; }
    }

Yes, Map.Pins is not bindable, but there is ItemsSource , which is easy to use instead.

   <maps:Map ItemsSource="{Binding Locations}">
        <maps:Map.ItemTemplate>
            <DataTemplate>
                <maps:Pin Position="{Binding Position}"
                          Label="{Binding Name}"
                          Address="{Binding Subtitle}" />

So, just for the pins, MVVM can be done without any custom control. But Map.MoveToRegion() (and Map.VisibleRegion to read) is still open. There should be a way to bind them. Why not both in a single read/write property? (Answer: because of an endless loop.)

Note: if you need Map.MoveToRegion only once on start, the region can be set in the constructor.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM