UIPicker within UIScrollView - Ignore drags/moves but capture touches - ios

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

Related

Disable touching view through subView

I'm trying to make my first game using SpriteKit + Swift.
The problem I'm trying to solve right now is that if I add to my main SKView a SUBVIEW without any buttons just with a background color and size and touch it, my main view handles this touch like there is no subview at all.
So the subView of type UIView like doesn't exist for UITapGestureRecognizer of parent view. And the only way I found to solve it, is to put a subView-sized button on the subview without text and handler. But this way looks creepy...
It could be that your subView (or one of its parents views) has the userInteractionEnabled set to false. UIView has the userInteractionEnabled set to true by default, but for example UIImageView (which is a subclass of UIView) has the userInteractionEnabled set to false by default.
When set to NO, user events—such as touch and keyboard—intended for
the view are ignored and removed from the event queue. When set to
YES, events are delivered to the view normally. The default value of
this property is YES.
(...)
Note
Some UIKit subclasses override this property and return a different
default value. See the documentation for that class to determine if it
returns a different value.
See Apple docs for more info.
It could be not your subView by itself, but one of its parents views too, because this setting is propagated down.
If set to NO, this view (along with its subviews) is excluded from
receiving touches. Touches on this view or one of its subviews "fall
through" to a view behind it.
source: Programming iOS by Matt Neuburg
For more, check On iOS, if a superview's userInteractionEnabled is NO, then all subviews are disabled as well?

How can I disable "slingshot" action in iOS gestures?

I'm using a UIPanGesture to move a UITextView and a UIScrollView around on my screen. I only want them to move exactly where they are dragged. But it responds to a "pull down like a slingshot" or a swipe sort of gesture that sends the objects flying across the screen, out of view, and then they slowly drift back into their places within set boundaries. How can I disable these specific gestures? I only want a direct drag and place, no flying across the screen with a flick, swipe, or slingshot.
I'm writing in Objective-C...
Try setting the view that is the one moving, sounds like your scrollView, to scrollView.bounces = NO. Should be one or more scrollViews (or class that inherits from UIScrollView. bounce property creating that effect, the default for this is YES, here is apple docs on UIScrollView's bounce property.
-bounces
Property
A Boolean value that controls whether the scroll view bounces past the edge of content and back again.
Declaration
SWIFT
var bounces: Bool
OBJECTIVE-C
#property(nonatomic) BOOL bounces
Discussion
If the value of this property is YES, the scroll view bounces when it encounters a boundary of the content. Bouncing visually indicates that scrolling has reached an edge of the content. If the value is NO, scrolling stops immediately at the content boundary without bouncing. The default value is YES.
Availability
Available in iOS 2.0 and later.

How to handle tap out of custom view frame

I have a custom view with custom popup on it (added as subview).
When I'm opening this popup it's frame goes out of parent view frame.
And I can't handle user interaction on the outside popup view.
How can I fix it?
I thought about this plan:
1. Add custom view on superview;
2. Add custom popup on superview (right the position where it should be on custom view)
But i think it isn't right way.
Any suggestions?
To be honest I haven't tried it yet, but you might have luck with adding a custom view on the superview and overriding pointInside:withEvent:.
I'm thinking the superview is recognizing that the tap doesn't occur inside its own frame, so it won't even check its subviews. But if you override pointInside:withEvent: you can check that the tap location is inside the subviews frame regardless of whether it's inside the superview's frame.
If that doesn't work, you might have to override hitTest:withEvent: also (or instead of). I can try to work out an example if you need more direction than that.
This Technical Q&A might help you out https://developer.apple.com/library/ios/qa/qa2013/qa1812.html
What you mention is actually the right approach: you should have a superview (it can be transparent (clear color)) that contains both the custom view and the popup inside of its frame.
On iOS, touch events are sent to a receiver at a low level in your visual hierarchy (I believe this is your Window to be specific). This receiver calls HitTest on its subviews to figure out which one to forward the event to. This happens recursively until the HitTest fails on all of a view's subviews. Then the parent of those subviews handles (or doesn't handle) the touch event.
But before the HitTest implementation even starts calling HitTest on the subviews, it calls PointInside on the superview. If PointInside returns false for the touch point, then HitTest returns null.
So overriding PointInside on the class that you call SuperView may solve the problem you're facing, but not necessarily. You may need to override PointInside on the superview of SuperView, and then on that view's superview, and so on. This is not a good solution, it is brittle and hacky. Overriding PointInside does have its uses, but this isn't one.
So try to keep all of your views within the bounds of their superview, even if you have to make a transparent superview that has no other reason to exist other than to contain other views. That's fine.

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

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];

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.

Resources