Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forward touches to a UIScrollView

I have two view controllers. View controller A has a UIScrollView and presents view controller B. The presentation is interactive and controlled by the scrollView.contentOffset.

I want to integrate an interactive dismiss transition: When panning up, ViewController B should be dismissed interactively. The interactive dismiss transition should also control the ViewController A scrollView.

My first attempt was using a UIPanGestureRecognizer and setting the scrollView.contentOffset according to the panned distance. This works, however when the pan gesture is ended, the scrollView offset has to be animated to the end position. Using -[UIScrollView setContentOffset:animated: is not a good solution since it uses a linear animation, doesn't take the current pan velocity into account and doesn't decelerate nicely.

So I thought it should be possible to feed the touch events from my pan gesture recognizer into the scroll view. This should give us all the nice scroll view animation behavior.

I tried overriding the -touchesBegan/Moved/Ended/Cancelled withEvent: methods in my UIPanGestureRecognizer subclass like this:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [scrollView touchesBegan:touches withEvent:event];
    [scrollView.panGestureRecognizer touchesBegan:touches withEvent:event];

    [super touchesBegan:touches withEvent:event];
}

But apparently something is blocking the scroll view from entering tracking mode. (It does go to dragging = YES but that's about it.) I verified the scrollView is userInteractionEnabled, not hidden and added to the view hierarchy.

So how can I forward my touch events to UIScrollView?

like image 482
Ortwin Gentz Avatar asked Feb 04 '14 13:02

Ortwin Gentz


2 Answers

After reading an interesting answer describing UIScrollView's event flow, I came to the conclusion that trying to "remote control" a scroll view from a gesture recognizer is probably very hard to achieve because touches are mutated while being routed to views and gesture recognizers. Since UITouch doesn't conform to NSCopying we also can't clone touch events in order to send them later in unmodified state.

While not really solving the problem I asked for, I found a workaround to accomplish what I need. I just added a scroll view to view controller B and synced it with VC A's scroll view (which is added to the view hierarchy when vertically scrolling):

// delegate of VC B's scrollView
- (void)scrollViewDidScroll:(UIScrollView*)scrollView
    scrollViewA.contentOffset = scrollView.contentOffset;
}

Thanks to Friedrich Markgraf who came up with the idea.

like image 96
Ortwin Gentz Avatar answered Nov 16 '22 12:11

Ortwin Gentz


Try subclassing the UIScrollView and overriding hitTest:withEvent: so that the UIScrollView picks up touches outside its bounds. Then you get all the nice UIScrollView animations for free. Something like this:

@interface MagicScrollView : UIScrollView
@end

@implementation MagicScrollView

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // Intercept touches 100pt outside this view's bounds on all sides. Customize this for the touch area you want to intercept
    if (CGRectContainsPoint(CGRectInset(self.bounds, -100, -100), point)) {
        return self;
    }
    return nil;
}

@end

Or in Swift (Thanks Edwin Vermeer!)

class MagicScrollView: UIScrollView {
    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        if CGRectContainsPoint(CGRectInset(self.bounds, -100, -100), point) {
            return self
        }
        return nil
    } 
}

You may also need to override pointInside:withEvent: on the UIScrollView's superview, depending on your layout.

See the following question for more info: interaction beyond bounds of uiview

like image 3
johnboiles Avatar answered Nov 16 '22 13:11

johnboiles