locked
iOS-7 - Major trouble with anonymous methods RRS feed

  • Question

  • User22080 posted

    This is with iOS-7.

    Device updated over the air with the final iOS 7, not the GM. My app, built with the iOS-6 Xamarin stack works nicely in 7. Updated XCode through AppStore to version 5 Updated Xamarin using the stable channel. Recompiled, rebuilt everything targetting iOS-7. => Major trouble with gestures...

    The "return true" below never gets called.

    this.LeftSwipe = new UISwipeGestureRecognizer() { Direction = UISwipeGestureRecognizerDirection.Left , Enabled = false }; this.LeftSwipe.AddTarget(() => { this.HandleLeftSwipe(this.LeftSwipe); }); this.LeftSwipe.ShouldRecognizeSimultaneously = delegate { return true; }; this.view.AddGestureRecognizer(this.LeftSwipe);

    This sometimes works, not always, not for all GR.

    this.RightSwipe = new UISwipeGestureRecognizer() { Direction = UISwipeGestureRecognizerDirection.Right , Enabled = false }; this.RightSwipe.AddTarget(() => { this.HandleRightSwipe(this.RightSwipe); } ); this.RightSwipe.ShouldRecognizeSimultaneously = this.AlwaysTrueGestureProbe ; this.view.AddGestureRecognizer(this.RightSwipe);

        private bool AlwaysTrueGestureProbe ( UIGestureRecognizer r1 , UIGestureRecognizer r2 )
        { 
            return true; 
        } 
    

    All this stuff used to work perfectly. And the old iOS-6 app still works perfectly in 7.

    The latest update has broken something with anon's, delegates.. ??? Any idea?

    Thursday, September 19, 2013 5:27 PM

