32

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 ;)

Daniyar
  • 2,975
  • 2
  • 26
  • 39
Erik S
  • 1,939
  • 1
  • 18
  • 44
  • is there a reason you aren't doing this on the individual cells? – drewag Mar 24 '11 at 22:59
  • Centralized code, and the methods being called are in the tableviewcontroller anyway. But even if they were on the cells themselves the recognizers would steal the touches from the tableview. – Erik S Mar 24 '11 at 23:25
  • You could just as easily "Centralize" the code within a subclass of UITableViewCell. Keeping the code in the tableview isn't really a proper http://en.wikipedia.org/wiki/Separation_of_concerns . Of course that isn't what the questions is about :) – drewag Mar 24 '11 at 23:35
  • 1
    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 – drewag Mar 24 '11 at 23:37
  • Hm, think i mistyped it. It's actually not in the tableview itself, but in the viewcontroller that holds the tableviewcontroller :X no tableviewcontroller subclass, just subclasses of cells. @drewag: i could try that, will need to find out how though... never used anything other than gesturerecognizers – Erik S Mar 24 '11 at 23:53
  • Do you also happen to know how to pass on the touches to the viewcontroller? I can write the code to check the touches myself, just need to know where to pass it. Thanks! – Erik S Mar 25 '11 at 00:19
  • 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 ) – drewag Mar 25 '11 at 02:22
  • Check my own answer, that one did it. I came to it thanks to your tips :) If you'll send them in as a normal answer, i'll accept it so you will have your well-earned reputation ;) – Erik S Mar 25 '11 at 06:46

5 Answers5

58

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.

ivanzoid
  • 5,952
  • 2
  • 34
  • 43
Florian Mielke
  • 3,310
  • 27
  • 31
  • So what this does, is pass the gesture on to its parent view (in this case, the tableview) if it's not a horizontal gesture? – Erik S Aug 19 '11 at 10:58
  • 2
    Actually does not pass the gesture to the parent view. It sets the UIGestureRecognizer's state to UIGestureRecognizerStateFailed. As a result, the parent view is then able to handle the gesture. That's what I think how it works, but I don't know if this is technically correct. – Florian Mielke Aug 19 '11 at 11:27
  • As long as it's practically correct and it works, it's fine for me. I believe i've eventually implemented something similar, but thanks a bunch for the answer! – Erik S Aug 19 '11 at 11:32
  • 5
    You might be able to simplify `sqrt(translation.x * translation.x)` ;) – Olie Feb 06 '12 at 05:43
  • This was a timesaver. Thanks! –  Jan 27 '13 at 12:44
  • 1
    Note: just in case it wasn't immediately obvious, `gestureRecognizerShouldBegin` is a delegate method for `UIGestureRecognizerDelegate`. From the docs - Return Value: `YES` (the default) to tell the gesture recognizer to proceed with interpreting touches, `NO` to prevent it from attempting to recognize its gesture. – Gavin Hope Apr 08 '14 at 07:23
  • The edit part is incase of using swipe gesture right? I have only pan gesture to show the delete button. I don't need to add the edited part right? – hasan Nov 05 '14 at 16:04
7

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);
}
Thane Brimhall
  • 9,256
  • 7
  • 36
  • 50
1

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

Kobski
  • 1,636
  • 15
  • 27
  • This wont work, as the user has to be able to slowly slide the cell. I have considered using the swipe recognizer too indeed. Unfortunately the pangesturerecognizer does not have a direction method :/ – Erik S Mar 25 '11 at 05:56
1

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.

drewag
  • 93,393
  • 28
  • 139
  • 128
0

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?

Erik S
  • 1,939
  • 1
  • 18
  • 44
  • 1
    So the UITableView has logic in it to detect touches in order to implement scrolling. As the subclass, you are overriding this function, so if you never called the super version of the function you would not be able to scroll. You are essentially doing your own algorithm and then also allowing the default algorithm to run. This is very similar to how you call [super init] in initializers. You call that so that you don't have to re-impliment EVERYTHING that the superclass does, only the things you are introducing in your new subclass. – drewag Mar 25 '11 at 12:54
  • That part sounds logic to me; However, what is it actually calling this on? What exactly is the subclasses super? – Erik S Mar 25 '11 at 15:16