Zoomable UIScrollView with UIButtons capturing touches - iphone

I have a UIScrollView and I'm implementing the viewForZoomingInScrollView: delegate method which returns a UIImage which the user can zoom and pan. I've also got some UIButtons as sub views of the UIScrollView which I'm using as annotations, like Google Maps.
The problem I'm having is that a lot of the UIImage can be obscured by the UIButtons when zoomed right out. When trying to pinch to zoom the UIButtons are receiving the touch event instead and the zoom is not happening. You end up having to carefully place your fingers in clear space to zoom.
I note the Google Maps app seems to work ok when there are lots of annotation views, you can still pinch.
I guess I need to priorities the touches, the UIScrollView needs to respond to pinches and pans, while the buttons just taps.
Anyone have experience of this?

I had this exact issue, and it was kind of weird. It wasn't due to any gesture recognizer on the button, it was the UIScrollView's pinch gesture recognizer that was being forwarded to the UIButton for some reason. Also, the UIButton was responding to certain UIGestureRecognizerDelegate calls (like -gestureRecognizerShouldBegin:) but not others (like -gestureRecognizer:shouldReceiveTouch:). It's like the UIScrollView was selectively forwarding some delegate calls to the UIButton and some not.
I finally found a very easy way to solve this issue:
yourScrollView.pinchGestureRecognizer.delaysTouchesBegan = YES;
yourScrollView.pinchGestureRecognizer.delaysTouchesEnded = YES;
Luckily, the default pinch gesture recognizer on UIScrollView's are publicly accessible, and the two delaysTouches property are NO by default. When you set them to YES, if the gesture recognizer could possibly or does recognize a pinch gesture even when it starts on top of a UIButton, it won't forward those touches to the UIButton, and the UIButtons will no longer interfere with the UIScrollView's zooming.

try this
UIGestureRecognizer* tapRecognizer = nil;
for (UIGestureRecognizer* recognizer in yourButton) {
if ( ![recognizer isKindOfClass:[UITapGestureRecognizer class]] ) {
[yourButton removeGestureRecognizer:recognizer];
break;
}
}
so you remove all the gesture recognizers from the button different from UITapGestureRecognizer

take a look at UIView's hitTest:withEvent: method. Inside that method youll need to check for which view you want to return. The view you return will be the one recieving the touches. for example, you can subclass the button and override that method to return the ImageView for your particular scenario.

I fixed this problem in a different way, but this might not be your case.
My buttons were added on an image view, the image view being added by itself to another container view. In the:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
... method, I returned the image view - bad. Changing to return my very first container view in hierarchy fixed the touches.

Related

Gesture is not working when used on transparent UIView when subclassed to detect objects behind it

Hi all,
I am having some issue with detecting touches. Please refer image where yellow and brown are my UIView which is subclassed to detect the transparent touches. I have added three gesture recognizers pan, tap and rotation on that UIViews, but when I subclass UIView and override the - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event method to detect all the Imageviews added, gesture recognizers stop working outside the ImageView. And if I am not using subclass then it's unable to detect touches for ImageView on the yellow UIView. I tried the solution from this link
Forwarding UIGesture to views behind
though it is not the same as my requirement, but it didn't help! Any help/hint is appreciated! Thanks in advance.
I have resolved the issue myself. I achieved the my target by applying gesture recognizers on superview not separate for each uiimageview or the colored uiviews. I have added uiviews on superview and now gesture is getting applied to imageview also. And also changed the code in gesture actions so that gesture will get applied to touched imageview and not on superview.
Try implementing gestureRecognizerShould begin in your subclass, test to see if the class of the recognizer is those classes [gestureRecognizer class] == [UITapGestureRecognizer class] for example, and return yes if it is.
http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/gestureRecognizerShouldBegin:
The only way I've found to fix this is to add your stack of views as children to a UIView (A). It should be the same size as your transparent view/ all of the child views on your stack. You should implement the gesture recogniser in the view controller if and connect (A) as the iboutlet (just drag and drop a gesture recogniser onto A in interface builder for example).
(View Controller)
|->(A)
|->(1) Need gesture
|->(2) Need another gesture
|->(3) Transparent (messing everything up)
Connect the gesture handlers to the appropriate children of A who need to handle them.
For View Controller
#interface AUIViewController : UIViewController <bla, bla, UIGestureRecognizerDelegate >
For (1)
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer{/*yada yada*/}
In my opinion, this is an apple bug.
My answer is pretty much this but it is a work around for what is in my opinion, a bug.
P.S, sorry I didn't fix this without using interface builder. Hope its still helpful

UIGestureRecognizer not receiving touches during UIScrollView deceleration

I've been trying to create a UITextView subclass that handles swiping away the keyboard like in the Message.app.
I have a UIPanGestureRecognizer added to the keyWindow of my app, and the gesture delegate is configured for shouldRecognizeSimultaneouslyWithGestureRecognizer. Everything works fine except for when the UIScrollView is decelerating, during that phase it is possible to pan without the touches being registered.
You can take a look at a very simple github sample project here.
I have tried adding the UIPanGestureRecognizer directly to the viewController.view and to the scrollView, same issue occurs. I have also tried setting scrollView.panGestureRecognizer requireGestureRecognizerToFail: with my UITextView subclass gesture recognizer.
Any ideas as to why this may be happening?
Instead of creating a new UIPanGestureRecognizer, maybe you could use the one on the UIScrollView, and add your own pan logic to that gesture recognizer with - (void)addTarget:(id)target action:(SEL)action.

UITapGestureRecognizer interefering with UIPinchGestureRecognizer

