iPhone SDK - UIButton on a UIScrollView touch interactions - iphone

I have a UIScrollView which is doing some custom pinch zooming. To do this I subclassed UIScrollView, the overwrote the touch methods touchesBegan, touchesMoved, and touchesEnded. Everything works well and as expected.
My problem comes when I try to add a series of UIView subviews, I can only detect taps on my UIScrollView when the UIView UserInteractions is set to NO. I would like to be able to continue to detect two finger taps on my UIScrollView, AND a single finger tap on any of my UIView subview.
Is this possible?
I've tried countless number of ways with little help. Does anyone have any experience in this?
Cheers,
Brett

Apple's documentation for UIScrollView explains how it does it:
it temporarily intercepts a touch-down event by starting a timer and, before the timer fires, seeing if the touching finger makes any movement. If the timer fires without a significant change in position, the scroll view sends tracking events to the touched subview of the content view. If the user then drags their finger far enough before the timer elapses, the scroll view cancels any tracking in the subview and performs the scrolling itself.
There are a couple of methods for interception given as answers to this question: How to make a superview intercept button touch events?

Any UIView with userInteractionEnabled will block touches from reaching views under it. You may have to rethink how you are structuring your layout. Or subclass the UIView to change how it's handling touches.

Related

How to cancel touches exactly like UIScrollView?

I created an app where you can drag views around. I needed special behavior that UIScrollView was not a good fit for. But now the problem is that some of these views respond to touch events and when I drag them, I must cancel touches.
UIScrollView has the ability to cancel touches in it's subviews so that when the user touches down on a button and then begins scrolling, the scroll view would cancel the touches in the button as soon as the user scrolled enough, so that the button does not trigger an action.
One way to do it would be to subclass each and every subview and work with boolean flags in all touch handling methods but that would be very tedious and dirty.
Is there a better way through the API to cancel touches in subviews just like UIScrollView does?
It cancels the touches through the UIGestureRecognizer API. UIScrollView actually uses UIPanGestureRecognizer underneath.
http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIGestureRecognizer_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40009279-CH1-SW9
UIGestureRecognizer also has the ability to delay touches start being sent, and touches ended.
#property(nonatomic) BOOL delaysTouchesBegan
#property(nonatomic) BOOL delaysTouchesEnded
To perform dragging gestures I use UIPanGestureRecognizer now, like UIScrollView. Doing this will let you have all it's little tricks.

Why does touchesBegan stop working when UIImageView in placed inside a UIScrollView?

UIView -> UIImageView
I know I have things somewhat working ok since I can tap on my UIImageView and see an NSLog() statement in my touchesBegan method.
.
UIView -> UIScrollView -> UIImageView
I drag that same UIImageView into a UIScrollView and touchesBegan no longer gets called when I tap on my UIImageView. (I haven't changed anything else. All the same connections, methods, and code remains unchanged.)
Why does touchesBegan no longer work? And what can I do to get it working again?
Add uitapgesture to get event
Code is
UITapGestureRecognizer *ges11=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(Handeltap:)];
[imagename addGestureRecognizer:ges11];
Create one action name "Handeltap" U will get called there.
by default UIImageView don't handle user gestures.
set UIImageView instance's userInteractionEnabled to YES
Have a look at the documentation for UIScrollView.
Because a scroll view has no scroll bars, it must know whether a touch signals an intent to scroll versus an intent to track a subview in the content. To make this determination, it temporarily intercepts a touch-down event by starting a timer and, before the timer fires, seeing if the touching finger makes any movement. If the timer fires without a significant change in position, the scroll view sends tracking events to the touched subview of the content view. If the user then drags their finger far enough before the timer elapses, the scroll view cancels any tracking in the subview and performs the scrolling itself. Subclasses can override the touchesShouldBegin:withEvent:inContentView:, pagingEnabled, and touchesShouldCancelInContentView: methods (which are called by the scroll view) to affect how the scroll view handles scrolling gestures.
I'd also recommend reading the Scroll View Programming Guide.

touchesEnded: not detecting end of touch properly?

