5
votes

I have a UIScrollView that's around 600 pixels in height and 320 in width. So I'm allowing the user to scroll vertically.

I'm also trying to capture horizontal swipes on the view. The problem seems to be that when a user swipes horizontally with some accidental vertical movement, the UIScrollView scrolls and my touchesEnded delegate method never gets called.

Here's my code:

- (void)touchesEnded: (NSSet *)touches withEvent: (UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint currentPosition = [touch locationInView:self];

    if (currentPosition.x + 35 < gestureStartPoint.x)
    {  
        NSLog(@"Right");
    }    
    else if (currentPosition.x - 35 > gestureStartPoint.x)
    {
        NSLog(@"Left");
    }    
    else if (!self.dragging)
    {
        [self.nextResponder touchesEnded: touches withEvent:event]; 
    }

    [super touchesEnded: touches withEvent: event];
}

Does anyone know how I can get this to work even when there is vertical drag involved?

3

3 Answers

5
votes

UIScrollView tries to figure out which touches to pass through to its contents, and which are scrolls, based on movement immediately after a touch begins. Basically, if the touch appears to be a scroll right away, it handles that gesture and the contents never see it; otherwise, the gesture gets passed through (with a very short delay for the first touch).

In my experience, I've been able to capture horizontal swipes in the contents of a UIScrollView that handled vertical-only scrolling -- it basically just worked by default for me. I did this by setting the contentSize to be the same as the width of the scroll view's frame, which is enough information to tell the UIScrollView that it won't be handling horizontal scrolling.

It sounds like you're having trouble with the default behavior, though. One hardware gotcha is that it's very hard to simulate a finger swipe on a laptop's trackpad. If I were you, I would test out the default UIScrollView setup using either a mouse or, preferably, on the device itself. I found that these input methods work much better for conveying swipes.

If that doesn't work, here is a very pertinent paragraph from Apple's UIScrollView docs:

Because a scroll view has no scroll bars, it must know whether a touch signals an intent to scroll versus an intent to track a subview in the content. To make this determination, it temporarily intercepts a touch-down event by starting a timer and, before the timer fires, seeing if the touching finger makes any movement. If the time fires without a significant change in position, the scroll view sends tracking events to the touched subview of the content view. If the user then drags their finger far enough before the timer elapses, the scroll view cancels any tracking in the subview and performs the scrolling itself. Subclasses can override the touchesShouldBegin:withEvent:inContentView:, pagingEnabled, and touchesShouldCancelInContentView: methods (which are called by the scroll view) to affect how the scroll view handles scrolling gestures.

In summary, you could try what they suggest by subclassing UIScrollView and overriding the suggested methods.

1
votes

If @Tyler's method doesn't work, try putting a view right over the UIScrollView, and handle any horizontal swipes in that view's touchesBegan, and pass vertical ones to the next responder. You can be a little fuzzy and handle anything that has more of a horizontal movement than vertical as a horizontal swipe, and pass the more pure vertical swipes to the UISCrollView (via nextResponder).

0
votes

Swift 4 solution

Register two gesture recognizers in viewDidLoad() in your view controller

let leftGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:)))
leftGesture.direction = .left
leftGesture.delegate = self
view.addGestureRecognizer(leftGesture)

let rightGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:)))
rightGesture.direction = .right
rightGesture.delegate = self
view.addGestureRecognizer(rightGesture)

Implement swipe action

@objc func handleSwipe(_ sender: UISwipeGestureRecognizer) {
    // do swipe left/right based on sender.direction == .left / .right
}

In order to have simultaneous horizontal swipe and vertical scroll, let your view controller implement UIGestureRecognizerDelegate

class MyViewController: UIViewController, UIGestureRecognizerDelegate

...

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                       shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

The last step fixes the problem that when you move more than +/- 5 pixels on the y-axis the Scroll view takes over and starts scrolling vertically.