I have been struggling with a similar problem for several days, and I went through several potential solutions. I found the best way, as well as the simplest solution, to subclass UIGestureRecognizer to handle horizontal movement and snap it to your UITableViews.
How it works, it captures any touch events before they go into a UITableView (also a UIScrollView). UITableView, which is a subclass of UIScrollView, has a built-in custom UIPanGestureRecognizer in which it can detect drag and drop and scroll it accordingly. By adding your own subclass of UIGestureRecognizer, you can get the strokes before recognizing the UIScrollView gestures. If your recognizer sees that the user is dragging it horizontally, it must change its state by overriding the Moved: method to UIGestureRecognizerStateBegan. Otherwise, it sets it to UIGestureRecognizerCancelled, which allows you to process strokes instead of UIScrollView.
Here's what my subclass of UIGestureRecognizer looks like:
#import <UIKit/UIGestureRecognizerSubclass.h> @interface TableViewCellPanGestureRecognizer : UIGestureRecognizer { CGPoint startTouchPoint; CGPoint currentTouchPoint; BOOL isPanningHorizontally; } - (void)reset; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; @end @implementation TableViewCellPanGestureRecognizer -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event]; startTouchPoint = [[touches anyObject] locationInView:nil]; } -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesMoved:touches withEvent:event]; currentTouchPoint = [[touches anyObject] locationInView:nil]; if ( !isPanningHorizontally ) { float touchSlope = fabsf((currentTouchPoint.y - startTouchPoint.y) / (currentTouchPoint.x - startTouchPoint.x)); if ( touchSlope < 1 ) { self.state = UIGestureRecognizerStateBegan; isPanningHorizontally = YES; [self.view touchesCancelled:touches withEvent:event]; } else { self.state = UIGestureRecognizerStateCancelled; [self.view touchesCancelled:touches withEvent:event]; } } else { self.state = UIGestureRecognizerStateChanged; } } -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesCancelled:touches withEvent:event]; self.state = UIGestureRecognizerStateCancelled; [self.view touchesCancelled:touches withEvent:event]; } -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesEnded:touches withEvent:event]; self.state = UIGestureRecognizerStateCancelled; } -(void)reset { [super reset]; startTouchPoint = CGPointZero; currentTouchPoint = CGPointZero; isPanningHorizontally = NO; } @end
Then I have a subclass UITableView that attaches the recognizer to itself and implements an action method to trigger horizontal movement of individual rows:
In my UITableView init:
horizontalPanGesture = [[TableViewCellPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleHorizontalDrag:)]; [self addGestureRecognizer:horizontalPanGesture]; [horizontalPanGesture release];
And the method of action:
-(void)handleHorizontalDrag:(UIGestureRecognizer *)gesture { UIGestureRecognizerState state = gesture.state; // Set the touched cell if (!touchedCell){ NSIndexPath *indexPathAtHitPoint = [self indexPathForRowAtPoint:[gesture locationInView:self]]; id cell = [self cellForRowAtIndexPath:indexPathAtHitPoint]; touchedCell = cell; startTouchPoint = [gesture locationInView:touchedCell]; } if ( state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged ) { // move your views horizontally } else if ( state == UIGestureRecognizerStateEnded || state == UIGestureRecognizerStateCancelled ) { touchedCell = nil; } }
The above information relates to the fact that the current cell is within the tabular view, and then applies horizontal movements to it when the user drags them left or right. However, if my gesture recognizer determines that the touches are designed to scroll vertically, it just cancels itself, and the next touches are sent to the UITableView to automatically trigger the vertical scroll.
This setting seems to be much simpler than catching hitTest and does all kinds of touch tricks in the UITableView itself. It just makes an immediate determination of the direction of movement of the touch. You will want to read UIGestureRecognizers - specifically about how it should be subclassed. You need to forward certain touch events, such as touchesCancelled to a UITableView, since the UITableView created in panGestureRecognizer will not handle these events, as is usually the case. Obviously, you will need to move entire table views, not individual cells, but this should be pretty simple.
This solution, so simple, took time to fit my exact needs exactly. I'm new to iOS, so I had to spend a lot of time reading and messing around with gesture recognizers and scrolling to figure this out.