I created a UIPanGestureRecognizer in a UICollectionViewCell subclass. It works in all cells except the top of the screen. About the height of status bar the pan gesture recognizer is dead. It doesn't fire. My test app only has a UICollectionView and nothing else. No status bar. I think maybe it has to do with the notification center "active edge" stealing touch events.
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGestureRecognized:)];
_panGestureRecognizer.delegate = self;
_panGestureRecognizer.maximumNumberOfTouches = 1;
[self addGestureRecognizer:_panGestureRecognizer];
At the top of screen when I pan in the cell I get no message upon pan begin. There is no call to the callback until I lift the finger. Then the callback receives BEGAN and immediately after that it receives ENDED.
- (void)panGestureRecognized:(UIPanGestureRecognizer*)sender {
NSLog(#"pan detected"); // for first cell, only called when finger is lifted up.
if (sender.state == UIGestureRecognizerStateBegan) {
NSLog(#"BEGAN");
} else if (sender.state == UIGestureRecognizerStateEnded) {
NSLog(#"ENDED");
}
}
The cell is the delegate of the gesture recognizer. It implements:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
The cell also overwrites the UIView method:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
Is this a known problem with pan gesture recognizers at the top of the screen? And is there a workaround?
I have a button and a gesture recognition and when the button is pressed only one method should be called.
Most cases only the gesture recognition is being called which is fine but every no and then the button gets called to which causes problems.
I have a view and then a scroll view. All my buttons are on the scroll view
you can implement this in your view controller and assign it to the delegate of the gesture recognizer.
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
CGPoint p = [gestureRecognizer locationInView:self.view];
UIView *view = [sefl.view hitTest:p withEvent:nil];
if ([view isKindOfClass:[UIButton class]]) {
return NO;
}
return YES;
}
or you can implement - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch with similar logic if gestureRecognizerShouldBegin: does not suit.
I have a UIView with 4 buttons on it and another UIView on top of the buttons view. The top most view contains a UIImageView with a UITapGestureRecognizer on it.
The behavoir I am trying to create is that when the user taps the UIImageView it toggles between being small in the bottom right hand corner of the screen and animating to become larger. When it is large I want the buttons on the bottom view to be disabled and when it is small and in the bottom right hand corner I want the touches to be passed through to the buttons and for them to work as normal. I am almost there but I cannot get the touches to pass through to the buttons unless I disable the UserInteractions of the top view.
I have this in my initWithFrame: of the top view:
// Add a gesture recognizer to the image view
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageTapped:)];
tapGestureRecognizer.cancelsTouchesInView = NO;
[imageView addGestureRecognizer:tapGestureRecognizer];
[tapGestureRecognizer release];
and I this is my imageTapped: method:
- (void) imageTapped:(UITapGestureRecognizer *) gestureRecognizer {
// Toggle between expanding and contracting the image
if (expanded) {
[self contractAnimated:YES];
expanded = NO;
gestureRecognizer.cancelsTouchesInView = NO;
self.userInteractionEnabled = NO;
self.exclusiveTouch = NO;
}
else {
[self expandAnimated:YES];
expanded = YES;
gestureRecognizer.cancelsTouchesInView = NO;
self.userInteractionEnabled = YES;
self.exclusiveTouch = YES;
}
}
With the above code, when the image is large the buttons are inactive, when I touch the image it shrinks and the buttons become active. However, the small image doesn't receive the touches and therefore wont expand.
If I set self.userInteractionEnabled = YES in both cases, then the image expands and contracts when touched but the buttons never receive touches and act as though disabled.
Is there away to get the image to expand and contract when touched but for the buttons underneath to only receive touches if the image is in its contracted state? Am I doing something stupid here and missing something obvious?
I am going absolutely mad trying to get this to work so any help would be appreciated,
Dave
UPDATE:
For further testing I overrode the touchesBegan: and touchesCancelled: methods and called their super implementations on my view containing the UIImageView. With the code above, the touchesCancelled: is never called and the touchesBegan: is always called.
So it would appear that the view is getting the touches, they are just not passed to the view underneath.
UPDATE
Is this because of the way the responder chain works? My view hierarchy looks like this:
VC - View1
-View2
-imageView1 (has tapGestureRecogniser)
-imageView2
-View3
-button1
-button2
I think the OS first does a hitTest as says View2 is in front so should get all the touches and these are never passed on to View3 unless userInteractions is set to NO for View2, in which case the imageView1 is also prevented from receiving touches. Is this how it works and is there a way for View2 to pass through it's touches to View3?
The UIGestureRecognizer is a red herring I think. In the end to solve this I overrode the pointInside:withEvent: method of my UIView:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
BOOL pointInside = NO;
if (CGRectContainsPoint(imageView.frame, point) || expanded) pointInside = YES;
return pointInside;
}
This causes the view to trap all touches if you touch either the imageView or if its expanded flag is set. If it is not expanded then only trap the touches if they are on the imageView.
By returning NO, the top level VC's View queries the rest of its view hierarchy looking for a hit.
Select your View in Storyboard or XIB and...
Or in Swift
view.isUserInteractionEnabled = false
Look into the UIGestureRecognizerDelegate Protocol. Specifically, gestureRecognizer:shouldReceiveTouch:
You'll want to make each UIGestureRecognizer a property of your UIViewController,
// .h
#property (nonatomic, strong) UITapGestureRecognizer *lowerTap;
// .m
#synthesize lowerTap;
// When you are adding the gesture recognizer to the image view
self.lowerTap = tapGestureRecognizer
Make sure you make your UIViewController a delegate,
[self.lowerTap setDelegate: self];
Then, you'd have something like this,
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (expanded && gestureRecognizer == self.lowerTap) {
return NO;
}
else {
return YES;
}
}
Of course, this isn't exact code. But this is the general pattern you'd want to follow.
I have a another solution. I have two views, let's call them CustomSubView that were overlapping and they should both receive the touches. So I have a view controller and a custom UIView class, lets call it ViewControllerView that I set in interface builder, then I added the two views that should receive the touches to that view.
So I intercepted the touches in ViewControllerView by overwriting hitTest:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return self;
}
Then I overwrote in ViewControllerView:
- (void)touchesBegan:(NSSet *)touches
withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
for (UIView *subview in [self.subviews reverseObjectEnumerator])
{
if ([subview isKindOfClass:[CustomSubView class]])
{
[subview touchesBegan:touches withEvent:event];
}
}
}
Do the exact same with touchesMoved touchesEnded and touchesCancelled.
#Magic Bullet Dave's solution but in Swift
Swift 3
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
var pointInside = false
if commentTextField.frame.contains(point) {
pointInside = true
} else {
commentTextField.resignFirstResponder()
}
return pointInside
}
I use it in my CameraOverlayView for ImagePickerViewController.cameraOverlay to give user ability to comment while taking new photo
Pretty simple question, perhaps not so simple answer though:
I've got a clear view which needs to receive touches. Underneath this is a UIButton, which I also want to receive touches (for reasons I won't go into, it has to be underneath). In the case where the button is pressed, I don't want the clear view to receive the touches.
How can I do this?
EDIT:
Final Solution:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
for (UIView * view in self.subviews)
{
if ([view isKindOfClass:[UIButton class]]) {
CGPoint pointInButton = [view convertPoint:point fromView:self];
if ([view pointInside:pointInButton withEvent:event]) {
return view;
}
}
}
return [super hitTest:point withEvent:event];
}
Give the clear view a reference to the UIButton. Override the clear view's pointInside:withEvent: method. In your override, check whether the point is inside the button (by sending pointInside:withEvent: to the button). If the point is in the button, return NO. If the point is outside the button, return [super pointInside:point withEvent:event].
I have a main window xib on an iPad. The main window has tap gesture recognizers that show/hide a toolbar when the user taps the screen. There is also a web view in the main window.
How would I go about canceling the gestures if the user clicks a link in the web view? I don't want the toolbar to toggle if they hit a link.
Thanks
You need to check if the view should receive the touch implementing:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// test if our control subview is on-screen
if (self.view.superview != nil) {
if ([touch.view isKindOfClass:[UIWebView class]]) {
// we touched our UIWebView
return NO; // ignore the touch
}
}
return YES; // handle the touch
}
If you want to use the UITapGestureRecognizer you need to subclass the UIWebView as explained here: https://github.com/psychs/iphone-samples/tree/4028ab78af92ab17465338575b78ed80310a613f/WebViewTappingHack