I've got a custom UIView as a subview in a UIScrollView.
When selected, the user can resize the subview by pinching anywhere on the screen (the sub view is fairly small).
When deselected, I would like this pinch gesture to be passed on to the UIScrollView so that it can handle it as it normally does.
Here is what I'm trying.
- (IBAction)pinchInView:(UIPinchGestureRecognizer *)sender {
if (self.item.isSelected)
{
if ((sender.state == UIGestureRecognizerStateChanged) || (sender.state == UIGestureRecognizerStateEnded))
{
[self.item resizeWithScaleFactor:sender.scale];
}
} else
{
[self.scrollView setZoomScale:self.scrollView.zoomScale *= sender.scale];
}
sender.scale = 1;
}
While it does work, it seems an awkward way to go about this.
Is there a way to let the UIScrollView handle its own zooming?
I'm pretty much using the same approach with pan gestures as well.
If there is any way to make this less awkward, I'd really appreciate your help.
Provide a Delegate to the Gestures which You have added to subview. Implement following Method in delegate
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecoznizer shouldReceiveTouch:(UITouch *)touch{
if(isSelected == true)
return YES;
else
return NO;
}
Implement the below delegate method
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
Related
I am having a View where the TapGestureRecognizer is used. I am using the TapGestureRecognizer for the Single and double tap event. So far so good. Now I have added a ImageView on Top of the View , the image view frame is imageView.frame=CGRectMake(50,290,205,100);
Now wherever I am tapping the View , my #selectors being called. I want to skip the tap events only for the ImageView . How to do it ?
I tried using the
if(recognizer.state == UIGestureRecognizerStateRecognized)
{
CGPoint point = [recognizer locationInView:recognizer.view];
}
You have to implement this check
if(!CGRectContainsPoint(image.view.frame, point))
{
//Complete your Work
}
Do this...I hope this will help you...
Whenever you tap on screen this delegate method will call..
in this method please check touch and gestureRecognizer will give some data regarding your tapping.....
Based on that you can proceed.....
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
NSLog(#"%#",touch.description);
NSLog(#"%#",gestureRecognizer.description);
}
You need to check for the touch point ,
if(!CGRectContainsPoint(image.view.frame, point))
{
//Do you work here
}
Do this:
if(recognizer.state == UIGestureRecognizerStateRecognized)
{
CGPoint point = [recognizer locationInView:recognizer.view];
if(CGRectContainsPoint(imageView.frame,point)
{
//igonre
}
else
{
// continue
}
}
I'm working in iOS5, and apparently I should be able to control or at least subdue the UIScrollView's internal pinch gesture recognizer using scrollView.pinchGestureRecognizer.
However, my code does not seem to work. The recognizer does not treat my class as a delegate and does not wait for my rotation gesture recognizer to fail. What can I do to make the rotation gesture a priority, after which the pinch would be considered?
More precisely, the issue that I'm running in is that the view that is being rotated and zoomed at the same time "flies off the screen" towards the bottom left corner, never to be seen again.
-(void)setup scrollViews
{
[tempScrollView.pinchGestureRecognizer requireGestureRecognizerToFail:rotationRecognizer];
tempScrollView.pinchGestureRecognizer.delegate = self;
tempScrollView.maximumZoomScale = 4.0;
tempScrollView.minimumZoomScale = 0.25;
//
tempScrollView.delegate = self;
}
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if([gestureRecognizer isEqual:rotationRecognizer])
{
NSLog(#"Rotation gesture");
}else {
NSLog(#"Other gesture: %#", [gestureRecognizer class]);
}
return YES;
}
- (IBAction)rotateView:(id)sender {
if([sender isKindOfClass:[UIRotationGestureRecognizer class]])
{
UIRotationGestureRecognizer* recognizer = sender;
float recognizerRotation = [recognizer rotation];
CGAffineTransform transform = CGAffineTransformMakeRotation(recognizerRotation);
activeView.transform = transform;
}
}
As far as I know, the pinchGestureRecognizer in UIScrollView is read only.
However, you might try to subclass UIScrollView and override the method addGestureRecognizer: to disable the pinchGestureRecognizer, then add your own custom pinchGestureRecognizer.
I'm using an UIPageViewController in my application and I wanted to have a few UIButtons inside it, sort of like a menu. The problem I have is that when I put an UIButton (or any other interactive element) near the edges of the screen and tap it, instead of the UIButton action being applied, what happens is that the page changes (because the tap on the edge of the screen changes the page on the UIPageViewController). I'd like to know if there's a way to make it so that the UIButton has higher priority than the UIPageViewController so that when I tap the button, it applies the appropriate action instead of changing the page.
I came here with the same problem. Split’s link has the answer.
Make your root view controller the delegate of each of the UIPageViewController’s gesture recognizers, then prevent touches from being delivered if they occur inside any UIControl:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return ([touch.view isKindOfClass:[UIControl class]] == NO);
}
UIPageViewController has two UIGestureRecognizers. You can access them via gestureRecognizers property. Determine which one is UITapGestureRecognizer and then use this. Hope this helps.
For people that just want to copy/paste code, here is mine :
// I don't want the tap on borders to change the page
-(void) desactivatePageChangerGesture {
for (UIGestureRecognizer* gestureRecognizer in self.pageViewController.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
gestureRecognizer.enabled = NO;
}
}
}
Just call this function after the UIPageViewController creation.
I had this same problem, and was unsure how to handle the UIGestureRecognizer delegate methods. This short example assumes you are using the "Page Based Application" project type in Xcode 4. Here is what I did:
In RootViewController.h, I made sure to announce that RootViewController would handle the UIGestureRecognizerDelegate protocol:
#interface RootViewController : UIViewController <UIPageViewControllerDelegate, UIGestureRecognizerDelegate>
In RootViewController.m, I assigned RootViewController as the delegate for the UITapGestureRecognizer. This is done at the end of the viewDidLoad method. I did this by iterating over each gestureRecognizer to see which one was the UITapGestureRecognizer.
NSEnumerator *gestureLoop = [self.view.gestureRecognizers objectEnumerator];
id gestureRecognizer;
while (gestureRecognizer = [gestureLoop nextObject]) {
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
[(UITapGestureRecognizer *)gestureRecognizer setDelegate:self];
}
}
Finally, I added the gestureRecognizer:shouldReceiveTouch method to the bottom of RootViewController.m (This is copied directly from Split's link):
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([touch.view isKindOfClass:[UIControl class]]) {
// we touched a button, slider, or other UIControl
return NO; // ignore the touch
}
return YES; // handle the touch
}
Comment out these line from your code
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
or use UIGestureRecognizer as told by Split
Hope this will help you
OLD ANSWER: If your UIPageViewController has a transitionStyle of UIPageViewControllerTransitionStyleScroll and you are in iOS 6.0+, then you can't use the gestureRecognizer:shouldReceiveTouch: method, because there is no way to set the delegate to self on the gestureRecognizers since pageViewController.gestureRecognizers will return nil. See UIPageViewController returns no Gesture Recognizers in iOS 6 for more information about that.
If you simply want to make sure your UIPageViewController passes along button touch events to a UIButton, you can use
for (UIScrollView *view in _pageViewController.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
view.delaysContentTouches = NO;
}
}
if you have a transitionStyle of UIPageViewControllerTransitionStyleScroll and you are in iOS 6.0+.
See this answer about why delaysContentTouches = NO is needed for some cases of a UIButton in a UIScrollView
UPDATE: After doing a little more research it appears that if your issue is that the UIButton click seems to only be called sometimes, then that is actually probably the desired behavior inside a UIScrollView. A UIScrollView uses the delaysContentTouches property to automatically determine if the user was trying to scroll or trying to press a button inside the scroll view. I would assume it is best to not alter this behavior to default to NO since doing so will result in an inability to scroll if the user's finger is over a button.
None of the solutions here where you intercept the UIPageViewController's tap gesture recognizers worked for me. I'm targeting iOS 8 and 9.
What worked is to override the functions touchesBegan, touchesCancelled, touchesMoved, and touchesEnded in my custom button which is a subclass of UIControl. Then I just manually send the .TouchUpInside control event if the touch began and ended within the frame of my custom button.
I didn't have to do anything special for the containing page view controller, or the view controller that contains the page view controller.
Swift 5 answer here should do the job.
pageViewController.view.subviews.compactMap({ $0 as? UIScrollView }).first?.delaysContentTouches = false
I have a bunch of UIViews like in the image below. The red/pink (semi-transparent) view is on top of the others.
Red has a UISwipeGestureRecognizer.
Green has as a UITapGestureRecognizer.
Blue has no recognizer.
A tap on the visible (bottom-left) part of Green trigger its recognizer.
A tap on the hidden parts of Green does not trigger its recognizer (Red blocks it).
That's the problem: I want Green to trigger. How can I do this?
In practice, the views may be in any order, any number and be subviews of each others etc. But the problem is the same:
How can I reliably find the uppermost view that can handle the gesture (tap or swipe)?
I tried with the code below. It neatly traverses all views, but it fails since it cannot know if the event is part of a swipe or a tap. So the method always returns the red view. If I remove the swipe-recognizer from Red, the code works correctly.
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self)
{
if (self.hasASwipeRecognizer)
return self; // What if this was a tap?
if (self.hasATapRecognizer)
return self;
else
return nil;
}
else
return hitView;
}
An alternative to adding the gesture recognizer to these views would be to add the gesture recognizers to the parent view and handle the use cases appropriately using the delegate method gestureRecognizer:shouldReceiveTouch: method.
Identify whether the particular recognizer should receive the touch and return YES. For example, if the gesture recognizer passed is a swipe recognizer then check if the touch point is within the green view and return YES. Return NO otherwise.
If there are similar gesture recognizers then I suggest that you keep a reference and verify against it.
Usage
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
CGPoint pointInView = [touch locationInView:gestureRecognizer.view];
if ( [gestureRecognizer isMemberOfClass:[UITapGestureRecognizer class]]
&& CGRectContainsPoint(self.blueView.frame, pointInView) ) {
return YES;
}
if ( [gestureRecognizer isMemberOfClass:[UISwipeGestureRecognizer class]]
&& CGRectContainsPoint(self.greenView.frame, pointInView) ) {
return YES;
}
return NO;
}
One possible solution would be to add a tap gesture recognizer to the top red view and then whenever you get the tap, check whether the tap point intersects with the green view. If so, forward the tap to that view. If not, ignore the tap.
My solutions is:
-(void)handleGesture:(UIGestureRecognizer*)gestureRecognizer {
CGPoint touchPoint = [tapGestureRecognizer locationInView:viewUnderTest];
if ([viewUnderTest pointInside:touchPoint withEvent:nil]) {
NSLog(#"Hit done in view under test");
}
}
I would like to handle the rotation gesture in my iPhone app and rotate an imageView during this. At the end of the gesture I would like to rotate to a fix position the imageView.
So ie. if I rotate the imageView from 0 radian to M_PI/2 radians, but somewhere on halfway I end with the gesture. After the end I would like to test the angle and if it close to M_PI/2 then set it to M_PI/2 otherwise to 0.
Here's my code how I tried to do this:
I create and add the recognizer to my view.
UIGestureRecognizer *recognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(gestureRecognized:)];
recognizer.delegate = self;
[self addGestureRecognizer:recognizer];
[recognizer release];
Delegate methods:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (_imageView) {
return YES;
}
return NO;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
Gesture recognized method:
- (void)gestureRecognized:(UIRotationGestureRecognizer *)recognizer {
_imageView.transform = CGAffineTransformMakeRotation(recognizer.rotation);
}
These methods are working, but here's the method how I tried to get the end of the gesture. This is not working:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"gesture end");
}
Also with the transform I have a little problem. Sometimes it is going back to the 0 radian.
Any suggestion is welcome. Thanks!
I've found the answer on an other forum. I hope this will help somebody else too.
Answer:
You can detect the end of the gesture by checking the UIRotationGestureRecognizer's state in your action method. If [gestureRecognizer state] == UIGestureRecognizerStateEnded then it's done. If something had instead caused it to be cancelled (for instance, a phone call coming in) then you'd see it end in UIGestureRecognizerStateCancelled instead.
If you want to apply the recognizer's rotation directly to your view rather than treat it as a delta from the previous location you can set the recognizer's rotation the its state is UIGestureRecognizerStateBegan, and then it will calculate offsets from that value and you can apply them directly. This just requires that you remember the rotation you last ended at, and then you can do something like [gestureRecognizer setRotation:lastAppliedRotation] when the recognizer's state is UIGestureRecognizerStateBegan.
If the gestureRecognizer recognizes the gesture, by default touchesEnded won't be called (touchesCancelled will be called instead. touchesEnded would only be called if the recognizer doesn't recognize the gesture). Set gestureRecognizer.delaysTouchesEnded to NO, and it should work.
Just in case this happens to you as well and it's the reason you're reading that question :
"ended" and "recognized" are the same value ( 3 ). So this code (in swift) won't work:
if (gesture.state == .recognized || gesture.state == .changed) {
}
else if (gesture.state == .ended) {
//do something else
//=> never called
}