Horizontal movement of a UITableView?

I am trying to distinguish between horizontal scrolling / panning and vertical scrolling in a UITableView . The behavior I'm looking to imitate (in a way) is the Twitter iPad app that has several UITableView that can be moved horizontally on the screen. If I move my finger left or right on one of these UITableView , the view itself moves horizontally. If I scroll vertically, the view will scroll as expected.

I am having trouble finding the right way to implement this behavior. I saw some lessons about this, which include adding touch event handlers in UITableViewCells and overriding hitTest in UITableView for the corresponding event path depending on which direction the gesture is moving. I have implemented some of these methods, but none of them work particularly well.

Does anyone know the correct way to implement this behavior? Conditional actions on a UITableView , depending on the direction of movement of the user's finger?

Thanks.

+7
source share
3 answers

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.

+9
source

I never did this myself, but since UITableView is a subclass of UIScrollView, UITableView delegates are also UIScrollViewDelegates. Therefore, in your UITableViewController subclass, you should be able to use UIScrollView delegates and intercept scrolls - to also call a super method.

0
source

If you want the UITableViews to be placed side by side, and when you scroll horizontally, you expect all of them to move horizontally at the same time (for example, a photo gallery with UITableViews instead of images) you can do the following:

Use UIScrollView and add UITableViews as UIScrollView routines. You should set scrollview contentSize as follows:

 CGRect bounds = scrollView.bounds; scrollView.contentSize=CGSizeMake(bounds.size.width * kNumOfTableViews, bounds.size.height); 

so that UIScrollview scrolls horizontally and not vertically.

You can also use

 scrollView.pagingEnabled=YES; 

depending on the desired behavior.

UITableviews will react in the usual way if you place your finger vertically and you can switch between UITableViews by moving your finger horizontally.

For more information on how to do this efficiently, you can watch a video of WWDC 2010. Session 104 - Designing scrolling applications and check out the source code here: http://developer.apple.com/library/ios/#samplecode/PhotoScroller/ . This session describes how to navigate between images. Instead of images you will use UITableViews

However, if you want each UITableView to be able to move horizontally independently and possibly overlap with another, as in the twitter app for iPad, this solution will not work for you, at least not out of the box.

0
source

All Articles