1

I have a problem where I'm using Xamarin.Forms.Maps and everything works well on the iOS simulator, I open the map, from there I navigate to the Custom pins and all of them are displayed with no issue, however the problem happens when I use a physical device ( iOS 14.6 and wasn't working on the last update neither ).

When I launch the app everything works the same however if I try to go to the nearest pin the app throws an exception System.NullReferenceException: "Object reference not set to the instance of an object", that's only when I try to display multiple pins on the map.

I have a method in my app that redirects me to a pin if clicked on it and that works, it sends me to the pin but if I zoom out the app breaks again. When I debug and set a breakpoint at the method that breaks it is the GetCustomPin method in the GetViewForAnnotation that throws the exception and the problem is that it doesn't pass the argument to the GetCustomPin method if it's for multiple pins but it does if it's for one ( I don't know if it has to do with the fact that it might be working on a background thread when it's for multiple pins ) but it works for Android so it's not a problem in the shared project code.

Heres the code:

[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace App.iOS
{
public 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.OverrideUserInterfaceStyle = UIUserInterfaceStyle.Dark;
            //nativeMap.WeakDelegate = this;

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

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

        if (annotation is MKUserLocation)
            return null;

        var customPin = GetCustomPin(annotation as MKPointAnnotation);
        if (customPin == null)
        {
            return null;
        }

        annotationView = mapView.DequeueReusableAnnotation(customPin.Name);
        if (annotationView == null)
        {
            ...
            annotationView.CalloutOffset = new CGPoint(0, 0);
            annotationView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile("icon.png"));
            annotationView.RightCalloutAccessoryView = new UIImageView(UIImage.FromFile("icon.png"));
            ((CustomMKAnnotationView)annotationView).Name = customPin.Name;
            ((CustomMKAnnotationView)annotationView).Url = customPin.Url;
            annotationView.CanShowCallout = true;
        }
        else
        {
            annotationView.Annotation = annotation;
        }

        configureDetailView(annotationView);

        return annotationView;
    }

    void configureDetailView(MKAnnotationView annotationView)
    {
        int width = 350;
        int height = 250;

        var snapshotView = new UIView();
        snapshotView.TranslatesAutoresizingMaskIntoConstraints = false;
        NSDictionary views = NSDictionary.FromObjectAndKey(snapshotView, new NSString("snapshotView"));
        snapshotView.AddConstraints(NSLayoutConstraint.FromVisualFormat("H:[snapshotView(250)]", new NSLayoutFormatOptions(), null, views));
        snapshotView.AddConstraints(NSLayoutConstraint.FromVisualFormat("V:[snapshotView(80)]", new NSLayoutFormatOptions(), null, views));

        var options = new MKMapSnapshotOptions();
        options.Size = new CGSize(width, height);
        options.MapType = MKMapType.SatelliteFlyover;
        options.Camera = MKMapCamera.CameraLookingAtCenterCoordinate(annotationView.Annotation.Coordinate, 250, 65, 0);

        var snapshotter = new MKMapSnapshotter(options);
        snapshotter.Start((snapshot, error) =>
        {
            if (snapshot != null)
            {
                UILabel label = new UILabel();
                UILabel label2 = new UILabel();
                UILabel label3 = new UILabel();
                UILabel label4 = new UILabel();
                //UIButton uIButton = new UIButton(UIButtonType.System);
                //uIButton.SetTitle("Save", UIControlState.Normal);

                //uIButton.Frame = new CGRect(200, 10, 150, 15);
                label.Frame = new CGRect(15, 0, 150, 20);
                label2.Frame = new CGRect(15, 20, 210, 20);
                label3.Frame = new CGRect(15, 40, 210, 20);
                label4.Frame = new CGRect(15, 60, 210, 20);
                // Add your custom controls here
                snapshotView.AddSubviews(label, label2, label3, label4);
            }
        });

        annotationView.DetailCalloutAccessoryView = snapshotView;
    }

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

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

        customPinView.Frame = new CGRect(0, 0, 200, 84);
        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.Position == position)
            {
                return pin;
            }
        }
        return null;
    }
}

}

I have no idea how to solve this, thank you for your help.

Jimmy
  • 105
  • 15
  • so multiple pins works in the sim but not on the device? – Jason Jul 08 '21 at 22:21
  • Exactly @Jason, that’s the problem – Jimmy Jul 08 '21 at 22:36
  • to start with I'd suggest adding some basic error/exception handling. Like a null check in GetCustomPin – Jason Jul 08 '21 at 22:52
  • Alright, will do, but how come the same exact thing works in the simulator but not on the device ? – Jimmy Jul 08 '21 at 23:13
  • Does this problem occur on all devices? – Wen xu Li Jul 09 '21 at 05:48
  • @llill It occurs on iPhone 8 max since it's the only iOS device I have but it is the latest update of iOS – Jimmy Jul 09 '21 at 06:04
  • Would you mind sharing us a basic , reproducible project to test ? – Wen xu Li Jul 09 '21 at 09:45
  • Unfortunately I would like to but this is company code and I can't share a project, the thing is that it might work on a new project but on this one it's weird, I can't find answers online because it seems that no one is having the same problem. It navigates from one pin to the other but it doesn't want to show both as the GetCustomPin just doesn't get any parameters being passed to it when you try to show all pins at once but only on an iOS device which is even weirder because it works on the simulator and on Android, could it be the iPhone ? Thank you for helping – Jimmy Jul 09 '21 at 20:11
  • Visual Studio cannot use the ios14.6 simulator (https://developer.apple.com/forums/thread/683114), what version of the simulator are you using? – Wen xu Li Jul 12 '21 at 09:38
  • @llill 14.5 but I meant 14.6 for the physical device and since it's only happening on the physical device I was thinking maybe it has something to do with the device instead of the code – Jimmy Jul 12 '21 at 20:03
  • You can use another physical machine to test the code and see if it works. – Wen xu Li Jul 13 '21 at 09:41

0 Answers0