Mar 30

Nov 4th 2010 note: This is still a valid and working method, but since iOS4 there’s an easier method to detect swipes using Apple’s UISwipeGestureRecognizer method

I needed proper swipe detection in a UITableViewCell, I’ve seen some online examples but in practice they responded fairly poorly.
I found a proper example in the iPhoneIncubator example which is freely available, and does a proper detection of a swipe through a NSNotificationCenter.

Personally I didn’t need the NSNotificationCenter, so I modified the code to fit into a UITableViewCell subclass.
First we define the horizontal swipe distance at which it gets registered as being an actual swipe, the allowed vertical scrolling margin for people with shaky fingers and some generic CGPoint math operations (these are a 1on1 copied from the original source, I take no credit for this)

#define SWIPE_DRAG_HORIZ_MIN 40
#define SWIPE_DRAG_VERT_MAX 40

#pragma mark -
#pragma mark Helper functions for generic math operations on CGPoints

CGFloat CGPointDot(CGPoint a,CGPoint b) {
        return a.x*b.x+a.y*b.y;
}
CGFloat CGPointLen(CGPoint a) {
        return sqrtf(a.x*a.x+a.y*a.y);
}
CGPoint CGPointSub(CGPoint a,CGPoint b) {
        CGPoint c = {a.x-b.x,a.y-b.y};
        return c;
}
CGFloat CGPointDist(CGPoint a,CGPoint b) {
        CGPoint c = CGPointSub(a,b);
        return CGPointLen(c);
}
CGPoint CGPointNorm(CGPoint a) {
        CGFloat m = sqrtf(a.x*a.x+a.y*a.y);
        CGPoint c;
        c.x = a.x/m;
        c.y = a.y/m;
        return c;
}

Then we begin the touchesBegan function, to detect and possibly override a touch in the tablecell

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
        NSArray *allTouches = [[event allTouches] allObjects];
        UITouch *touch = [[event allTouches] anyObject];
       
        if (touch.phase==UITouchPhaseBegan) {
                startTouchPosition1 = [touch locationInView:self];
                startTouchTime = touch.timestamp;

                if ([[event allTouches] count] > 1) {
                        startTouchPosition2 = [[allTouches objectAtIndex:1] locationInView:self];
                        previousTouchPosition1 = startTouchPosition1;
                        previousTouchPosition2 = startTouchPosition2;
                }
        }       
        [super touchesBegan:touches withEvent:event];
}

Next we do the actual detection of the swipe, and define which action to take, note that there’s a set of if statements in there which sets and checks various variables. These are simply integers I defined in the header file (booleans would be fine too). The localSwipeActive variable defines if the swipe is being performed, this will prevent a single swipe being registered as multiple swipes.

The cellSwiped is a variable I use to see which action to take (either invoke the setSwipeView or the resetSwipeView function) based on the cell’s swipe status.

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
        UITouch *touch = [[event allTouches] anyObject];
        CGPoint currentTouchPosition = [touch locationInView:self];
        NSLog(@"%d %f %d %f time: %g",fabsf(startTouchPosition1.x – currentTouchPosition.x) >= SWIPE_DRAG_HORIZ_MIN ? 1 : 0,
                 fabsf(startTouchPosition1.y – currentTouchPosition.y),
                 fabsf(startTouchPosition1.x – currentTouchPosition.x) > fabsf(startTouchPosition1.y – currentTouchPosition.y)  ? 1 : 0, touch.timestamp – startTouchTime, touch.timestamp – startTouchTime);
        if (fabsf(startTouchPosition1.x – currentTouchPosition.x) >= SWIPE_DRAG_HORIZ_MIN &&
                fabsf(startTouchPosition1.y – currentTouchPosition.y) <= SWIPE_DRAG_VERT_MAX &&
                fabsf(startTouchPosition1.x – currentTouchPosition.x) > fabsf(startTouchPosition1.y – currentTouchPosition.y) &&
                touch.timestamp – startTouchTime < .7 && self.localSwipeActive == 0
                ) {
                // It appears to be a swipe.
                if (startTouchPosition1.x < currentTouchPosition.x) {
                        NSLog(@"swipe right");
                        self.localSwipeActive = 1; // set the swipe status to active so this function doesn’t get called again within the same swipe
                        if (self.cellSwiped == 0) {
                                self.setSwipeView;
                                self.cellSwiped = 1;
                                } else {
                                        if (self.cellSwiped == 1) {
                                                self.resetSwipeView;
                                                self.cellSwiped = 0;
                                        }       
                                }
                        } else {
                                NSLog(@"swipe left");
                                self.localSwipeActive = 1;
                                if (self.cellSwiped == 0) {
                                        self.setSwipeView;
                                        self.cellSwiped = 1;
                                }  else {
                                        if (self.cellSwiped == 1) {
                                                self.resetSwipeView;
                                                self.cellSwiped = 0;
                                        }       
                                }
                        }
                }
        startTouchPosition1 = CGPointMake(-1, -1);
}

And at the end we define that the swipe is over:

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
        self.localSwipeActive = 0; // Reset the swipeActive status now that we finished the swipe
        [super touchesEnded:touches withEvent:event];
}

There you have it, a proper swipe detection in a UITableViewCell subclass

preload preload preload