1
votes

Currently working on getting custom pins to work in maps on both Android and iOS, while it is currently working on Android I am experiencing a null exception in the code immediately below (full custom renderer further down) in my custom renderer on iOS and I am struggling to figure out what is causing it.

    CustomPin GetCustomPin(MKPointAnnotation annotation)
    {
        var position = new Position(annotation.Coordinate.Latitude, annotation.Coordinate.Longitude);
        foreach (var pin in customPins)
        {
            if (pin.Pin.Position == position)
            {
                return pin;
            }
        }
        return null;
    }

I have copied the code from the Xamarin sample provided at https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/map/customized-pin/ and the sample applies the custom pin correctly.

My custommaprenderer:

[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace WorkingWithMaps.iOS
{
    class CustomMapRenderer : MapRenderer
    {
        UIView customPinView;
        List<CustomPin> customPins;

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

            if (e.OldElement != null)
            {
                var nativeMap = Control as MKMapView;
                nativeMap.GetViewForAnnotation = null;
                nativeMap.CalloutAccessoryControlTapped -= OnCalloutAccessoryControlTapped;
                nativeMap.DidSelectAnnotationView -= OnDidSelectAnnotationView;
                nativeMap.DidDeselectAnnotationView -= OnDidDeselectAnnotationView;
            }

            if (e.NewElement != null)
            {
                var formsMap = (CustomMap)e.NewElement;
                var nativeMap = Control as MKMapView;
                customPins = formsMap.CustomPins;

                nativeMap.GetViewForAnnotation = GetViewForAnnotation;
                nativeMap.CalloutAccessoryControlTapped += OnCalloutAccessoryControlTapped;
                nativeMap.DidSelectAnnotationView += OnDidSelectAnnotationView;
                nativeMap.DidDeselectAnnotationView += OnDidDeselectAnnotationView;
            }
        }

        MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
        {
            MKAnnotationView annotationView = null;

            if (annotation is MKUserLocation)
                return null;

            var anno = annotation as MKPointAnnotation;
            var customPin = GetCustomPin(anno);
            if (customPin == null)
            {
                throw new Exception("Custom pin not found");
            }

            annotationView = mapView.DequeueReusableAnnotation(customPin.Id);
            if (annotationView == null)
            {
                annotationView = new CustomMKAnnotationView(annotation, customPin.Id);
                annotationView.Image = UIImage.FromFile("pin.png");
                annotationView.CalloutOffset = new CGPoint(0, 0);
                annotationView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile("monkey.png"));
                annotationView.RightCalloutAccessoryView = UIButton.FromType(UIButtonType.DetailDisclosure);
                ((CustomMKAnnotationView)annotationView).Id = customPin.Id;
                ((CustomMKAnnotationView)annotationView).Url = customPin.Url;
            }
            annotationView.CanShowCallout = true;

            return annotationView;
        }

        void OnCalloutAccessoryControlTapped(object sender, MKMapViewAccessoryTappedEventArgs e)
        {
            var customView = e.View as CustomMKAnnotationView;
            if (!string.IsNullOrWhiteSpace(customView.Url))
            {
                UIApplication.SharedApplication.OpenUrl(new Foundation.NSUrl(customView.Url));
            }
        }

        void OnDidSelectAnnotationView(object sender, MKAnnotationViewEventArgs e)
        {
            var customView = e.View as CustomMKAnnotationView;
            customPinView = new UIView();

            if (customView.Id == "Xamarin")
            {
                customPinView.Frame = new CGRect(0, 0, 200, 84);
                var image = new UIImageView(new CGRect(0, 0, 200, 84));
                image.Image = UIImage.FromFile("xamarin.png");
                customPinView.AddSubview(image);
                customPinView.Center = new CGPoint(0, -(e.View.Frame.Height + 75));
                e.View.AddSubview(customPinView);
            }
        }

        void OnDidDeselectAnnotationView(object sender, MKAnnotationViewEventArgs e)
        {
            if (!e.View.Selected)
            {
                customPinView.RemoveFromSuperview();
                customPinView.Dispose();
                customPinView = null;
            }
        }

        CustomPin GetCustomPin(MKPointAnnotation annotation)
        {
            var position = new Position(annotation.Coordinate.Latitude, annotation.Coordinate.Longitude);
            foreach (var pin in customPins)
            {
                if (pin.Pin.Position == position)
                {
                    return pin;
                }
            }
            return null;
        }
    }
}

Link to my repository: https://github.com/Mortp/CustomMapPinsXamarin

1

1 Answers

3
votes

Your custom pin is missing ID required here:

annotationView = mapView.DequeueReusableAnnotation(customPin.Id);

Need:

var pin = new CustomPin
            {
                Id="Xamarin",
                Pin = new Pin
                {
                    Type = PinType.Place,
                    Position = new Position(55.240121, 10.469895),
                    Label = "Testing Pins"
                }
            };

also instead of

 if (annotation is MKUserLocation)
                return null;

 var anno = annotation as MKPointAnnotation;

use

var anno = annotation as MKPointAnnotation;
if (anno == null)
     return null;

because annotation can be something else as you can see below.

Also, I found out that the code below (using MoveToRegion in this event handler) creates problems with this renderer because GetViewForAnnotation starts receiving parameters other than MKPointAnnotation but cast protection above saves it from crashing. Another problem with code below that if your pin location is far from your real location you will not see the custom pin and locator tracker will always put your map into your location. To debug your pin location in such case just comment out "Move" line as below.

maplocator.PositionChanged += (sender, e) =>
            {
                var position = e.Position;

                //map.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(position.Latitude, position.Longitude), Distance.FromKilometers(2)));
            };

The function OnReverseGeocodeButtonClicked will crash soon :-) because your class member pin is not assigned.

warning CS0649: Field 'MainPage.pin' is never assigned to, and will always have its default value null