Search code examples
iosios7uipangesturerecognizeruiswipegesturerecognizer

Do I have to use a UIPanGestureRecognizer instead of a UISwipeGestureRecognizer if I want to track the movement?


I'm trying to implement a paging interaction for one of my views. I thought I would just use UISwipeGestureRecognizers. But through trial and error as well as examination of the documentation it appears that

A swipe is a discrete gesture, and thus the associated action message is sent only once per gesture.

So while I could trigger the page, I wouldn't be able to hook up animation that occurred during the drag.

Is my only alternative to use a UIPanGestureRecognizer and reimplement the basic filtering/calculations of the swipe?

Update/Redux

In hindsight, what I really should have been asking is how to implement a "flick" gesture. If you're not going to roll your own subclass (may bite that off in a bit), you use a UIPanGestureRecognizer as @Lyndsey 's answer indicates. What I was looking for after that (in the comments) was how to do the flick part, where the momentum of the flick contributes to the decision of whether to carry the motion of the flick through or snap back to the original presentation.

UIScrollView has behavior like that and it's tempting to mine its implementation for details on how one decelerates the momentum in a way that would be consistent, but alas the decelerationRate supplied for UIScrollView is "per iteration" value (according to some). I beat my head on how to properly apply the default value of 0.998 to the end velocity of my pan.

In the end, I used code pulled from sites about "flick" computation and did something like this in my gesture handler:

...
else if (pan.state == UIGestureRecognizerStateEnded) {
    CGFloat v = [pan velocityInView: self.view].x;
    CGFloat a = -4000.0; // 4 pixels/millisecond, recommended on gavedev
    CGFloat decelDisplacement = -(v * v) / (2 * a); // physics 101
    // how far have we come plus how far will momentum carry us?
    CGFloat totalDisplacement = ABS(translation) + decelDisplacement;
    // if that is (or will be) half way across our view, finish the transition
    if (totalDisplacement >= self.view.bounds.size.width / 2) {
        // how much time would we need to carry remainder across view with current velocity and existing displacement? (capped)
        CGFloat travelTime = MIN(0.4, (self.view.bounds.size.width - ABS(translation)) * 2 / ABS(v));
        [UIView animateWithDuration: travelTime delay: 0.0 options: UIViewAnimationOptionCurveEaseOut animations:^{
            // target/end animation positions
        } completion:^(BOOL finished) {
            if (finished) {
                // any final state change
            }
        }];
    }
    else { // put everything back the way it was
        ...
    }
}

Solution

  • Yes, use a UIPanGestureRecognizer if you want the specific speed, angle, changes, etc. of the "swipe" to trigger your animations. A UISwipeGestureRecognizer is indeed a single discrete gesture; similar to a UITapGestureRecognizer, it triggers a single action message upon recognition.

    As in physics, the UIPanGestureRecognizer's "velocity" will indicate both the speed and direction of the pan gesture. Here are the docs for velocityInView: method which will help you calculate the horizontal and vertical components of the changing pan gesture in points per second.