How to receive touch event behind another view? - ios

I have two subviews inside the view. The frame is equal. A view has a swipe left gesture recognizer and B view has a swipe right gesture recognizer. B is behind A so I can only trigger swipe left gesture. How can I receive both?

The view in the back will not respond because, well, it's in the back.
For both gestures to respond simultaneously (left+right at the same time), add a UIGestureRecognizerDelegate to either and handle shouldRecognizeSimultaneouslyWithGestureRecognizer as suggested below.
If you want B to respond to a gesture, even though it is partially hidden by A, rethink your hierarchy: add a 3rd view above A & B (call it C), same size as B, as a placeholder to your gesture recognizer.
Your delegate:
// leftGesture
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer
*)otherGestureRecognizer
{
return otherGestureRecognizer == rightGesture;
}

How can I receive both?
Add both gesture recognizers to view A, since that's the view in front. Since you need the right swipe gesture to be handled by view B, make view B the target for the right swipe gesture recognizer.
A better option might be to make your view controller the target of both gesture recognizers and let it sort out what to do with the user's gestures. That helps to get the views out of the business of knowing how the user interface is set up -- they just have to follow instructions from the view controller. If you adopt that plan, your view controller might look like this in part:
- (void)viewDidLoad
{
UISwipeGestureController *leftSwipe = [[UISwipeGestureController alloc] initWithTarget:self action:#selector(leftSwipe:)];
leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
UISwipeGestureController *rightSwipe = [[UISwipeGestureController alloc] initWithTarget:self action:#selector(rightSwipe:)];
rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
[self.viewA addGestureRecognizer:leftSwipe];
[self.viewA addGestureRecognizer:rightSwipe];
}
- (IBAction)leftSwipe:(id)sender
{
[self.viewA doSomething];
}
- (IBAction)rightSwipe:(id)sender
{
[self.viewB doSomethingElse];
}

Related

Gesture recognizor on UICollectionView not receiving gestures

I have a ViewController with a vertically scrolling collection view that takes up the entire view. I want to be able to get swipe and pan gestures on the entire collection view (not just on cells) but I can't get any gestures. I have tried adding the gesture recognizer to the view and the collection view but neither seem to work.
Adding the gesture recognizer to the view
self.panEdgeGesture = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
self.panEdgeGesture.delegate = self;
[self.collectionView addGestureRecognizer:self.panEdgeGesture];
[self.panEdgeGesture setEdges:UIRectEdgeRight];
Then I added these functions:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch{
return YES;
}
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
- (void)handlePan:(UISwipeGestureRecognizer *)sender
{
DebugLog(#"Received pan gesture");
}
Could the collection view cells stop the gesture events from triggering? They have no gestures themselves.
Per UIScreenEdgePanGestureRecognizer's Class Reference:
After creating a screen edge pan gesture recognizer, assign an
appropriate value to the edges property before attaching the gesture
recognizer to your view. You use this property to specify from which
edges the gesture may start. This gesture recognizer ignores any
touches beyond the first touch.
So change you code to:
self.panEdgeGesture = [[UIScreenEdgePanGestureRecognizer alloc];
[self.panEdgeGesture setEdges:UIRectEdgeRight];
initWithTarget:self action:#selector(handlePan:)];
self.panEdgeGesture.delegate = self;
[self.collectionView addGestureRecognizer:self.panEdgeGesture];

Overriding a framework gesture in AVPlayerViewController

I want the ability to change how the playback controls are presented while using the new AVPlayerViewController in AVKit. Basically, I want to overide the single finger tap gesture to do something else, and replace that gesture with a double finger tap. I am subclassing AVPlayerViewController to add this functionality.
I can add the double finger tap easily by creating a new UITapGestureRecognizer, but doing so with a single tap does nothing, as the playback controls still appear and my custom gesture method is not called. I assume because the AVPlayerViewController has a gesture with priority that is called instead.
I setup the gestures like normal...
// singleFingerTap: will never fire...
UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleFingerTap:)];
singleFingerTap.numberOfTouchesRequired = 1;
singleFingerTap.delegate = self;
[self.view addGestureRecognizer:singleFingerTap];
// doubleFingerTap: will work correctly...
UITapGestureRecognizer *doubleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleFingerTap:)];
doubleFingerTap.numberOfTouchesRequired = 2;
doubleFingerTap.delegate = self;
[self.view addGestureRecognizer:doubleFingerTap];
Any thoughts on how to achieve this without accessing private properties? Is it even permitted? I know I can create my own view controller with an instance of AVPlayer and then create my own playback controls, but I'm hoping I can use the lightweight AVKit player here with a few simple modifications.
I've tried looping through the gestures in AVPlayerViewController's view and removing them, but the gestureRecognizers property was empty. Even if I could do this, I wouldn't know how to add back the gesture to display the playback controls on a double finger tap instead of the single finger tap.
Any suggestions would be much appreciated! Especially whether this is possible/allowed or not. Thanks!
EDIT:
I have found a way to explicitly unblock the player's private gesture that was blocking my own gesture.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
id firstGesture = gestureRecognizer;
id secondGesture = otherGestureRecognizer;
if ([firstGesture isKindOfClass:[UITapGestureRecognizer class]] && [secondGesture isKindOfClass:[UITapGestureRecognizer class]]) {
UITapGestureRecognizer *tapGesture = firstGesture;
UITapGestureRecognizer *otherTapGesture = secondGesture;
if (tapGesture.numberOfTapsRequired == otherTapGesture.numberOfTapsRequired && tapGesture.numberOfTouches == otherTapGesture.numberOfTouches) {
// Disable the single tap that shows the playback controls...
return YES;
}
}
return NO;
}
This effectively prevents the playback controls from appearing on a tap, and my singleTapGesture: fires as expected. However, I now have the issue of getting the playback controls to appear on a different gesture. Is it possible to reroute the private gesture, or simulate the private gesture programmatically?
Why not just check/modify the target/remove the default gesture recognizers first?
You can access them with the standard UIView's gestureRecognizers.
The gestures recognizers may be buried inside a private subview.
Another solution would be to set userInteractionEnabled to NO and add your gesture recognizers to the superview or to a "overlay" transparent view.

