Avoid event propagation to scrollview

EuricomEuricom USMember

I have a child control (Google Mapview) embedded in another control that is embedded in a UIScrollView.

The problem is that when I swipe on the child control (to navigate the map), the UISCrollView also reacts to the swipe event.

Is there a way to prevent event propagation from a child control to its parent control?


  • JohnMillerJohnMiller USForum Administrator, Xamarin Team Xamurai

    Do you want the scroll view to be able to scroll horizontally/vertically in another situation?

  • EuricomEuricom USMember

    Yes ...

    Before this I had a UIWebview control (showing a HTML based online map) inside my scroll view.

    When I pinched, swiped, ... on the web view, these events were not propagated to the scroll view. I also need this behavior with the MapView control.

  • JohnMillerJohnMiller USForum Administrator, Xamarin Team Xamurai

    I have not done something like this, but I am wondering if you could override the TouchesBegan and TouchesEnded methods of your child view. In the TouchesBegan you could disable whatever you want in the parent view e.g. Scrolling, UserInteraction, etc. In the TouchesEnded, undo whatever you did in the TouchesBegan.

  • EuricomEuricom USMember

    Thanks for your reply, @John. It is not possible to use a subclass of the MapView class because MapView is instantiated using a factory method (var mapView = MapView.FromCamera(...)).

  • JohnMillerJohnMiller USForum Administrator, Xamarin Team Xamurai
    edited April 2013

    @EuriMobile Oh I see. I looked at the sample code form the component store to see what you were talking about.

    There is an ExclusiveTouch property in a MapView that says it "Restricts the event delivery to this view". Have you played with that at all?


    var map = MapView.FromCamera(...);
    map.ExclusiveTouch = true;
  • adamkempadamkemp USInsider, Developer Group Leader mod
    edited April 2013

    This can be done in at least two ways depending on which OS you need to support. We needed to do it for a similar situation.

    For iOS versions before 6 it's a little trickier. This is what we did. The trick is when your nested control is touched you want to cancel the gesture in the scroll view. You can cancel an active gesture by setting its Enabled property to false and then back to true (this is documented behavior).

    What we did is create a very simple custom gesture recognizer which recognized as soon as any touch began. It was set up such that it didn't interfere with any other gestures or touch events by making its ShouldRecognizeSimultaneously function (actually an action in a property) always return true, its DelaysTouches(Began/Ended) properties were set to false, and its CancelsTouchesInView property was set to false.

    Then we made an extension method which applied that gesture recognizer to any view with pan or pinch gestures if that view might be nested in a scroll view. When the gesture is recognized it would go up the view hierarchy (all the way up to the window) looking for any scroll view, and for each of those scroll views if the pan or pinch gestures are enabled then disable and reenable them.

    When you put all of that together you will ensure that as soon as the nested view is touched then that touch will be immediately discarded by any ancestor scroll views.

    For iOS 6 or later there is an easier way. There is a new method in UIView called GestureRecognizerShouldBegin. It is called on the hit test view (and maybe ancestor views?) when a gesture is about to enter the recognized state, and this method gives that view a chance to veto the gesture. That means you can implement that method in your custom view and check if the gesture is attached to a scroll view and is a gesture which would conflict with yours (i.e., is it a pan gesture or a pinch gesture?). If so then return false.

    If you are using a built in control like a UISwitch or a UISlider then you may need to subclass them. I'm not sure whether they implemented that method in their own controls or not. You should test it on iOS 6 first.

  • EuricomEuricom USMember

    Hi @Adamkemp. Thanks for your suggestions. It tried implementing GestureRecognizerShouldBegin in the view that contains my map view, but the method is never called.

    I cannot override the method in a class derived from MapView because the MapView control is always instantiated by a factory method.

  • EuricomEuricom USMember

    I have to correct myself: it's not necessary to use a factory method to instantiate the MapView, it can also be done by simply calling new MapView().

    I created a derived class MyMapView in which I overrode the method GestureRecognizerShouldBegin. However, the method is never called. :-(

    public class MyMapView : MapView
        public override bool GestureRecognizerShouldBegin(UIGestureRecognizer gestureRecognizer)
            if (gestureRecognizer.View is UIScrollView && gestureRecognizer is UISwipeGestureRecognizer) return false;
            return base.GestureRecognizerShouldBegin(gestureRecognizer);
  • EuricomEuricom USMember
    edited April 2013
  • EuricomEuricom USMember

    Converted to C# code, the solution is as follows:

    mapView.AddGestureRecognizer(new UIPanGestureRecognizer { CancelsTouchesInView = false });

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Which OS were you testing on for the GestureRecognizerShouldBegin approach? That only works in iOS 6, I believe. I've never actually used that method because we needed iOS 5 support at the time so maybe there are additional things you have to do.

    The downside to the approach you took is that it doesn't necessarily disable other gestures (namely, a pinch gesture). Maybe your ancestor scroll view doesn't support zooming so that's ok, but in our case we supported both pan and zoom in our parent scroll view so we needed something that covered all of those cases.

  • DonalFarrellDonalFarrell USMember, University

    For iOS6+:
    To get the GestureRecognizerShouldBegin(...) method to trigger, you need to add:

    this.UserInteractionEnabled = true;

    in the constructor of your custom renderer. It should be hit unless your touch events are being swallowed by another control!

Sign In or Register to comment.