0

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
        ...
    }
}
Travis Griggs
  • 21,522
  • 19
  • 91
  • 167
  • 1
    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`. – Lyndsey Scott Nov 21 '14 at 21:34
  • Sigh. Is there any documentation on what one does to properly emulate the directional filtering and velocity? – Travis Griggs Nov 21 '14 at 22:31
  • FYI, I posted links to the documentation in my answer below. – Lyndsey Scott Nov 24 '14 at 16:27

1 Answers1

1

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.

Lyndsey Scott
  • 37,080
  • 10
  • 92
  • 128
  • I'd like to figure out where my swipe would end up at. At time of the `Ended` event, I have the current displacement, as well as the velocity. But I don't know what to use as a deceleration factor to compute "how far" that end velocity will carry me. Does Apple publish anything for what they use for these computations? – Travis Griggs Nov 24 '14 at 18:21
  • @TravisGriggs What do you mean by "will carry me"? Do you just want to know where the user's finger ended up? If so, there's no need to do any computation. Just use `locationInView:`: https://developer.apple.com/library/ios/documentation/UIKit/reference/UIGestureRecognizer_Class/index.html#//apple_ref/occ/instm/UIGestureRecognizer/locationInView: – Lyndsey Scott Nov 24 '14 at 18:26
  • No, it's a little more involved than just `locationInView:`. That just tells me where the swipe (pan) ended. Given I'm using the swipe to "page" a view, I need to figure out how far the swipe on the view would have been to decide if it's enough to advance to next page. If the user flicks the view hard, there's not just the travel of the finger, but how much farther the view will travel given the velocity of the swipe at lift-off time. You see this with Apple's scroll views. I was hoping for some hints how they do that. – Travis Griggs Nov 25 '14 at 23:44
  • Oh, you're swiping on a UIScrollView? Just check its content offset after it's done scrolling to find out its final position. – Lyndsey Scott Nov 25 '14 at 23:48
  • No, I'm not swiping on a `UIScrollView`. But I **am** trying to get something that approximates a `UIScrollView` swipe with a normal `UIView` and a `UIPanGestureRecognizer`. – Travis Griggs Nov 25 '14 at 23:56