Search code examples
ioscocoa-touchuigesturerecognizeruipageviewcontroller

How to tell whether a UIPageViewController page turn is due to a swipe/pan or a tap


When using a UIPageViewController, the user can swipe right or left to create an animated page turn that follows their finger. Or the user can simply tap on one side of the page view or the other to cause a page turn. When the page turn animation is done, the delegate method pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted: method is called.

I would like my final page state to be slightly different, depending on whether the user turned the page with a swipe or a tap. But I can't figure out a robust way to determine what caused the page turn. The pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted: method doesn't provide that information.

In my UIPageViewController delegate (self), the gestureRecognizer:shouldReceiveTouch: method is called, but it is called for both a swipe (pan) and a tap gesture recognizer even though the user is ultimately employing only one or the other gesture. The delegate gesture method is called the moment the screen is touched for either gesture. So that's no help.

I added the set of touch-related intercepting methods to the page view. For touchesBegan:withEvent: I reset a flag. For touches:moved:, which is not called for the tap gesture, I set the flag. This sort of works, but it's too low-level and might not work correctly for some other kind of gesture in the future that also moves, but isn't a swipe/pan. And the UIPageViewController's tap gesture seems to gobble up the touch event completely, so not even touchesBegan:withEvent: is called (which is weird).

Is there any way to distinguish robustly, after the fact, which style of page turn (tap or pan/swipe) a UIPageViewController has used to handle the user's interaction to turn a page?


Solution

  • The question is really how to determine which of several gesture recognizers is responsible for something happening.

    The key thing to understand is that any gesture recognizer can have more than one target/action installed. The UIPageViewController sets its own target/actions internally, but that doesn't preclude others being added.

    When setting up a sub-class of UIPageViewController after it has been loaded, or instantiated, add the following code (or Swift code equivalent):

    for (UIGestureRecognizer *gr in self.gestureRecognizers) {
        if ([gr class] == [UIPanGestureRecognizer class])
            [gr addTarget:self action:@selector(panGestureOccurred)];
         else if ([gr class] == [UITapGestureRecognizer class])
            [gr addTarget:self action:@selector(tapGestureOccurred)];
        }
    

    Then, add the two target/action methods to the sub-class:

    - (void)tapGestureOccurred
    {
        // Set a flag here to rely on later after the page turn
    }
    
    - (void)panGestureOccurred
    {
        // Reset a flag here to rely on later after the page turn
    }
    

    This obviously generalizes for any other types of gestures, but currently only the pan and tap gestures appear to be supported by a UIPageViewController.

    panGestureOccurred will be called multiple times while the user is touching the screen and moving a finger/stylus. The tapGestureOccurred is only called once.

    But only one of the two methods will be called, depending on which gesture recognizer the page view controller in its infinite wisdom decides wins. This all seems to work much more robustly than the too-low level touchesBegan and touchesMoved idea posited in the original question.