SWRevealViewController pan gesture recogniser issue

I am using SWRevealViewController from John-Lluch. I need to use pan gesture to view the sidebars and I am using swipe to view my previous and next articles. However, the pan gesture can only be detected.
UPDATED: My swipe gesture worked if I disable my pan gesture.
Pan Gesture
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
Swipe gesture
UISwipeGestureRecognizer *left = [[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeLeft:)] autorelease];
left.direction = UISwipeGestureRecognizerDirectionLeft;
UISwipeGestureRecognizer *right = [[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeRight:)] autorelease];
right.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:left];
[self.view addGestureRecognizer:right];
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
[self.view addGestureRecognizer:left];
Notice the difference? Implement the SwipeGesture as a property in your .h or make it in your .xib and link it to your .h
It is difficult to handle both pan and swipe gesture recognizer simultaneously. You will have handle the SWRevealViewController delegates for pan gesture and also swipe gesture for the current viewcontroller.
As apple suggests to differentiate the gesture you could use the following method
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
Excerpt from the Apple Documentation:
This method is called when recognition of a gesture by either
gestureRecognizer or otherGestureRecognizer would block the other
gesture recognizer from recognizing its gesture. Note that returning
YES is guaranteed to allow simultaneous recognition; returning NO, on
the other hand, is not guaranteed to prevent simultaneous recognition
because the other gesture recognizer's delegate may return YES.

How to identify touch on UIView subview

I have a scrollview. Inside the scrollview, I have three subviews, A, B, C. When I click on subview A, I want to get either its tag value or know which view I've clicked. I've gone through many codes and blogs, but couldn't find a solution for it.
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {}
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event{} //This function is not working with sub view.
Tried these methods but didn't solve my problem.
Use UITapGestureRecognizer. Add a tap gesture recognizer to each of A, B, C views, set the delegate to the view controller and you will be notified on each tap.
UITapGestureRecognizer* tgrA = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
[viewA addGestureRecognizer:tgrA];
...
-(void) handleTapGesture:(UIGestureRecognizer *)sender
{
//sender.view.tag will give you what you need.
}
More information on tap gesture recognizer here:
https://developer.apple.com/library/ios/documentation/uikit/reference/UITapGestureRecognizer_Class/Reference/Reference.html

disable pinch recognizer when 1 finger is lifted on the screen

I have a 2d map that the user can zoom and pan using the gesture recognizers. While it works, i want the user to start panning immediately after a zoom once they have 1 finger lifted. Unfortunately, in the docs it says:
The gesture ends (UIGestureRecognizerStateEnded) when both fingers
lift from the view.
which is pretending me from going from a pinch zoom to a pan right away. What could i do to fix this?
This is possible, and easy! It involves being your gesture recognizer's delegate. Something no-one seems to know exists. In my view controller subclass I have declared both conforming to the protocol <UIGestureRecognizerDelegate> and two ivars:
UIPinchGestureRecognizer *myPinchGR;
UIPanGestureRecognizer *myPanGR;
These ivars are instantiated in view did load. Notice setting self as the delegate.
-(void)viewDidLoad{
[super viewDidLoad];
myPanGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panTarget:)];
myPanGR.delegate = self;
[self.view addGestureRecognizer:myPanGR];
myPinchGR = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(pinchTarget:)];
myPinchGR.delegate = self;
[self.view addGestureRecognizer:myPinchGR];
}
One of the delegate calls made by a UIGestureRecognizer is shouldRecognizeSimultaneouslyWithGestureRecognizer: if I had more than two gesture recognizers then this function would have to contain some logic. But since there are only two I can just return YES.
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
Now you do have to include a little (very little) extra logic in your action methods to screen for the appropriate conditions.
-(void)panTarget:(UIPanGestureRecognizer *)panGR{
if (panGR.numberOfTouches > 1) return;
NSLog(#"panny");
}
-(void)pinchTarget:(UIPinchGestureRecognizer *)pinchGR{
if (pinchGR.numberOfTouches < 2) return;
NSLog(#"pinchy");
}
Run this code an look at the logs. you will see when you move one finger you will see "panny" when you place a second finger down you will see "pinchy", and back and forth.
Use this code inside the gesture handling method.
if (gesture.numberOfTouches != 2) {
// code here to end pinching
}
As Gesture handling method will be called immediately when user lift a finger while 2 finger pinching.

Resources