ios - Xamarin Forms Maps Circle 自定义渲染器

标签 ios iphone xamarin xamarin.forms

在 iOS 上使用自定义 map /渲染器时遇到两个问题。

演示视频:https://ufile.io/pscn3

我有一个自定义 map 类,它带有一个放置在 map 上的圆圈。 slider 控件根据可绑定(bind)属性调整圆圈的大小。

  1. 当 slider 值发生变化时,圆的半径属性会更新为所选值。但如您所见,它并没有更新 map 上的半径,而是将圆移动到曲线内的新位置。

  2. 当圆移出 x 像素时,它会在可见边界外消失或被截断。

这些是正在使用的类:

页面.xaml:

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:CompanyName.Data.ViewModels.MapWithCircleSlider;assembly=CompanyName"
    xmlns:local="clr-namespace:CompanyName.UI;assembly=CompanyName"
    x:Class="CompanyName.UI.Pages.MapWithCircleSlider"
    Title="{Binding Title}">

    <ContentPage.BindingContext>
    <vm:MapWithCircleSliderViewModel></vm:MapWithCircleSliderViewModel>
    </ContentPage.BindingContext>

    <ContentPage.Content>
        <ScrollView>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <local:CircleMap Grid.Row="0" CircleRadius="{Binding CircleRadius}" Latitude="{Binding Latitude}" Longitude="{Binding Longitude}" MapRadius="{Binding MapRadius}" IsShowingUser="true" HasZoomEnabled="true" />
                <!--<Image Grid.Row="0" HorizontalOptions="Center" VerticalOptions="Center" Source="ic_place_green_48dp.png" />-->
                <StackLayout Grid.Row="1" Padding="32,16">
                    <Entry VerticalOptions="Start" Placeholder="navn *" Text="{Binding Name}">
                        <Entry.Style>
                            <OnPlatform x:TypeArguments="Style">
                                <On Platform="iOS" Value="{x:Static local:Styling.IosEntryStyle}" />
                            </OnPlatform>
                        </Entry.Style>
                    </Entry>
                    <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding CircleRadius, StringFormat='{0}m'}" />
                    <Slider VerticalOptions="End" Maximum="{Binding Maximum}" Minimum="{Binding Minimum}" Value="{Binding CircleRadius}" />
                    <!-- NB: Maximum must be set before Minimum, ref: https://bugzilla.xamarin.com/show_bug.cgi?id=23665 -->
                </StackLayout>
            </Grid>
        </ScrollView>
    </ContentPage.Content>

</ContentPage>

页面 View 模型:

using System;
using CompanyName.ViewModels;

namespace CompanyName.Data.ViewModels.MapWithCircleSlider
{
    public class MapWithCircleSliderViewModel : ViewModelBase
    {

        private string name;
        private int circleRadius;
        private float latitude;
        private float longitude;
        private int mapRadius;

        public MapWithCircleSliderViewModel()
        {
            Name = "Labs";
            CircleRadius = 200;
            MapRadius = 200;
            Latitude = 58.9698634f;
            Longitude = 5.7331874f;
        }

        public int Maximum => 1000;
        public int Minimum => 100;
        public string Id { get; set; }
        public bool IsEditMode { get; set; }
        public string Title { get; set; }

        public string Name
        {
            get => name;
            set
            {
                if (name == value) return;

                name = value;
                OnPropertyChanged("Name");
            }
        }

        public int CircleRadius
        {
            get => circleRadius;
            set
            {
                if (circleRadius == value) return;

                circleRadius = value;
                OnPropertyChanged("CircleRadius");
            }
        }

        public float Latitude
        {
            get => latitude;
            set
            {
                if (Math.Abs(latitude - value) < float.Epsilon) return;

                latitude = value;
                OnPropertyChanged("Latitude");
            }
        }

        public float Longitude
        {
            get => longitude;
            set
            {
                if (Math.Abs(longitude - value) < float.Epsilon) return;

                longitude = value;
                OnPropertyChanged("Longitude");
            }
        }