All replies

  • User13824 posted

    This might be a new bug. If you could file a bug that includes a zipped up example project, plus the steps to reproduce the problem, that would be great!

    Thursday, September 19, 2013 8:45 PM
  • User13613 posted

    I wouldn't say it is specific to Anonymous delegates. I have it through out my code, including the swipe gesture and it is working as expected.

    Thursday, September 19, 2013 8:51 PM
  • User10647 posted

    Changing the order in which I add gesture reconizers and adding Pinch.RequireGestureRecognizerToFail(Pan) helped me. Try it out.

    Thursday, September 19, 2013 10:42 PM
  • User22080 posted

    @APR: Could you please provide that order ? Thank you!

    I am going to try the Fail thing right now...

    Thursday, September 19, 2013 10:48 PM
  • User181 posted

    Why is it a problem if that method never gets called? That method would only get called if there were actually two gesture recognizers that wanted to recognize at the same time. It won't get called if the other gesture recognizer failed or is still in the "possible" state. I can't imagine that a right and left swipe would both succeed with the same touch simultaneously.

    Are there other gestures in the system that you haven't shown?

    Thursday, September 19, 2013 10:58 PM
  • User22080 posted

    RequireGestureRecognizerToFail did not help...

    Thursday, September 19, 2013 10:59 PM
  • User22080 posted

    @Adam: yes, my view has - Tap - Double Tap - Pan/drag - Pinch - LongPress

    Thursday, September 19, 2013 11:01 PM
  • User181 posted

    How are those other gestures configured?

    Thursday, September 19, 2013 11:15 PM
  • User22080 posted

    Here is below the code that create all the gesture recognizers. Everything is created Disabled. The recognizers are Enabled later 'on demand', when needed.

    Again (For Xamarin guys): All that was working perfectly in iOS-6 and started to break with the latest update. Also: my last iOS6 build is working just fine in 7.

    private void SetupGestures() { if (this.gestureParameters.CreateRightSwipe) { // create the right swipe gesture recognizer // wire up the event handler and add the gesture recognizer to the view this.RightSwipe = new UISwipeGestureRecognizer() { Direction = UISwipeGestureRecognizerDirection.Right , Enabled = false }; this.RightSwipe.AddTarget( this.HandleRightSwipe ); this.RightSwipe.ShouldRecognizeSimultaneously = this.AlwaysTrueGestureProbe ; this.view.AddGestureRecognizer(this.RightSwipe);
    }

            if (this.gestureParameters.CreateLeftSwipe)
            {
                // same for left 
                this.LeftSwipe = new UISwipeGestureRecognizer() { Direction = UISwipeGestureRecognizerDirection.Left  , Enabled = false }; 
                this.LeftSwipe.AddTarget( this.HandleLeftSwipe );
                this.LeftSwipe.ShouldRecognizeSimultaneously = this.AlwaysTrueGestureProbe ; 
                this.view.AddGestureRecognizer(this.LeftSwipe);                         
            } 
    
            if (this.gestureParameters.CreateUpSwipe)
            {
                // same for up  
                this.UpSwipe = new UISwipeGestureRecognizer() 
                { 
                    Direction = UISwipeGestureRecognizerDirection.Up , 
                    NumberOfTouchesRequired = 1 , 
                    Enabled = false , 
                };
                this.UpSwipe.AddTarget( this.HandleUpSwipe );
                this.UpSwipe.ShouldRecognizeSimultaneously = this.AlwaysTrueGestureProbe ; 
                this.view.AddGestureRecognizer(this.DownSwipe);                 
            } 
    
            if (this.gestureParameters.CreateDownSwipe)
            {
                // same for down 
                this.DownSwipe = new UISwipeGestureRecognizer() 
                { 
                    Direction = UISwipeGestureRecognizerDirection.Down , 
                    NumberOfTouchesRequired = 1 , 
                    Enabled = false ,
                };
                this.DownSwipe.AddTarget( HandleDownSwipe );
                this.DownSwipe.ShouldRecognizeSimultaneously = this.AlwaysTrueGestureProbe ; 
                this.view.AddGestureRecognizer(this.DownSwipe);                 
            } 
    
            if (this.gestureParameters.CreateRotation)
            {
                // Same for rotate
                this.Rotation = new UIRotationGestureRecognizer() { Enabled = false };
                this.Rotation.AddTarget( this.HandleRotateGesture ); 
                this.view.AddGestureRecognizer(this.Rotation); 
                this.Rotation.ShouldRecognizeSimultaneously = this.AlwaysTrueGestureProbe ; 
            } 
    
            if (this.gestureParameters.CreateLongPress)
            {
                // Same for Long Press
                this.LongPress = new UILongPressGestureRecognizer() { Enabled = false }; 
                this.LongPress.AddTarget( this.HandleLongPressGesture ); 
                this.view.AddGestureRecognizer(this.LongPress); 
                this.LongPress.Enabled = true; 
                this.LongPress.MinimumPressDuration = 0.700; 
                this.LongPress.NumberOfTapsRequired = 0; 
                this.LongPress.NumberOfTouchesRequired = 1; 
                this.LongPress.ShouldRecognizeSimultaneously = this.AlwaysTrueGestureProbe ; 
            }           
    
            if (this.gestureParameters.CreateTouchDown)
            {
                // Same for touch down == Long Press with zero delay 
                this.TouchDown = new UILongPressGestureRecognizer() { Enabled = false }; 
                this.TouchDown.AddTarget( this.HandleTouchDownGesture ); 
                this.view.AddGestureRecognizer(this.TouchDown); 
                this.TouchDown.Enabled = true; 
                this.TouchDown.MinimumPressDuration = 0.010; 
                this.TouchDown.NumberOfTapsRequired = 0; 
                this.TouchDown.NumberOfTouchesRequired = 1; 
                this.TouchDown.ShouldRecognizeSimultaneously = this.AlwaysTrueGestureProbe ; 
            }   
    
            if (this.gestureParameters.CreateTap)
            {
                this.Tap = new UITapGestureRecognizer() { Enabled = false }; 
                this.Tap.AddTarget( this.HandleTapGesture ); 
                this.view.AddGestureRecognizer(this.Tap); 
                this.Tap.NumberOfTouchesRequired = 1; 
                this.Tap.ShouldRecognizeSimultaneously = this.AlwaysTrueGestureProbe ; 
            }
    
            if (this.gestureParameters.CreateDoubleTap)
            {
                this.DoubleTap = new UITapGestureRecognizer() { Enabled = false }; 
                this.DoubleTap.AddTarget( this.HandleDoubleTapGesture ); 
                this.view.AddGestureRecognizer(this.DoubleTap); 
                this.DoubleTap.NumberOfTouchesRequired = 1; 
                this.DoubleTap.NumberOfTapsRequired = 2; 
                this.DoubleTap.ShouldRecognizeSimultaneously = this.AlwaysTrueGestureProbe ; 
            }
            if (this.gestureParameters.CreatePan)
            {
                this.Pan = new UIPanGestureRecognizer() { Enabled = false }; 
                this.Pan.AddTarget( this.HandlePanGesture ); 
                this.view.AddGestureRecognizer(this.Pan); 
                this.Pan.MinimumNumberOfTouches = 1; 
                this.Pan.MaximumNumberOfTouches = 2; 
                this.Pan.ShouldRecognizeSimultaneously = this.AlwaysTrueGestureProbe ; 
            }
    
            if (this.gestureParameters.CreatePinch)
            {
                // Same for Pinch 
                this.Pinch = new UIPinchGestureRecognizer() { Enabled = false };
                this.Pinch.AddTarget( this.HandlePinchGesture ); 
                this.view.AddGestureRecognizer(this.Pinch); 
                this.Pinch.ShouldRecognizeSimultaneously = this.AlwaysTrueGestureProbe ; 
                this.Pinch.RequireGestureRecognizerToFail ( this.Pan ); 
            } 
        }
    
        private bool AlwaysTrueGestureProbe ( UIGestureRecognizer r1 , UIGestureRecognizer r2 )
        { 
            Debug.WriteLine("Gesture Probe: " + r1.GetType().Name + " " + r2.GetType().Name );
            return true; 
        } 
    

    Thursday, September 19, 2013 11:28 PM
  • User181 posted

    Which of these are enabled at the time that it doesn't work the way you want? Is there another gesture that is actually being recognized? Which one? Are the gestures you want to recognize actually enabled before your finger touches?

    In short, what is actually happening versus what do you want to happen? All you've told us is that this one method isn't being called, but that's not helpful because that method is not guaranteed to be called except in a very specific circumstance. It's not obvious to me that it should be called from the information you've given me.

    Also, have you tried building with the older Xamarin tools but with the iOS 7 SDK? It might be a change in behavior in iOS.

    Thursday, September 19, 2013 11:51 PM
  • User22080 posted

    When an enabled recognizer starts processing touches events, it invokes ShouldRecognizeSimultaneously. In my particular case, most active recognizers do not print the "gesture probe" debug line. Which one? It changes at each app start, almost ramdomly: Sometimes, drag works, sometimes pinch works, sometimes tap works, and all others do not.

    I have not tried Xamarin 6 with Xcode 5-iOS 7. I am not sure this is actually possible.

    Friday, September 20, 2013 12:40 AM
  • User10647 posted

    I'd suggest you try false as ShouldRecognizeSimultaneously result. Then add your recognizers one by one and see which requires RequireGestureRecognizerToFail to work properly. I think swipes, rotation and pinch need pan to fail first. Tap requires double tap to fail (this one even on iOS 6). And I doubt you really want to recognize them all simultaneously.

    I have a feeling it is SDK 7 change - looks like more gesture recognizers constraints are required. SDK 7 build works flawlessly on iOS 6, but same build fails to perform properly on iOS 7, which is also curious.

    Here's what I have so far, it works in both OS:

    pinch = new UIPinchGestureRecognizer();
                pinch.AddTarget(() => {
                    HandlePinch();
                });
                pan = new UIPanGestureRecognizer();
                pan.AddTarget(() => {
                    HandlePan();
                });
                pan.MaximumNumberOfTouches = 1;
                pan.MinimumNumberOfTouches = 1;
                Container.AddGestureRecognizer(pan);
                Container.AddGestureRecognizer(pinch);
    
                tap = new UITapGestureRecognizer();
                tap.AddTarget(() => {
                    HandleTap();
                });
                tap.NumberOfTapsRequired = 1;
                Container.AddGestureRecognizer(tap);
    
                tap2 = new UITapGestureRecognizer();
                tap2.AddTarget(() => {
                    HandleTap2();
                });
                tap2.NumberOfTapsRequired = 2;
                tap2.CancelsTouchesInView = true;
                Container.AddGestureRecognizer(tap2);
    
                tap.RequireGestureRecognizerToFail(tap2);
    
                if (Application.IsIOS7())
                    pinch.RequireGestureRecognizerToFail(pan);
    
                pan.ShouldRecognizeSimultaneously += (UIGestureRecognizer r, UIGestureRecognizer other) => {
                    return false;
                };
                pinch.ShouldRecognizeSimultaneously += (UIGestureRecognizer r, UIGestureRecognizer other) => {
                    return false;
                };
                tap.ShouldRecognizeSimultaneously += (UIGestureRecognizer r, UIGestureRecognizer other) => {
                    return false;
                };
                tap2.ShouldRecognizeSimultaneously += (UIGestureRecognizer r, UIGestureRecognizer other) => {
                    return false;
                };
    

    Note the if (Application.IsIOS7()) part - this is what I had to add to make it all work properly. Without that I was getting random pan only/pinch only/pan+pinch working.

    Friday, September 20, 2013 7:04 AM
  • User10647 posted

    P.S. SDK 6 build with newest Xamarin for iOS 7 is also indeed possible. Download previous Xcode (4.6.2?) and point Xamarin to it's SDK.

    Though I don't think it's a good way out - you'll have to fix it at some point anyway.

    Friday, September 20, 2013 7:12 AM
  • User181 posted

    "When an enabled recognizer starts processing touches events, it invokes ShouldRecognizeSimultaneously."

    That is not true. ShouldRecognizeSimultaneously is only called when two gesture recognizers both enter the recognized state, not when they first start processing touches. If that method isn't being called then something else is preventing that gesture from being recognized.

    Friday, September 20, 2013 2:28 PM
  • User22080 posted

    Okay... I did the following:

    • Reverted to Xamarin iOS 6
    • Reverted to Xcode 4
    • Restored iPad #1 to iOS 6
    • Build and run the current code
    • Everything works in 6.
    • Pushed the IPA to TestFlight
    • Installed from TF on iPad #2 that runs iOS 7 installed OTA
    • Everything works in 7: Pinch and Pan at the same time, very nice...

    So where is the bug??? Xamarin? SDK 7 ? Both ???

    I think I will wait until 7.2 before I try another 'upgrade'..

    Friday, September 20, 2013 9:38 PM
  • User13824 posted

    Just a small update in case anyone else is running into this problem: I've noticed that using the Delegate property instead of the ShouldRecognizeSimulataneously property works around the issue in a simple test case (2 swipe gesture recognizers).

    So for example, for LeftSwipe instead of:

    this.LeftSwipe.ShouldRecognizeSimultaneously = delegate { return true; };

    You could try:

    this.LeftSwipeDelegate = new SwipeDelegate (); this.LeftSwipe.Delegate = LeftSwipeDelegate;

    Where LeftSwipeDelegate is a new instance variable, and SwipeDelegate looks something like this:

    public class SwipeDelegate : UIGestureRecognizerDelegate { public override bool ShouldRecognizeSimultaneously (UIGestureRecognizer gestureRecognizer, UIGestureRecognizer otherGestureRecognizer) { return true; } }

    Saturday, September 21, 2013 3:36 AM
  • User22080 posted

    @APR: Thanks for posting your code. Much appreciated! It seems like what you are doing is disabling the ability to have two or more GR's working together, which is not what I need actually. I have a game map and I want the experience as the built-in Maps app: Scale, Pan and Rotate at the same time. On a side note: Recognize Simultanously == false is the default so you can remove a bit of redundant code...

    @Brendan. Thanks for looking into this. - Best way to test that is to use Pinch and Pan. These two should work together simultanously.

    • Since the new instance of the Swipe delegate is prefixed by 'this', I assume it is persisted within a class instance, then possibly preventing the GC to collect it. I strongly suspect that some weak references are 'vanishing' in this case. This is just speculation: I dont have the source code... Please keep us posted if you make any progress. Thank you.
    Saturday, September 21, 2013 7:08 PM
  • User13824 posted

    Thanks for the Pinch and Pan suggestion! I updated my example project, and filed a bug report: https://bugzilla.xamarin.com/show_bug.cgi?id=14937

    In case it's helpful while waiting on a bug fix, I found that the example worked correctly in Xcode 5 with Xamarin.iOS 6.4.5.0. So the problem seems to be due to a change in Xamarin.iOS 7.

    Monday, September 23, 2013 7:43 PM
  • User22080 posted

    Also: This sample http://oleb.net/blog/2012/01/gesture-recognition-on-ios-with-attention-to-detail/ should be ported ok in Xamarin.

    Okay, Bug confirmed... But when do we get a fix?

    Also important: Are there other similar delegate issues in other areas? ( This pattern is everywhere in UIKit) Or this is only a problem for Gesture Recognizers?

    Monday, September 23, 2013 7:53 PM
  • User249 posted

    This is so frustrating. :-(

    The time Xamarin saves with stuff like async, it takes easily away with bugs like these getting into stable releases. (Happens pretty much all the time!)

    I was hoping to solve this problem by downgrading to 6.4.5, but guess what, my old binding code now throws InvalidCastExceptions every single time. Why? No idea.

    Thursday, September 26, 2013 5:47 PM
  • User249 posted

    So we worked around it by creating a delegate type:

    static class UIHelper {
        public class ShouldRecognizeDelegate : UIGestureRecognizerDelegate
        {
            public bool ShouldRecognize { get; set; }
            public UIGestureProbe ShouldBeginFunc { get; set; }
    
            public override bool ShouldRecognizeSimultaneously (UIGestureRecognizer gestureRecognizer, UIGestureRecognizer otherGestureRecognizer)
            {
                return ShouldRecognize;
            }
    
            public override bool ShouldBegin (UIGestureRecognizer recognizer)
            {
                if (ShouldBeginFunc == null)
                    return true;
    
                return ShouldBeginFunc (recognizer);
            }
        }
    
        public static ShouldRecognizeDelegate ShouldRecognizeTrueDelegate = new ShouldRecognizeDelegate () {
            ShouldRecognize = true
        };
    
        public static ShouldRecognizeDelegate ShouldRecognizeFalseDelegate = new ShouldRecognizeDelegate () {
            ShouldRecognize = false
        };
    }
    

    Then we went through all our code to replace intilization of recognizers with initialization of this delegate.

    This seems to work well.

    Thursday, September 26, 2013 8:28 PM
  • User39 posted

    This bug has been fixed (I'm not sure exactly which version will have the fix though at this point).

    There is however an easy workaround: add event handlers for the ShouldRequireFailureOf and ShouldBeRequiredToFailBy events, returning false:

    recognizer.ShouldRequireFailureOf = (a, b) => { return false; };
    recognizer.ShouldBeRequiredToFailBy = (a, b) => { return false; };
    
    Thursday, September 26, 2013 10:17 PM
  • User22080 posted

    @Rolf: Thanks for the good news!

    Any comment on this still unanswered previous question:

    Are there other similar delegate issues in other areas? ( This pattern is everywhere in UIKit) Or this is only a problem for Gesture Recognizers?

    Thursday, September 26, 2013 10:31 PM
  • User39 posted

    @TheDriver: this was a bug with UIGestureRecognizer, it is not a more general problem (when we bind a protocol with methods that return a value we must also state what should be returned from the binding if there is no corresponding event handler for those methods - in this case we got that default value wrong for the two new iOS7 methods (ShouldRequireFailureOf and ShouldBeRequiredToFailBy)).

    Monday, September 30, 2013 10:44 PM