Xamarin - iOS Multiple polygons on map

2019-07-18 19:14发布

问题:

I am currently following this tutorial for adding a polygon to a map. I need to be able to add multiple polygons to my map, so I have slightly altered the code so that I can use addOverlays which takes in an array of IMKOverlay objects instead of one addOverlay which just takes in a single IMKOverlay object.

This doesn't work however... It only draws the first polygon on the map!

void addPolygonsToMap()
        {
            overlayList = new List<IMKOverlay>();
            for (int i = 0; i < polygons.Count; i++)
            {

                    CLLocationCoordinate2D[] coords = new CLLocationCoordinate2D[polygons[i].Count];

                    int index=0;
                    foreach (var position in polygons[i])
                    {
                        coords[index] = new CLLocationCoordinate2D(position.Latitude, position.Longitude);
                        index++;
                    }
                    var blockOverlay = MKPolygon.FromCoordinates(coords);
                    overlayList.Add(blockOverlay);

            }
            IMKOverlay[] imko = overlayList.ToArray();
            nativeMap.AddOverlays(imko);

        }

In this discussion, it would appear that I have to call a new instance of MKPolygonRenderer each time I need to add another polygon to my map, but I'm unsure how this example translates to my code. Here is my MKPolygonRenderer function:

MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlayWrapper)
  {
      if (polygonRenderer == null && !Equals(overlayWrapper, null)) {
          var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as IMKOverlay;
          polygonRenderer = new MKPolygonRenderer(overlay as MKPolygon) {
              FillColor = UIColor.Red,
              StrokeColor = UIColor.Blue,
              Alpha = 0.4f,
              LineWidth = 9
          };
      }
      return polygonRenderer;
  }

回答1:

Create a new renderer instance each time OverlayRenderer is called, there is no need to cache the renderer in a class level variable as the MKMapView will cache the renderers as needed.

Subclass MKMapViewDelegate:

class MyMapDelegate : MKMapViewDelegate
{
    public override MKOverlayRenderer OverlayRenderer(MKMapView mapView, IMKOverlay overlay)
    {
        switch (overlay)
        {
            case MKPolygon polygon:
                var prenderer = new MKPolygonRenderer(polygon)
                {
                    FillColor = UIColor.Red,
                    StrokeColor = UIColor.Blue,
                    Alpha = 0.4f,
                    LineWidth = 9
                };
                return prenderer;
            default:
                throw new Exception($"Not supported: {overlay.GetType()}");
        }
    }
}

Instance and assign the delegate to your map:

mapDelegate = new MyMapDelegate();
map.Delegate = mapDelegate;

Note: Store the instance of your MyMapDelegate in a class level variable as you do not want to get GC'd

Update:

MKMapView has two steps involved to display an overlay on its map.

1. Calling `AddOverlay` and `AddOverlays`

First you add overlays to the map that conform to IMKOverlay. There are basic built-in types such as MKCircle, MKPolygon, etc... but you can also design your own overlays; i.e. overlays that define the location of severe weather (lightning, storm clouds, tornados, etc..). These MKOverlays describe the geo-location of the item but not how to draw it.

2. Responding to `OverlayRenderer` requests

When the display area of the map intersects with one of the overlays, the map need to draw it on the screen. The map's delegate (your MKMapViewDelegate subclass) is called to supply a MKOverlayRenderer that defines the drawing routines to paint the overlay on the map.

This drawing involves converting the geo-coordinates of the overlay to local display coordinates (helper methods are available) using Core Graphics routines (UIKit can be used with some limitations). There are basic built-in renderers for MKCircleRenderer, MKPolygonRenderer, etc.. that can be used or you can write your own MKOverlayRenderer subclass.

You could supply a custom way to renderer a MKCircle overlay, maybe a target-style red/white multi-ringed bullseye, instead of the way the default circle renderer draws it, or custom renderers that draw severe storm symbols within the bounds of a MKPolygon to match your custom severe storm overlays.

My Example code:

Since you are using MKPolygon to build your overlays, you can use the MKPolygonRenderer to display them. In my example, I provide a pattern matching switch (C# 6) that returns a semi-transparent Red/Blue MKPolygonRenderer for every MKPolygon that you added to the map (if you added a non-MKPolygon based overlay it will throw an exception).