Google Maps iOS Components (v1.5.0) contains bug at Async interface of TileLayer

Hello.

I try to use Google Maps iOS Components (v1.5.0), and make custom tile layer as below.

public class MyTileLayer : TileLayer
{
    public MyTileLayer () : base()
    {
    }

    public override void RequestTile (uint x, uint y, uint zoom, TileReceiver receiver)
    {
        byte[] image;

        ... code to get tile image ...

        receiver.RecieveTile(x, y, zoom, new UIImage(NSData.FromArray(image)));
    }
}

But this code raises error below.

2013-10-15 17:34:48.755 MyApp[4891:4403] Unhandled managed exception: Cannot create an instance of Google.Maps.TileReceiver because it is an abstract class (System.MemberAccessException)
  at System.Reflection.MonoCMethod.DoInvoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00095] in /Developer/MonoTouch/Source/mono/mcs/class/corlib/System.Reflection/MonoMethod.cs:528 
  at System.Reflection.MonoCMethod.Invoke (BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in /Developer/MonoTouch/Source/mono/mcs/class/corlib/System.Reflection/MonoMethod.cs:556 
  at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters) [0x00000] in /Developer/MonoTouch/Source/mono/mcs/class/corlib/System.Reflection/ConstructorInfo.cs:62 
  at MonoTouch.ObjCRuntime.Runtime.ConstructNSObject[NSObject] (IntPtr ptr, System.Type type, MissingCtorResolution missingCtorResolution) [0x00000] in <filename unknown>:0 
  at MonoTouch.ObjCRuntime.Runtime.GetNSObject (IntPtr ptr, System.Type target_type, MissingCtorResolution missingCtorResolution) [0x0006d] in /Developer/MonoTouch/Source/monotouch/src/ObjCRuntime/Runtime.cs:499 
  at MonoTouch.ObjCRuntime.Runtime.GetNSObjectWrapped (IntPtr ptr, IntPtr type_ptr) [0x0000c] in /Developer/MonoTouch/Source/monotouch/src/ObjCRuntime/Runtime.cs:671 
  at (wrapper native-to-managed) MonoTouch.ObjCRuntime.Runtime:GetNSObjectWrapped (intptr,intptr)

Error says "Cannot create an instance of Google.Maps.TileReceiver because it is an abstract class ", but I never make instance of Google.Maps.TileReceiver.
So I think this is a bug of failing create TileReceiver object from Java's object which given as argument of callback method.

If this interface never works, we can't make asynchronous tile layers or cannot hook tile image from tile layer.
Please check this.