I'm using the touchesEnded: method to do some work when I lift a finger off my UIScrollView, but my problem (and i've confirmed using NSLog) is that the touchesEnded: method seems to only get called when I tap on my scroll view and not when I touch and hold/slide my finger and then let go?
Is there another method I need to use? (btw i'm calling super as well)
I need a way to do stuff as soon as the user removes their fingers off the view
When you simply tap, the scroll view will pass touches through to its subviews. But if you start dragging, the scrollview will send a touchesCancelled message to the subview and process the touches itself. Check out the methods on UIScrollViewDelegate - there's probably something there you can use.
Alternatively, UIScrollView has a property canCancelContentTouches. If you turn that off, its subviews will always receive touches, but of course then the scroll view won't scroll.

How to filter touch events for a UIScrollView?

I have a view that displays a PDF. It should be zoomable, so I also created a UIScrollView, and in its delegate I implemented viewForZoomingInScrollView to return the PDF view. So far so good.
However, when the user reaches the edge of a zoomed PDF page, I'd like to flip to the next page. Sounds easy, yet I can't seem to figure out how to do it.
I've tried some different approaches:
Using scrollViewDidScroll to detect if scrolling has reached the edge. The problem here is that if zoomScale is 1, and therefore scrolling is not possible, then this function is never called. But the UIScrollView still swallows all touch events, so I also can't detect reaching the edge in touchesMoved. Setting canCancelContentTouches to NO when not zoomed is not an option, as that would also prevent zooming in.
Subclassing UIScrollView, and forwarding some of the touch events to the next responder. Unfortunately when UIScrollView detects a drag operation and cancels the touch, touchesMoved and touchesEnded are not called even for the UIScrollView subclass anymore. Again, setting canCancelContentTouches to NO is not good, as that would also prevent some desired UIScrollView functionality.
Creating a transparent view on top of the scroll view (as a sibling of it), so that this view gets all touch events first, and then forwarding some of the touches to the scroll view. Unfortunately the scroll view doesn't respond to these calls.
I can't use touchesShouldCancelInContentView, becasue that doesn't get the actual touches as an argument, and whether or not I want the scroll view to handle the touch event also depends on the properties of the touch event itself (eg. a touch movement in a direction in which we're already at the edge should not be cancelled by the scroll view, but a movement in the other direction could be).
Looks like whatever UIScrollView is doing is not initiated from touchesBegan / touchesMoved, but instead it gets some notifications about the touches way before that. Possibly in some undocumented way that I can't intercept, nor reproduce.
So is there any way to get notified about all touch movements done over a UIScrollView, while still being able to use (when certain conditions apply) the UIScrollView for zooming and scrolling?
Ok, so here's what I did in the end:
Leaving all scrolling and zooming up to UIScrollView, and handling page turning in the UIScrollViewDelegate's scrollViewDidEndDragging:willDecelerate: is almost a solution, except that this function is never called if the whole content is on-screen, so dragging / scrolling is not possible.
Swipes in this case are handled in a ViewController's touchesBegan / touchesEnded functions, but for this to work, we need to make sure that the UIScrollView does not cancel these events. However, in other cases the UIScrollView should be able to cancel touches so that it can do zooming and scrolling.
The UIScrollView should be able to cancel touches if:
Scrolling is possible (and needed) because the whole content doesn't fit on screen (zoomScale > 1 in my case),
OR
The user touched the screen with two fingers, so that zooming in and out works.
When scrolling is not possible, and the user single-touched the screen, then touches should not be cancelled, and touch events should be forwarded to the view controller.
So I created a UIScrollView subclass.
This subclass has a property pointing to the ViewController.
Using the touchesXXX methods I keep track of the current touch count.
I forward all touch events to the ViewController.
And finally, I've overridden touchesShouldCancelInContentView:, and return NO when zoomScale <= 1 and touchCount == 1.

Scroll view touch detection

I have a problem in the touchesMoved handler with a view that is added onto a UIScrollView. I add a number of labels to the scroll view. Each of these labels contain some text and, on swiping my finger on the labels, I have to play a specific file for that text.
If I just add the view onto the window directly, I get all of the touch events in touchesMoved without any problem. When I add my view onto the UIScrollView and then add this to the window, there is some lag in the touchesMoved handler. I am not getting continuous touch points in touchesMoved as with the normal view. As a result, while swiping the finger from the view, it happens that some labels are missed.
Is the problem due to scroll view? The same code runs perfectly in normal conditions (without a scroll view).
Does anyone have any solution to this?
UIScrollView sets a timer on touchDown to be able to know if it should handle scrolling or if it should pass the events on to subviews.
There is a property on UIScrollView for controlling this behaviour:
#property(nonatomic) BOOL delaysContentTouches