Question

I have a custom UITableViewCell which has a pan gesture recognizer on:

// CustomCell.m

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        // Initialization code

        // Add a pan recognizer
        UIGestureRecognizer* recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
        recognizer.delegate = self;
        [self addGestureRecognizer:recognizer];
    }
    return self;
}

I'm using the pan gesture to detect if the user is swiping the cell in any horizontal way and then the cell does some different things depending on how much the cell has been swiped. That aspect of the cell behavior works as I wan't it to.

But I just discovered that if I press the cell and don't swipe in any direction the app crashes due to a call to UILongPressGestureRecognizer, but I don't even have a UILongPressGestureRecognizer in my app at all. Why does the UILongPressGestureRecognizer method get called on my UIPanGestureRecognizer?

Here is my methods for my Pan gesture handling:

#pragma mark - Horizontal pan gesture methods

- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
    CGPoint translation = [gestureRecognizer translationInView:[self superview]];
    // Check for horizontal gesture
    if (fabsf(translation.x) > fabsf(translation.y)) {
        return YES;
    }
    return NO;
}

#pragma mark - Handel pan gesture

- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
    // 1
    if (recognizer.state == UIGestureRecognizerStateBegan) {
        // if the gesture has just started, record the current centre location
        originalCenter = self.center;
    }

    // 2
    if (recognizer.state == UIGestureRecognizerStateChanged) {

        // translate the center
        CGPoint translation = [recognizer translationInView:self];
        self.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y);

        // determine whether the item has been dragged far enough to initiate a delete / complete / edit
        editOnDragRelease = self.frame.origin.x < -self.frame.size.width / 4;
        deleteOnDragRelease = self.frame.origin.x < -self.frame.size.width / 1.5;
        markCompleteOnDragRelease = self.frame.origin.x > self.frame.size.width / 8;
    }

    // 3
    if (recognizer.state == UIGestureRecognizerStateEnded) {
        // the frame this cell would have had before being dragged
        CGRect originalFrame = CGRectMake(0, self.frame.origin.y, self.bounds.size.width, self.bounds.size.height);

        if (!deleteOnDragRelease || !editOnDragRelease) {
            // if the item is not being deleted/edited, snap back to the original location
            [UIView animateWithDuration:0.2 animations:^{
                self.frame = originalFrame;
            }];
        }

        // Edit
        if (!deleteOnDragRelease && editOnDragRelease) {
            [self.delegate editItem:self.item];
        }

        // Delete
        if (deleteOnDragRelease) {
            [self.delegate itemDeleted:self.item];
        }

        // Toggle color
        if (markCompleteOnDragRelease) {
            [self.delegate toggleColorForItem:self.item];
        }
    }
}

The error I'm getting in my crash log is when I select a cell but don't swipe/pan it in any way is:

-[UILongPressGestureRecognizer translationInView:]: unrecognized selector sent to instance 0x1093327c0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UILongPressGestureRecognizer translationInView:]: unrecognized selector sent to instance 0x1093327c0'

Any help would be much appreciated!

Was it helpful?

Solution

The crash is probably happening in the gestureRecognizerShouldBegin: delegate method which gets called for any gesture recognizer for which this class instance is a delegate.

This includes gesture recognizers that iOS might be creating and assigning itself to the underlying standard UITableViewCell.

iOS must be adding a UILongPressGestureRecognizer to the cell (for its own use) and that delegate method gets called for it as well as your UIPanGestureRecognizer.

Since a UILongPressGestureRecognizer does not have a translationInView: method, this results in the "unrecognized selector" crash.

At a minimum, you can check whether the gestureRecognizer parameter is actually a UIPanGestureRecognizer and, if not, do nothing and just return YES.

Also note that even though you've declared the type of the parameter to the gestureRecognizerShouldBegin: method as a UIPanGestureRecognizer *, this does not actually prevent any UIGestureRecognizer * from calling it (ie. leave the parameter type as the generic UIGestureRecognizer * so the meaning and implications are clear).

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 
{
    //if gestureRecognizer is not a UIPanGestureRecognizer, let it go...
    if (! [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
    {
        return YES;
    }

    //Cast the gestureRecognizer as a UIPanGestureRecognizer so
    //compiler knows and checks what we're doing...
    UIPanGestureRecognizer *pgr = (UIPanGestureRecognizer *)gestureRecognizer;

    CGPoint translation = [pgr translationInView:[self superview]];
    // Check for horizontal gesture
    if (fabsf(translation.x) > fabsf(translation.y)) {
        return YES;
    }
    return NO;
}

OTHER TIPS

UITableViewCell has a built-in UILongPressGestureRecognizer, and is itself the gesture delegate. So it can show editing menu(copy, paste,etc.) when user tap-hold the row and the tableView:shouldShowMenuForRowAtIndexPath: method return YES.

the problem here is as @Anna said in your gestureRecognizerShouldBegin method. the built-in (parent class) UITableViewCell's UILongPressGestureRecognizer share the same delegate you assigned to the Pan gesture.

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