how to make iOS 7 UIScrollView suppress touch events on embedded UIView - ios

Think of a UIScrollView with embedded (subview) UIViews, arranged in a column. In iOS6 and previously, the scroll view could be configured so that when you flick it with your finger, the embedded views do not receive touch events even if the initial touch is on one of the subviews; but if you touch a subview without flicking, the scroll view decides this is not a scroll action and forwards the touch events to the embedded views. This was very convenient behavior if you wanted to be able to drag/drop the embedded UIViews within the UIScrollView.
In iOS 7 the documentation indicates that setting the UIScrollView property delaysContentTouches will cause touch-down events to be delayed until the UIScrollView decides whether it's being scrolled. But in fact, this simply does not appear to work. The subview immediately receives touch events and responds to them if the scrolling touch-down event is on one of the subviews. Thus if the subview is programmed for drag/drop it starts dragging while the scroll view also scrolls.
It appears that the model for this behavior has been changed, since iOS 5/6 both did suppress touch events while deciding whether this is a scroll action. Some new methods are now available to cancel the touch-down events after the UIScrollView decides it is scrolling. But obviously this is not useful if the drag/drop code has also started moving the subview.
My question: Is there any way to prevent iOS 7 UIScrollViews from invoking low-level touch-down events on its subviews, when you initiate a scrolling action by stroking a subview?
When I say "low-level", I mean actual touch events as opposed to using gesture recognizers. I am convinced that simply setting the delaysContentTouches property to YES does not work.
This problem has totally busted some quite complex code that worked smoothly and beautifully in iOS 5 and 6; and, so far I have discovered no way to tell the UIScrollView to suppress events to its subviews until it determines whether or not it is being scrolled. The events go through, then a cancellation touch event is triggered later, after the scroll view determines it is scrolling. It looks like the underlying model has been redefined for the worse, or this is a bug. Any insights will be greatly appreciated.

Are you sure it was working on iOS5/6? Based on my experience and posts like UIScrollview delaysContentTouches issue it doesn't work.
Maybe a sample code which shows it working on iOS5/6 but not on iOS7 will help to answer your question.

This works for me:
[scrollView setCanCancelContentTouches:YES];
[scrollView setDelaysContentTouches:YES];

Related

UIView inside UIScrollView becoming touch-unresponsive with iOS 7.1

I am developing an iPad app which presents to the user a set of zoomable pages. There is a (paged) UIScrollView used for scrolling between pages, and each page consists of another UIScrollView which allows pinch&zoom, and contains as subview the actual page content which is a custom UIView subclass with CALayer drawings.
Everything was working fine as of iOS 7.0.x. Unfortunately, when running the app on iPads updated to iOS 7.1, the pages'UIView is completely unresponsive to touch events: none of the UIGestureRecognizers attached to it is ever fired, touchesBegan/Moved/Ended methods are never called, and even pinch gestures that should be captured by the parent ScrollView are not recognized anymore if they happen inside the UIView's bounds (outside of it, they work).
When i set to NO the parent Scrollviews' scrollEnabled property, the touch events properly reach the UIView but of course this is not the behavior I need.
Apple documentation doesn't say anything about changes in UIKit framework, and I also have no clue on how to debug this, since on previous iOS releases everything was working as expected.
Any idea?
Edit 31/03/2014:
I was able to do some more debug, and noted that if I omit to add to the UIView's CALayer a couple of sublayers (that come from two custom subclasses of UIView), the problem doesn't occur.
I ended up solving my issue.
What I did was to stop adding the CALayers as sub-layers to the UIVIew, and instead adding the CALayers' parent views as sub-views to the main UIView.
I still have no idea on why this is happening in 7.1, given that no changes from 7.0 was mentioned in the documentation for this topic.
Anyhow, hope this can help...

UIPicker within UIScrollView - Ignore drags/moves but capture touches

I have a rather large (in screen-estate) UIControl that I'm putting into a UIScrollView. Being a UIControl, it hijacks all touches and any drag that starts from within the control will not be reflected by the UIScrollView.
This control only needs UIControlEventTouchUpInside, so is there any way to "only" listen to that event?
My first instinct was to override hitTest:withEvent: and return the superview if it detected a drag, but that doesn't provide any information on the type of control event.
My last resort is to make it a generic UIView, but I'd rather not go down that road if possible.
EDIT:
Sorry, for clarity, I purposefully set delaysContentTouches to NO to support other features on the screen I am working on. The unintended side effects result in Joel H's second point.
Try setting both the canCancelContentTouches and delaysContentTouches properties of your UIScrollView to YES.
When the UIScrollView property canCancelContentTouches is set to YES, it should transfer any scrolling/drag touches to the scroll view and cancel whatever that touch would have been registered in the subview.
The scroll view's delaysContentTouches should be set to YES as well. This will prevent drags from being triggered as taps (because the handler fires off too soon).
UIScrollView class reference

