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.