Trying to make UITableViewCell slideable AND tappable, while also keeping the UITableView scrollable, why is my cell sliding only working sometimes?

StackOverflow https://stackoverflow.com/questions/16266907

Question

I have a UITableView, where its cells are subclasses of UITableViewCell that have a top UIView and a bottom UIView that allow the top UIView to be moved to the right and left if the user drags his/her finger.

I got it working by adding a pan gesture recognizer to each UITableViewCell. Pretty easy stuff. But I only want horizontal pans, but it would detect vertical pans across the cell when it was intended to scroll up, which would cause the UITableView not to move.

I tried to make it so the gesture would only be detected if the user panned his/her finger horizontally more than 5px, but it doesn't seem to be working. The scroll works fine, and I can tap the cell, but I slide ten times across a cell and it works maybe once.

I can't figure out why.

Relevant code:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)recognizer {
    if ([recognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
        NSLog(@"hi");
        CGPoint translation = [(UIPanGestureRecognizer *)recognizer translationInView:self];

        if (translation.x > 5 || translation.x < -5) {
            return YES;
        }
        else {
            return NO;
        }
    }
    else {
        return YES;
    }
}

- (void)pannedCell:(UIPanGestureRecognizer *)recognizer {
    if (recognizer.state == UIGestureRecognizerStateBegan) {
        _firstTouchPoint = [recognizer translationInView:self];
    }
    else if (recognizer.state == UIGestureRecognizerStateChanged) {
        CGPoint touchPoint = [recognizer translationInView:self];

        // Holds the value of how far away from the first touch the finger has moved
        CGFloat xPos;

        // If the first touch point is left of the current point, it means the user is moving their finger right and the cell must move right
        if (_firstTouchPoint.x < touchPoint.x) {
            xPos = touchPoint.x - _firstTouchPoint.x;

            if (xPos <= 0) {
                xPos = 0;
            }
        }
        else {
            xPos = -(_firstTouchPoint.x - touchPoint.x);

            if (xPos >= 0) {
                xPos = 0;
            }
        }

        if (xPos > 10 || xPos < -10) {
            // Change our cellFront's origin to the xPos we defined
            CGRect frame = self.cellFront.frame;
            frame.origin = CGPointMake(xPos, 0);
            self.cellFront.frame = frame;
        }
    }
    else if (recognizer.state == UIGestureRecognizerStateEnded) {
        [self springBack];
    }
    else if (recognizer.state == UIGestureRecognizerStateCancelled) {
        [self springBack];
    }
}

What exactly should I be doing to make this implementation work better?

Was it helpful?

Solution

A nice way of doing this without messing about with only starting to pan when you've moved a certain value is to determine which direction the gesture recognizer has translated the most in and using that. It cuts down on the amount of code you need quite a bit and works fairly well in my testing.

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

    return YES;
}

And now your view sliding becomes simpler. This code allows you to drag in either direction.

- (void)drag:(UIPanGestureRecognizer *)sender {
    CGPoint translation = [sender translationInView:self.superview];
    CGRect frame = self.upperView.frame;

    switch (sender.state) {
        case UIGestureRecognizerStateChanged:
            frame.origin.x = translation.x;
            [self.upperView setFrame:frame];
            break;
        case UIGestureRecognizerStateBegan:
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateFailed:
        default:
            break;
    }
}

Additionally, you may want to allow your gesture recognizer to work along side others (as in, the recognizer that handles the scrolling of the table view) but the code in gestureRecognizerShouldBegin: seems to handle this without issue.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}

Also note that I'm using self.superview instead of just self as you have. This is because of this line in the translationInView: section of the docs:

The view in whose coordinate system the translation of the pan gesture should be computed. If you want to adjust a view's location to keep it under the user's finger, request the translation in that view's superview's coordinate system.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top