        public int MapRadius
        {
            get => mapRadius;
            set
            {
                if (mapRadius == value) return;

                mapRadius = value;
                OnPropertyChanged("MapRadius");
            }
        }
    }
}

圆图.cs

using System.Diagnostics;
using Xamarin.Forms;
using Xamarin.Forms.Maps;

namespace CompanyName.UI
{
    public class CircleMap : Map
    {
        private const int DefaultCircleRadius = 100;
        private const float DefaultLatitude = 58.8523208f;
        private const float DefaultLongitude = 5.7326743f;
        private const int DefaultMapRadius = 150;

        public static readonly BindableProperty CircleRadiusProperty = BindableProperty.Create("CircleRadius", typeof(int), typeof(CircleMap), DefaultCircleRadius, BindingMode.TwoWay, propertyChanged: OnCircleRadiusPropertyChanged);
        public static readonly BindableProperty LatitudeProperty = BindableProperty.Create("Latitude", typeof(float), typeof(CircleMap), DefaultLatitude, BindingMode.TwoWay, propertyChanged: OnLatitudePropertyChanged);
        public static readonly BindableProperty LongitudeProperty = BindableProperty.Create("Longitude", typeof(float), typeof(CircleMap), DefaultLongitude, BindingMode.TwoWay, propertyChanged: OnLongitudePropertyChanged);
        public static readonly BindableProperty MapRadiusProperty = BindableProperty.Create("MapRadius", typeof(int), typeof(CircleMap), DefaultMapRadius, BindingMode.TwoWay, propertyChanged: OnMapRadiusPropertyChanged);

        public CircleMap() : base(MapSpan.FromCenterAndRadius(new Position(DefaultLatitude, DefaultLongitude), Distance.FromMeters(DefaultMapRadius))) { }

        public int CircleRadius
        {
            get => (int)GetValue(CircleRadiusProperty);
            set => SetValue(CircleRadiusProperty, value);
        }

        public float Latitude
        {
            get => (float)GetValue(LatitudeProperty);
            set => SetValue(LatitudeProperty, value);
        }

        public float Longitude
        {
            get => (float)GetValue(LongitudeProperty);
            set => SetValue(LongitudeProperty, value);
        }

        public int MapRadius
        {
            get => (int)GetValue(MapRadiusProperty);
            set => SetValue(MapRadiusProperty, value);
        }

        private static void OnCircleRadiusPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var circleMap = (CircleMap)bindable;
            circleMap.CircleRadius = (int)newValue;
        }

        private static void OnLatitudePropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var circleMap = (CircleMap)bindable;
            circleMap.Latitude = (float)newValue;

            MoveToRegion(circleMap);
        }

        private static void OnLongitudePropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var circleMap = (CircleMap)bindable;
            circleMap.Longitude = (float)newValue;

            MoveToRegion(circleMap);
        }

        private static void OnMapRadiusPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var circleMap = (CircleMap)bindable;
            circleMap.MapRadius = (int)newValue;

            MoveToRegion(circleMap);
        }

        private static void MoveToRegion(CircleMap circleMap)
        {
            circleMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(circleMap.Latitude, circleMap.Longitude), Distance.FromMeters(circleMap.MapRadius)));
        }
    }
}

CustomMapRenderer.cs(iOS):

using CompanyName.UI;
using MapKit;
using ObjCRuntime;
using System;
using System.ComponentModel;
using System.Linq;
using Xamarin.Forms;
using Xamarin.Forms.Maps.iOS;
using Xamarin.Forms.Platform.iOS;
using CompanyName.iOS.Renderers.CustomRenderer;
using CompanyName.Utilities;

[assembly: ExportRenderer(typeof(CircleMap), typeof(CustomMapRenderer))]
namespace CompanyName.iOS.Renderers.CustomRenderer
{
    /// <remarks>
    /// https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/map/circle-map-overlay/#Creating_the_Custom_Renderer_on_iOS
    /// </remarks>
    public class CustomMapRenderer : MapRenderer
    {
        private CircleMap circleMap;
        private MKCircleRenderer circleRenderer;
        private MKMapView NativeMap => Control as MKMapView;

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

