8

I have some trouble with forwarding gestures and touches. I played around with it quite a bit, but i cant get it working the way I want.

Basically I want to control a scrollview on a dualscreen with 2 fingers and forward everything else to the ipad-views behind a overlaying scrollview.

To be able to control the scrollview on the dualscreen I subclassed UIScrollView and added it as a overlaying view with a clear background to the ipad-screen.

Then I hooked it up with a delegate to forward its dragging and stuff to the scrollview on the dualscreen. this works perfectly.

As I wrote I want the scrollview to respond just to 2 finger-scroll, so i set it to

ScrollView.panGestureRecognizer.minimumNumberOfTouches = 2;

But the scrollview eats up all touches and I dont get it right to forward everything else but 2 finger touches to the views behind. I think overriding the

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

must do the trick, but I don't get it right to detect the number of fingers on the screen right.

Fedor Hajdu
  • 4,657
  • 3
  • 32
  • 50
Tom
  • 213
  • 3
  • 10

4 Answers4

5

I'd like to share/explain the solution to the problem. That said I also want to point out that hatfinch input led me in the right direction. thank you very very much mate!

The problem when attempting to put a view/scrollview as an overlaying toplayer is that the toplayer doesnt know about its "next responder". putting the view/scrollview as a underlaying view will solve this problem. you may need to fine tune the touch behaviour of any scrollview within that underlaying scrollview to get the behaviour right (like setting maximum number of touches)

The solution is to subclass a UIScrollview, override this methods override the touchesBegan: and other touch methods as follows (see user1085093 answer), and add it as underlaying view to the ipad-screen.

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

// If not dragging, send event to next responder
if (!self.dragging){
    [self.nextResponder touchesBegan: touches withEvent:event];
}
else{
    [super touchesBegan: touches withEvent: event];
}}

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

// If not dragging, send event to next responder
if (!self.dragging){
    [self.nextResponder touchesMoved: touches withEvent:event];
}
else{
    [super touchesMoved: touches withEvent: event];
}}


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

// If not dragging, send event to next responder
if (!self.dragging){
    [self.nextResponder touchesEnded: touches withEvent:event];
}
else{
    [super touchesEnded: touches withEvent: event];
}}

i did the setup of the scrollview like this:

 TopLayerScrollView *newScrollView = [[TopLayerScrollView alloc] init];
[newScrollView setBackgroundColor:[UIColor clearColor]];
[newScrollView setFrame:self.tabBarController.view.frame];
[newScrollView setContentSize:dualScreenViewController.scrollContent.contentSize];
newScrollView.showsHorizontalScrollIndicator = NO;
newScrollView.showsVerticalScrollIndicator = NO;
newScrollView.delegate = self;
newScrollView.bounces = NO;
[newScrollView scrollsToTop];
newScrollView.panGestureRecognizer.minimumNumberOfTouches = 2;

self.topLayerScrollView = newScrollView;
[newScrollView release];

[self.tabBarController.view removeFromSuperview];
[topLayerScrollView addSubview:self.tabBarController.view];
[window addSubview:topLayerScrollView];
[topLayerScrollView bringSubviewToFront:self.tabBarController.view];

The delegate method of the underlaying scrollview:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (scrollView.dragging || scrollView.tracking)
{
    [dualScreenViewControlleremphasized text.scrollContent setContentOffset:CGPointMake(scrollView.contentOffset.x, scrollView.contentOffset.y) animated:NO];
    self.tabBarController.view.frame = CGRectMake(scrollView.contentOffset.x, scrollView.contentOffset.y, self.tabBarController.view.frame.size.width, self.tabBarController.view.frame.size.height);
}}

This solution works great. The other solution would be to have a scrollview as an overlaying view as I first intended. As said the problem is to let the toplayer view know about the views(nextResponder) benath it. To achive this you must sublass UIScrollview and create a UIResponder-property you must hookup either in your interface-builder file or during runtime. this way the overlaying scrollview will know who is the next responder. please see morningstar's answer

Community
  • 1
  • 1
Tom
  • 213
  • 3
  • 10
  • See https://developer.apple.com/videos/play/wwdc2012/223/ where Josh Shaffer "really discourages" us from manually calling `touches...` methods on next responder or other objects. There is also a solution in that video. – Oleksii Nezhyborets Jul 01 '20 at 14:14
2

Unfortunately, you can't do anything with -pointInside:withEvent: here because the system decides which view the touch is in (or which views the touches are in) before it determines whether they satisfy the requirements of the gesture recogniser.

I think your best bet here is to have the view that is currently behind the scrollview actually be the content view of the scrollview, and to move that view every time the content offset of the scroll view changes to keep it in the same position on the screen. (This sounds expensive but it's not really.) So instead of being an overlay, your scroll view is more like an underlay.

You may also wish to play with UIScrollView.delaysContentTouches, -[UIScrollView touchesShouldBegin:withEvent:inContentView:], etc. to refine the behaviour.

hatfinch
  • 3,095
  • 24
  • 35
  • thank you very much for this. I addded a explanation how i solved this in the end. – Tom Nov 04 '13 at 12:28
1

Update for Xcode 6.4, Swift 1.2 :

I also subclassed UIScrollView:

import UIKit

class WBMScrollView: UIScrollView
{
    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent)
    {
        self.nextResponder()?.touchesBegan(touches, withEvent: event)
        super.touchesBegan(touches, withEvent: event)
    }
}

In the app main vc ViewController.swift I tested it in:

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        println(" touches began")
    }

and it worked. The scrollview has been added via IB and hooked up to ViewController.swift

MB_iOSDeveloper
  • 4,178
  • 4
  • 24
  • 36
1

With my app I needed the one-finger touch events to get forwarded to the parent view controller of the scroll view while still allowing the scroll view to zoom in/out.

Using the next responder did not work because I'm also adding another overlaid view which ends up becoming the next responder and stealing the touch events. I ended up doing something based on Tom's answer by simply passing in the parent view controller and sending touch events directly to it. Here it is in Swift:

class CustomScrollView: UIScrollView {

    var parentViewController: UIViewController?

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        parentViewController?.touchesBegan(touches, with: event)
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        parentViewController?.touchesMoved(touches, with: event)
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        parentViewController?.touchesEnded(touches, with: event)
    }
}
Swindler
  • 802
  • 10
  • 9