Posts

  • NARAMAPNARAMAP JPMember
    edited October 2013

    What I want to do is like below, decoration pattern of tile layers:

    public class MyTileLayer : TileLayer
    {
        protected UrlTileLayer originalLayer;
    
        public MyTileLayer ()
        {
            originalLayer = UrlTileLayer.FromUrlConstructor (
                (uint x, uint y, uint zoom) => {
                    string url = ... logic for getting tile url ...;
                    return url;
                }
            ); 
        }
    
        public override void RequestTile 
            (uint x, uint y, uint zoom, TileReceiver receiver)
        {
            var image = ... Logic for getting cached tile image ...;
            if (image != null) {
                receiver.RecieveTile(x, y, zoom, new UIImage(NSData.FromArray(image)));
                return;
            }
    
            var wrappedReceiver = new MyTileReceiver (receiver);
            originalLayer.RequestTile (x, y, zoom, wrappedReceiver);
        }
    }
    
    public class MyTileReceiver : TileReceiver
    {
        private TileReceiver originalReceiver;
    
        public MyTileReceiver (TileReceiver original) : base()
        {
            this.originalReceiver = original;
        }
    
        public override void RecieveTile (uint x, uint y, uint zoom, UIImage image) //<= maybe this is typo of ReceiveTile 
        {
            originalReceiver.RecieveTile (x, y, zoom, image);
    
            ... logic for caching tile image ...;
        }
    }
    

    But this codes raise the error "Unhandled managed exception: Cannot create an instance of Google.Maps.TileReceiver because it is an abstract class (System.MemberAccessException)".

    I have few knowledge about bindings, but for trial, I changed https://github.com/mono/monotouch-bindings/blob/master/GoogleMaps/binding/ApiDefinition.cs as below:

    Original:

      [BaseType (typeof (NSObject), Name="GMSTileReceiver")]
      [Model]
      [Protocol]
      interface TileReceiver {
    
          [Abstract]
          [Export ("receiveTileWithX:y:zoom:image:")]
          void RecieveTile (uint x, uint y, uint zoom, UIImage image);
      }
    

    Edited:

      [BaseType (typeof (NSObject), Name="GMSTileReceiver")]
      [Protocol]
      interface TileReceiver {
    
          [Export ("receiveTileWithX:y:zoom:image:")]
          void RecieveTile (uint x, uint y, uint zoom, UIImage image);
      }
    

    Then "Cannot create an instance ... abstract class" error is not shown, but new error occurs:

    2013-10-16 00:27:51.212 MyApp[2133:4907] *** NSForwarding: warning: object 0x1f078e50 of class 'MyApp.MyTileReceiver' does not implement methodSignatureForSelector: -- trouble ahead

    2013-10-16 00:27:51.241 MyApp[2133:4907] *** NSForwarding: warning: object 0x1f078e50 of class 'MyApp.MyTileReceiver' does not implement doesNotRecognizeSelector: -- abort

    I'll happy if this report helps debugging.

  • NARAMAPNARAMAP JPMember

    I fixed this problem myself.

    Change ApiDefinition.cs as below:

    [BaseType (typeof (NSObject), Name="GMSTileReceiver")]
    [Model]
    [Protocol]
    interface TileReceiver {
    
        [Abstract]
        [Export ("receiveTileWithX:y:zoom:image:")]
        void ReceiveTile (uint x, uint y, uint zoom, [NullAllowed] UIImage image);
    }
    
    interface ITileReceiver {}
    
    [BaseType (typeof (NSObject), Name="GMSTileLayer")]
    interface TileLayer {
    
        [Export ("requestTileForX:y:zoom:receiver:")]
        void RequestTile (uint x, uint y, uint zoom, ITileReceiver receiver);
    
        [Export ("clearTileCache")]
        void ClearTileCache ();
    
        ...
    

    and my app's code:

    public class MyTileLayer : TileLayer
    {
        protected UrlTileLayer originalLayer;
    
        public MyTileLayer ()
        {
            originalLayer = UrlTileLayer.FromUrlConstructor (
                (uint x, uint y, uint zoom) => {
                    string url = ... logic for getting tile url ...;
                    return url;
                }
            ); 
        }
    
        public override void RequestTile 
            (uint x, uint y, uint zoom, ITileReceiver receiver)
        {
            var image = ... Logic for getting cached tile image ...;
            if (image != null) {
                receiver.ReceiveTile(x, y, zoom, new UIImage(NSData.FromArray(image)));
                return;
            }
    
            var wrappedReceiver = new MyTileReceiver (receiver);
            originalLayer.RequestTile (x, y, zoom, wrappedReceiver);
        }
    }
    
    public class MyTileReceiver : NSObject, ITileReceiver
    {
        private ITileReceiver originalReceiver;
    
        public MyTileReceiver (ITileReceiver original) : base()
        {
            this.originalReceiver = original;
        }
    
        public void ReceiveTile (uint x, uint y, uint zoom, UIImage image)
        {
            originalReceiver.ReceiveTile (x, y, zoom, image);
    
            ... logic for caching tile image ...;
        }
    }
    

    Then everything works fine.

  • AlexSotoAlexSoto MXXamarin Team Xamurai

    Will be updating the component shortly thanks for the pull request and sorting this out :)

  • SteveResnickSteveResnick USMember ✭✭

    Looks promising, but how do I associate the TileLayer with the MapView? I'm using a custom renderer to render the map.

    In my custom Android renderer, I do mapView.Map.AddTileOverlay(myTileProviderOptions);

    Presumably, I need to the something similar in my iOS renderer, by telling the mapView to use the TileLayer

  • SteveResnickSteveResnick USMember ✭✭

    In case someone stumbles across this... the solution is simple. Rather than telling the MapView to use the TileLayer, you tell the TileLayer to use the MapView. Code is SyncTileLayer.Map = mapView;

  • Mike.1034Mike.1034 USMember

    @dalexsoto , have @NARAMAP 's changes been implemented yet?

Sign In or Register to comment.