views:

47

answers:

1

Hi there,

I'm struggling with following problem. I'm having a view controller with scroll view. With this scroll view, user should be able to swipe through set of "pages" - like in apple's weather app, so nothing uncommon. Then each "page" within the scroll view is table view, showing a list of items. And here I'm getting into the problems.

Issue is that scroll view intercepts all touches to see if user moved his finger or just tapped some point inside some subview, and it doesn't pass events to it's subviews (or superview) in case it recognizes a move.

This behavior is causing a real problem in my case (table view within scroll view), because table view is descendant of scroll view and it's a view which is returned by hit-testing under normal circumstances, which causes that all events are grabbed by table view, so its parent scroll view get's never called - so no swiping is ever performed.

Naturally, when I override hitTest:withEvent: method in my subclass of table view and return a superview (the scroll view, actually), situation is opposite - user can swipe through pages, but cannot scroll the table view.

So what I need to figure out is how to - at any level - determine what action user wants to take (swipe or scroll down/up the table view) and then pass event flow to appropriate component.

I'm fighting with this problem couple of days now, but cannot get over. Below is what I've ended with up so far. It's very simplified here. It does almost what I need - except that table view is never scrolled... Please note that my table view subclass always returns self.superview, which is the scroll view.

/// UIViewController

- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event {

    [super touchesBegan: touches withEvent: event];

    /// Save initial touch location for later use
    _motionStart = [[touches anyObject] locationInView: self.scrollView];

    /// Save reference to initial event
    _initialEvent = event;

    /// Fire timer
    _gestureTimer = [NSTimer scheduledTimerWithTimeInterval: 0.2
                                 target:self selector:@selector(gestureTimer:)
                               userInfo: touches repeats: NO];
}

- (void) touchesMoved: (NSSet *) touches withEvent: (UIEvent *) event {

    if(!_gestureTimer && !_forwardingTouchesToTableView) {
    /// If no timer is running and no event forwarding is performed, pass to super
        [super touchesMoved: touches withEvent: event];
    }
    else {
        /// Pass event to currently visible tableview
        [self.scrollView.centerPage touchesMoved: touches withEvent: event];
    }

}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    [super touchesEnded: touches withEvent: event];

    if(_forwardingTouchesToTableView) {
        [self.scrollView.centerPage touchesEnded: touches withEvent: event];
    }

    /// Reset state
    _motionStart = CGPointZero;

    _forwardingTouchesToTableView = NO;
}

- (void) gestureTimer: (NSTimer *) timer {

    _gestureTimer = nil;

    NSSet *touches = (NSSet *)timer.userInfo;   
    UITouch *touch = (UITouch *)[touches anyObject];

    CGPoint currentTouchLocation = [touch locationInView: self.scrollView];

    float deltaX = fabs(_motionStart.x - currentTouchLocation.x);
    float deltaY = fabs(_motionStart.y - currentTouchLocation.y);

    if(deltaY >= 4.0 && deltaX <= 12.0) {

        /// Vertical move, forward to tableview
        [self.scrollView cancelTouches];
        _forwardingTouchesToTableView = YES;
        [self.scrollView.centerPage touchesBegan: touches  withEvent: _initialEvent];
        _initialEvent = nil;
    }
    else {
        /// Horizontal swipe - just reset state
        _initialEvent = nil;
        _forwardingTouchesToTableView = NO;
    }
}

To be honest, I don't yet what to do next. I tried quite a few solutions for this problem I've found, but neither of them really worked. I'll highly appreciate any advice or tip on this. Maybe it would be enough if somebody could explain how handling of events is implemented by UIScrollView or UITableView?

Thanks for any suggestion.

A: 

So basically you want vertical scrolling within the table view, but paging for horizontal swipes?

If you can live with 3.2+ compatibility (on iPhone 4.0 actually), I'd try: - stick with standard components and no fancy extra handling - use a UISwipeGestureRecognizer and add this to your table view - if the gesture recognizer fires, then check if it is a horizontal swipe, and if so change to pages programmatically

Of course the whole concept only works if no swipe gestures on cells are needed - i.e. for deleting. But this is valid for all approaches, of course.

Prior to 3.2, you really need to get your hands wet with the touch handlers. Subclassing UITableView and and overriding touchesBegan: etc. there would be my first try (and recognizing horizontal swipes and calling the paging), although the whole solution is ugly.

Eiko
Thanks for your suggestion, Eiko, but unfortunately app must be 3.0+ compatible, so that's why I'm messing with that stuff. I tried to implement touchesBegan:... and co. in custom subclasses of UITableView, UIScrollView and UIViewController, but with no luck so far. UIScrollView documentation mentions it's using timers to check if it should start 'dragging' of pass touches to the underlaying subviews, which is basically what I need to do as well, except adding horizontal/vertical gesture recognition to it. I'm wondering how it's implemented in UIScrollView!
Matthes