2
votes

assume I've got the following view hierarchy;

enter image description here

Those views are actually ShinobiCharts, which are subclasses of UIView. View1 acts as a main chart that can be touched by the user (pinch, pan, long press and so on). This view in turn controls the other views (View2, View3 and so on) with regards to specific, touch dependent properties (the user pans the View1, so views 2 and 3... have to act accordingly and pan as well). However, once the user scrolls the UIScrollView, View1 may disappear from the screen, leaving only views 2, 3 etc. visible, which don't have the corresponding gesture recognizers, so the user can't interact with the charts any more, bad user experience. I sure could add recognizers to these additional charts as well, but during pinch, that would mean that both fingers would have to be located within a single view, the user can't just touch where he wants in order to pinch and zoom, again, bad user experience.

To cut things short, I need some sort of touch interceptor that covers the entire content area of the UIScrollView so that when a user pinches or pans, the corresponding touches will be forwarded to the main chart (View1) which in turn can update the other subviews. Vertical scrolling the UIScrollView should be possible at all times.

First experiment:

I've tried adding a transparent UIView to the ViewController that covers the UIScrollView. However, even with a direct reference to View1, the touches wouldn't get forwarded.

-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.chartReference touchesBegan:touches withEvent:event];
}

I'm not so sure if this is the correct way to achieve this anyways.

Second experiment:

Disabling userInteraction on the chart subviews in UIScrollView and add my own pinch and pan gestures (in UIViewController) to the UIScrollView.

Issues; 1. The UIScrollView doesn't scroll any more. 2. How to forward these gestures to View1?

I'm afraid I can't provide more example code at this point since there isn't really much relevant code to show yet.

Edit: Small note to experiment two; The gesture recognisers have been added as follows;

UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchGesture:)];
pinchGestureRecognizer.cancelsTouchesInView = NO;
[scrollView addGestureRecognizer:pinchGestureRecognizer];

Simultaneous gesture recognition on the UIViewController has been enabled;

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;

}

Edit:

Problem 2 for experiment 2 has been solved. I didn't set the correct delegate for the pan/pinch gestures. D'oh!

What remains is how to forward the pinch/pan gestures.

1
Did you try the UIScrollView zoom functionality. It supports by default pinch to zoom, you just have to provide the view that will be zoomed, so I think you just have to determine which view is on screen and pass it to the UIScrollView.danypata
Zooming in UIScrollView seems more like a solution to simply zoom subViews of UIScrollView, hence modifying their sizes. That's not what I'm looking for though. The sizes have to remain constant as only the content of the my UIViews need to change, which in turn is handled by the ShinobiChart itself.AlBirdie

1 Answers

1
votes

You're close with placing a transparent view on top of the others. But, you need to override one more method.

You want to override hitTest:withEvent on the transparent view. This method is used while traversing the responder chain to see which view handles a touch in a particular area. If a view handles that touch, it returns Itself. If it wants to pass it on to the next view below it, it returns nil. If it knows another view handles that touch, it can return that view.

So, in your case, if the point is in the target area of your top transparent view, you return view1. View1's gesture recognizer should then be called.

Example:

InterceptorView is a transparent view which lies on top of the scrollView. TargetView is a view inside the scrollView, and has a TapGestureRecognizer attached to it.

class InterceptorView: UIView {

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    @IBOutlet weak var targetView1: UIView?

    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        print("[Interceptor] Testing point: \(point) ")
        if self.pointInside(point, withEvent: event) {
            println("Hit")
            return targetView1
        }
        else {
            println()
            return nil;
        }
    }
}

--

class TargetView: UIView {

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

     @IBAction func handleGesture(gestureRecognizer: UIGestureRecognizer) {
        let location = gestureRecognizer.locationInView(self)
        print("[TargetView] Got gesture.  Location \(location) ")
        if (pointInside(location, withEvent: nil)) {
            println("Inside");
        }
        else {
            println("Outside");
        }
    }
}

Here's the project: https://github.com/annabd351/GestureForwarding

(there's some other stuff in there too, but it works!)