I have a view controller that attaches a UITapGestureRecognizer to its main UIView, and enables the user to tap the screen to make the status, navigation, and tool bars reappear / disappear (like the photos app). I also have a UIScrollView attached to the main UIView which implements zooming and thus has its own UIPinchGestureRecognizer and UIPanGestureRecognizer to implement scrolling and zooming.
The problem I'm having, is when going to zoom / scroll the UIScrollView, it's very sensitive to picking up the UITapGestureRecognizer which is attached to the main UIView. It seems a lot of the time the UITapGestureRecognizer gets triggered when it shouldn't. Anyone have any ideas on how to fix this for versions of ios below 5.0? Is there someway I can override the simultaneous gestures delegate method for the UIGestureRecognizerDelegate in the UIScrollView and prevent the UITapGestureRecognizer from firing during other gestures?
Use the requireGestureRecognizerToFail: method.
[tapGestureRecognizer requireGestureRecognizerToFail:pinchGestureRecognizer];
This call tells the tap recognizer to wait for the pinch recognizer to fail.

UIGestureRecognizers for view in a scroll view

I'm using a few UIGestureRecognizers to pan, rotate and scale a view, which resides inside a scroll view.
My problem is that sometimes the scroll view eats the touches before the gesture recognizers do, so when this happens I end up zooming the scroll view instead of dragging the view. (It doesn't happen all the time. I still can't describe how to reproduce this behavior).
I'm pretty sure this can be solved in some way. For example MPMoviePlayerController doesn't have this problem: in fact, you can put it in a scroll view, and when you pinch it, it works just fine (i.e. it doesn't zoom the outer scroll view too). Does anyone know how does MPMoviePlayerController achieve this?
I've already searched for answers on SO, with no results. Thanks!
Disclaimer: This is just an idea, I haven't tested this.
UIGestureRecognizerDelegate defines gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:. You could try implementing this on your gesture recognizer delegate. This won't prevent the scroll view from zooming. To prevent zooming you could try temporary setting maximumZoomScale minimumZoomScale to zoomScale in the recognizer action method.
At a guess your rotation is conflicting with zoom - both probably require two touches?
In which case try creating a one touch rotation.
Or disable zooming on the scrollview and implement zooming/scaling via pinch gesture recognizer.
see:
http://www.icodeblog.com/2010/10/14/working-with-uigesturerecognizers/
Could you not set scrollEnabled(NO) on your UIScrollView?
http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIScrollView_Class/Reference/UIScrollView.html
If the value of this property is YES , scrolling is enabled, and if it
is NO , scrolling is disabled. The default is YES.
When scrolling is disabled, the scroll view does not accept touch
events; it forwards them up the responder chain.
Things like zoomToRect:animated: don't say anything about being disabled by this flag; I'd assume that would keep working when you pan/zoom/scroll in response to your gesture events.
I think you can differentiate zoom in/zoom out and scrolling functionality by identify number of tap option on UIScrolView. Here i do same things as you want. you may try it.
- (void)viewDidLoad {
[super viewDidLoad];
imgview=[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"2.png"]];
view1=[[UIView alloc]initWithFrame:CGRectMake(0, 0, 300, 400)];
view1.backgroundColor=[UIColor greenColor];
[view1 addSubview:imgview];
objscrollview.contentMode=UIViewContentModeScaleToFill;
objscrollview.contentSize=CGSizeMake(300, 400);
objscrollview.minimumZoomScale=1;
objscrollview.maximumZoomScale=10;
[objscrollview addSubview:view1];
UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap)];
singleFingerTap.numberOfTapsRequired = 1;
[objscrollview addGestureRecognizer:singleFingerTap];
[singleFingerTap release];
}
//Single tap on scrollview call below method
-(void)handleSingleTap
{
NSLog(#"Singletap identify");
}
//While perform zoom in/zoom out action on scroll view it's delegate method call
//In this method, we are return view that want to zoom in/Zoom out..
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView1
{
NSLog(#"hi++++++++++++++++++");
return imgview;
}

How to pass a 'tap' to UIButton that is underneath UIView with UISwipeGestureRecognizer?

I have a UIButton underneath a (transparent) UIView. The UIView above has a UISwipeGestureRecognizer added to it, and that is its only purpose - to detect certain swipe gestures. I want all other touches to be ignored by that UIView, and passed to other views (such as my UIButton underneath). Currently, the UIView above seems to be detecting the tap (for example), doing nothing (as it should be), and not letting the UIButton underneath get a chance to respond.
I would prefer not to implement my own swipe recognizer, if possible. Any solutions / advice? I basically just want to know how to tell a UIView to pay attention to only a certain type of added gesture recognizer, and ignore (and thus let through to views behind) all other touches.
Have you set:
mySwipeGesture.cancelsTouchesInView = NO;
to allow the touches to be sent to the view hierarchy as well as the gesture?
Additionally, ensure that the view on top is:
theTransparentView.opaque = NO;
theTransparentView.userInteractionEnabled = YES;
I've had pretty good success attaching gestures to the parent view without needing to create a transparent subview on top for the gesture. Are you sure you need to do that?
I must have just been in a funk yesterday - I woke up with a simple solution today. Add the UISwipeGesture to a view which is a superview to both the UIView and the UIButton. Then, when processing those swipes, figure out where the swipe originated, and whether that point is in the frame of where I used to have the UIView. (As I mentioned, the only reason for the existence of the UIView was to define a target area for these swipe gestures.)
Can't you put your button on top of the view and add gesture recognisers to that button too?
In the end, your UIButton inherits form UIView via UIControl. Therefore there is practically nothing that you could do with a view but not with a button.
In my case, I fixed it by not using a button, but rather a UITapGestureRecognizer. My pan gesture recognizer was added to the background view, and the tap gesture was added to a view above it.