Erik S
Erik S

Reputation: 1929

Detect horizontal panning in UITableView

I'm using a UIPanGestureRecognizer to recognize horizontal sliding in a UITableView (on a cell to be precise, though it is added to the table itself). However, this gesture recognizer obviously steals the touches from the table. I already got the pangesturerecognizer to recognize horizontal sliding and then snap to that; but if the user starts by sliding vertical, it should pass all events from that touch to the tableview.

One thing i have tried was disabling the recognizer, but then it wouldn't scroll untill the next touch event. So i'd need it to pass the event right away then.

Another thing i tried was making it scroll myself, but then you will miss the persistent speed after stopping the touch.

Heres some code:

//In the viewdidload method
UIPanGestureRecognizer *slideRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(sliding:)];
[myTable addGestureRecognizer:slideRecognizer];



-(void)sliding:(UIPanGestureRecognizer *)recognizer
{
    if (recognizer.state == UIGestureRecognizerStateBegan)
    {
    CGPoint translation = [recognizer translationInView:favoritesTable];
    if (sqrt(translation.x*translation.x)/sqrt(translation.y*translation.y)>1) {
        horizontalScrolling = YES; //BOOL declared in the header file
        NSLog(@"horizontal");
        //And some code to determine what cell is being scrolled:
        CGPoint slideLocation = [recognizer locationInView:myTable];
        slidingCell = [myTable indexPathForRowAtPoint:slideLocation];
        if (slidingCell.row == 0) {
            slidingCell = nil;
        }

    }
    else
    {
        NSLog(@"cancel");
    }

    if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled)
    {
    horizontalScrolling = NO;
    }


    if (horizontalScrolling)
    {
        //Perform some code
    }
    else
    {
    //Maybe pass the touch from here; It's panning vertically
    }

}

So, any advice on how to pass the touches?

Addition: I also thought to maybe subclass the tableview's gesture recognizer method, to first check if it's horizontal; However, then i would need the original code, i suppose... No idea if Apple will have problems with it. Also: I didn't subclass the UITableView(controller), just the cells. This code is in the viewcontroller which holds the table ;)

Upvotes: 32

Views: 15792

Answers (5)

Thane Brimhall
Thane Brimhall

Reputation: 9555

My answer is the same as Florian Mielke's, but I've simplified and corrected it some.

How to use:

Simply give your UIPanGestureRecognizer a delegate (UIGestureRecognizerDelegate). For example:

UIPanGestureRecognizer *panner = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panDetected:)];
panner.delegate = self;
[self addGestureRecognizer:panner];

Then have that delegate implement the following method:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    CGPoint translation = [(UIPanGestureRecognizer *)gestureRecognizer translationInView:gestureRecognizer.view.superview];
    return fabsf(translation.x) > fabsf(translation.y);
}

Upvotes: 7

Florian Mielke
Florian Mielke

Reputation: 3360

I had the same issue and came up with a solution that works with the UIPanGestureRecognizer.

In contrast to Erik I've added the UIPanGestureRecognizer to the cell directly, as I need just one particular cell at once to support the pan. But I guess this should work for Erik's case as well.

Here's the code.

- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{
    UIView *cell = [gestureRecognizer view];
    CGPoint translation = [gestureRecognizer translationInView:[cell superview]];

    // Check for horizontal gesture
    if (fabsf(translation.x) > fabsf(translation.y))
    {
        return YES;
    }

    return NO;
}

The calculation for the horizontal gesture is copied form Erik's code – I've tested this with iOS 4.3.

Edit: I've found out that this implementation prevents the "swipe-to-delete" gesture. To regain that behavior I've added check for the velocity of the gesture to the if-statement above.

if ([gestureRecognizer velocityInView:cell].x < 600 && sqrt(translate...

After playing a bit on my device I came up with a velocity of 500 to 600 which offers in my opinion the best user experience for the transition between the pan and the swipe-to-delete gesture.

Upvotes: 58

drewag
drewag

Reputation: 94733

You may try using the touch events manually instead of the gesture recognizers. Always passing the event back to the tableview except when you finally recognize the swipe gesture.

Every class that inherits from UIResponder will have the four touch functions (began, ended, canceled, and moved). So the simplest way to "forward" a call is to handle it in your class and then call it explicitly on the next object that you would want to handle it (but you should make sure to check if the object responds to the message first with respondsToSelector: since it is an optional function ). This way, you can detect whatever events you want and also allow the normal touch interaction with whatever other elements need it.

Upvotes: 1

Erik S
Erik S

Reputation: 1929

Thanks for the tips! I eventually went for a UITableView subclass, where i check if the movement is horizontal (in which case i use my custom behaviour), and else call [super touchesMoved: withEvent:];.

However, i still don't really get why this works. I checked, and super is a UITableView. It appears i still don't fully understand how this hierarchy works. Can someone try and explain?

Upvotes: 0

Kobski
Kobski

Reputation: 1636

Maybe you can use the UISwipeGestureRecognizer instead? You can tell it to ignore up/down swipes via the direction property.

Upvotes: 1

Related Questions