Set "Touchable" area of UIScrollView

I have a main menu with a list of buttons. On the bottom I have a bar that the user can flick up to the top via a scrollview. Whats underneath that bar however is a uiwebview. So when the user tries to scroll in the uiwebview, the bar and webview just snaps back down. Also, the uibuttons won't work when the bar is down because the scrollview is sitting over them. How do i make the scroll only work when the user touches the bar and make the buttons on my menu work when the bar is down?
Thanks in Advance!
Where you want the scrollView to "work" (maybe when user flicks the bar up) have the following code:
scrollView.userInteractionEnabled = YES;
and wherever you don't want the scrollView to register any user actions (like when the bar is down) just do the opposite and set it to NO.
So if I understand correctly, you want the UIScrollView to respond to touches in only a certain part of the frame (the bar)?
I would suggest taking a look at one of my previous answers to a similar problem, here.
In particular, see:
This answer to the SO question Touch and pull down a view. It uses the hitTest:withEvent: method to determine whether the scrollview responds to a touch or leaves it for the view under it to respond to.
This answer to the question Drag Down UIView in iOS 5 works in a similar way, using pointInside:withEvent: to make the UIScrollView only respond to touches in a given area.
This answer to the question Event handling for iOS - how hitTest:withEvent: and pointInside:withEvent: are related? gives a good overview of how hitTest:withEvent: and pointInside:withEvent: work together.

Propagate dragging touch to UIScrollView superview

I have been looking to all the other similar topics here, using UIGestureRecognizers, using hitTest:withEvent, pointInside:withEvent: etc. but nothing seems to be ok for what I need to achieve.
Basically I have a main view (self.view of a common UIViewController) and a small rectangular UIScrollView attached onto it at the bottom: the scrollView is filled with some UIImageViews and the user can scroll it as usual.
But the user should also be able to drag one UIImageView (or a copy of it) from the UIScrollView to the main view, and, this is what I am finding really difficult, with the SAME dragging gesture, hence I need a way to:
1) Distinguish between normal horizontal scrolling gesture, which should be handled by the UIScrollView the usual way and a dragging gesture over the image view.
2) Once identified a dragging gesture, should propagate the touch to the superview, which will host a copy of the UIImageView and WITH the SAME dragging gesture continue the dragging over the main view even out of the bounds of the UIScrollView.
Please note that I know that if the UIScrollView has userInteractionEnabled = NO the touch is propagated to the subviews, but 1) I want to propagate it to the superview not the subviews, 2) the userInteractionEnabled property apparently becomes active only once the initial gesture is terminated, while I need to use a single dragging gesture.
Thank you very much for any help.
So, so far I have ended up implementing the touchesShouldBegin:withEvent:inContentView: method of my UIScrollView subclass but with delayContentTouches set to YES (default) instead of NO as #nhahtdh was suggesting.
Strangely enough even only implementing the method was sufficient for my subviews to intercept the dragging, and still my scrollview is scrolling properly, while with delayContentTouches set to NO I was not able to scroll it as all the subviews were starting to move around.
Really the credit for this is #nhahtdh, so man, if you post an answer I will accept it, thank you very much for your help.

Is it possible to remove the delay of UIButton's highlighted state inside a UIScrollView?

I have noticed a slight delay on the highlighted state of a UIButton when touched down if it is inside a UIScrollView (or a table view). Otherwise, the highlighted state is pretty much instantaneous.
I surmise this must be by-design to provide a chance for user to scroll. But it just seems like the button is unresponsive to me. Is there a way to fix this?
Indeed, it's a design choice. It needs this small time to differentiate a scroll (panGesture) from a tap. If you eliminate this delay, then the user won't be able to scroll if he places the finger on top of the button, which is not good user experience.
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.
from the UIScrollView Documentation
I wouldn't recommend disabling the delay, but if you insist, you can set it in interface builder (select the Scroll View, and on the right panel, right under "Bounces Zoom"), or using this code:
scrollView.delaysContentTouches = false

Resources