                if (e.OldElement != null)
                {
                    if (Control is MKMapView nativeMap)
                    {
                        nativeMap.RemoveOverlays(nativeMap.Overlays);
                        nativeMap.OverlayRenderer = null;
                        circleRenderer = null;
                    }
                }

                if (e.NewElement != null)
                {
                    circleMap = (CircleMap)e.NewElement;
                    NativeMap.OverlayRenderer = GetOverlayRenderer;

                    AddOverlay();
                }
            }
            catch (Exception ex)
            {
                //Logger.LogException(ex, GetType().Name);
            }
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (sender == null) return;
            circleMap = (CircleMap)sender;

            if (e.PropertyName == "VisibleRegion") OnVisibleRegionChanged();
            if (e.PropertyName == CircleMap.CircleRadiusProperty.PropertyName) RedrawOverlay();
        }

        private MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlayWrapper)
        {
            if (circleRenderer == null && !Equals(overlayWrapper, null))
            {
                var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as IMKOverlay;
                circleRenderer = new MKCircleRenderer(overlay as MKCircle)
                {
                    Alpha = 0.15f,
                    FillColor = CompanyName.Constants.Colors.Skobeloff500.ToUIColor(),
                    LineWidth = 1,
                    StrokeColor = CompanyName.Constants.Colors.Skobeloff500.ToUIColor()
                };
            }
            return circleRenderer;
        }

        private void OnVisibleRegionChanged()
        {
            SetNewCoordinates();
            RedrawOverlay();
        }

        private void SetNewCoordinates()
        {
            circleMap.Latitude = (float)circleMap.VisibleRegion.Center.Latitude;
            circleMap.Longitude = (float)circleMap.VisibleRegion.Center.Longitude;
            circleMap.MapRadius = (int)circleMap.VisibleRegion.Radius.Meters;
        }

        private void RedrawOverlay()
        {
            RemoveOverlays();
            AddOverlay();
        }

        private void RemoveOverlays()
        {
            if (NativeMap?.Overlays == null) return;
            if (NativeMap.Overlays.Any()) NativeMap.RemoveOverlays(NativeMap.Overlays);
        }

        private void AddOverlay()
        {
            var circleOverlay = MKCircle.Circle(new CoreLocation.CLLocationCoordinate2D(circleMap.Latitude, circleMap.Longitude), circleMap.CircleRadius);
            NativeMap.AddOverlay(circleOverlay);
        }
    }
}

非常感谢任何反馈/建议!

最佳答案

您可以尝试刷新 circleRenderer 来实现您的效果,例如:

private void RemoveOverlays()
{
    if (NativeMap?.Overlays == null) return;
    if (NativeMap.Overlays.Any())
    {
        NativeMap.RemoveOverlays(NativeMap.Overlays);

        circleRenderer = null;
        NativeMap.OverlayRenderer = GetOverlayRenderer;
    }
}

关于ios - Xamarin Forms Maps Circle 自定义渲染器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48868681/

相关文章:

ios - map 在 Xamarin iOS 上的方向变化时失去位置

php - 如何在 android/ios 应用程序中配置 facebook 注册功能

ios - 如何使用 AFNetworking 对 api 调用进行单元测试

ios - 针对 ios6.1 编译的 Xcode 5 不保留布局

JavaScript:input.focus() 并显示 Cursor

iphone - 更改一个单元格的内容也会更改 UITableView 中的其他单元格

iphone - UrbanAirShip 中的角标(Badge)编号未更新

android - 从 DialogFragment (Xamarin) 更新数据库中的记录时更新 ListView 和绘图

iphone - iPhone 中的 cookie 和 session

xamarin - SonarQube 扫描仪适用于 macOS 上的 MSBuild,使用 mono