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

10 Responses to “Custom swipe detection in a UITableViewCell”

  1. Jeroen says:

    By request, I’ll make a fully working example project with this code included the upcoming week

  2. mga says:

    This example is similar to others I have seen and works well. But what if you need to have the rest of the table view cells “close” once any other is “opened” via swipe?

  3. Jeroen says:

    I don’t have that built in my code, what I do is reset the swipe status once the tablecell goes off-screen so it doesn’t get recycled with the swipe status enabled.

    But from the top off my head I suppose you could delegate a function to the UITableViewController to see if any ‘cell.cellSwiped = 1′ are there in the indexPath, and if so reset the status using the ‘resetSwipeView’ on that cell before enabling another one.

    Or set a global variable in your appDelegate which holds the index of the swiped cell so you can use that to ‘unswipe’ the other cell in the same function as above with an external call to the parent.

    I’m still making a fully working project example which can be downloaded, I’ll see if I can add it as an option in the code.

  4. senthil says:

    Can you please share the example project

  5. Jeroen says:

    I didn’t get around to finishing the example yet, I’ll try and get it up next week

  6. wsidell says:

    Wondering if you have finished the example for this?

  7. Jeroen says:

    No I haven’t made time yet, is there a specific part you need help with ?

  8. Dave Phoenix says:

    Hi, please can you tell me, with your code, how can i tell which cell had been swiped?

    I want to change the

    cell.alpha = 0.5;

    to effectively dim the swiped cell.

    Excellent example, and thank you in advance for any help you can offer. Dave.

  9. Jeroen says:

    Hi Dave,

    The code for detecting the swipe is used in a UITableViewCell subclass so it’s always used for the cell you’re actually swiping.

    Basically in your UITableViewController class you add a:
    #import “ImageCell.h”

    And then you specify the ImageCell subclass to be used:
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @”Cell”;
    ImageCell *cell = (ImageCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    etc..

    Then implement the code in the post into this ImageCell subclass, you can set the alpha values for the elements you use in the subclass.

    i.e. I darken several elements in the ImageCell subclass once the swipe is set with:
    self.imageView.alpha = 0.7;
    self.urlLabel.alpha = 0.7;
    self.titleLabel.alpha = 0.7;

    Let me know if this clarifies it for you

    I admit I still need to put an example online, been very busy with getting an X-mas app ready, so very guilty on not keeping a promise ;)

  10. anka says:

    Hi thanks for your example. Maybe it would be also an option to simply use gesture recognizers. Look at our blog at http://blog.blackwhale.at/?p=795 and checkout our source code example.

Leave a Reply

preload preload preload