Handle tap event by subview of UIScrollView while scrolling - objective-c

I have custom UIScrollView subclass with some content views inside. In some of them I have UITapGestureRecogniser. All works fine when scroll view is not scrolling. But when it scrolling content views does not receive tap action. What is the simplest solution to handle tap action by subview while scroll view is scrolling?
Details:
MyScrollView scrolls horizontally. It contains a lot of content views (e.g. MyContentView). Each MyContentView has width about one third of MyScrollView width. So there are about 3-4 visible MyContentView elements at a moment. The main behavior of MyScrollView is to 1)make sure that after scrolling one of MyContentView elements will be at center of screen and 2)to scroll to center of MyContentView if user taps on it. So the main answer I hope to get is how to "properly" implement handling of tap action in MyContentView while MyScrollView is decelerating.
I found some same questions and answers but none of them satisfied me. The best was to implement gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: of UITapGestureRecogniser delegate. But in this case I sometimes (when I tap, make smaaaal drag and release finger so tap is steel recognizable(lets called it quasi tap)) have both tap and scroll events and it leads to bugs for me even if scroll view is not scrolling when I begin tap. When user make quasi tap my application tries to scroll to tapped MyContentView element and than immediately handle normal scrolling. It seems even more terrible, due to some other functionality start to perform after handling tap (it must not perform when normal scrolling).
I need solution where scroll view wait enough to decide it is not tap event and only then make scroll. Otherwise if tap event had recognized scroll must not happen.

You can go with the custom delegates methods as well, using #protocol. Implement those delegate methods in view controller where your UIScrollView has been added.
like in MyContentView:
In touchesBegan method,
[self.delegate contentViewTapped:self];
Now in ContainerView class where scroll view is added, implement that method:
- (void)contentViewTapped:(MyContentView *)myContentView {
NSLog (#"ContentView no: %d", myContentView.tag); // if tag has been set while adding this view to scrollview.
}
Go through the examples for #protocol.
Hope this is what you required.
Enjoy Coding :)

This is built into UIScrollView - take a look at the delaysContentTouches and canCancelContentTouches properties. This should alleviate the problem when dragging a small bit after a tap.
This is all system built-in behaviour. I would suggest sticking with what Apple has provided for the feel of your interface (how it reacts to small drags, for instance) so that your app doesn't feel out of place on a user's phone.
EDIT:
Alternatively, you could disable scrolling of your scroll view in you gesture recognizer and re-enable it once it's ended/cancelled.
Further Edit:
I don't understand - I've created a sample project that illustrates how to intercept touches in a subview of a scroll view using gesture recognizer delegate methods. Play close attention to the "Cancellable Content Touches" and "Delays Content Touches" properties of the scroll view. They're both YES for very important reasons.
You scroll view should be delaying content touches until it has determined if the user is attempting a tap, pseudo-tap (as you put it), or a pan for the scroll view. Apple has already written the functionality you're trying to build; UIScrollView will already do what you want.
The problem is that the system doesn't want a scroll view's subviews intercepting tap events while the scroll view is scrolling. To this end, it cancels touch events if it determines that the user is actually trying to pan. Setting "Delays Content Touches" enables this behaviour. Ensure it's turned on and you should be fine.

Related

NSView Absorb Clicks?

I have my standard app setup, window with lots of views. From time to time I place an NSView over the top of everything, black with some transparency, to act an a dimmer/overlay.
I need this top overlay view to absorb all clicks so that any views underneath it can't be interacted with. E.g. an NSButton under this NSView won't be clickable.
How can I do this?
I have seen -(NSView *)hitTest:(NSPoint)aPoint but I don't want to put this on every single subview with a rule to block clicks while the overlay view is present.
Override the NSView with an empty mouseDown: and the views underneath will not receive any mouse events.

scrollview, buttons events exchange

I have a scrollview where I added several buttons (dynamically, programmatically). Hence my view is totally covered with buttons. Also there are some labels
However, I observed that the uiscrollview is not scrolling when the drag starts on the button. All labels work fine. But I want this scroll to happen i.e. when drag event occurs in the uibutton, I want it to send this event to its superview (scrollview).
Please note, according to my search, subclassing the scrollview and overrriding the content touches event, or add touch began actions to uibuttons are not helpful.
How do one in general work with this event passing things when objects are added dynamically?
You have to subclass the UIScrollView, there's no other proper way, but you do not need to mess with the touch events. All you have to do is override this method
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
return YES;
}
And set the canCancelContentTouches property of the UIScrollView to YES.

Prevent UITableView from scrolling when custom subview is handling touches

In my iOS app have a UITableView which contains a custom subview in one of it's cells. This cell is an interactive view that handles touch events (touchesBegan, touchesEnded, touchesMoved) to update itself.
The problem is that when the user 'drags' up or down, the tableView catches these touches (although I don't pass the touches up the responder chain), scrolls the table and prevents the subview from working correctly. I would like to prevent the table from scrolling as long as the user is touching that particular subview.
The subview has no reference at all to the tableView.
How can I prevent the scrolling behavior of the table?
Update
Despite accepting the answer below, I ended up handling my situation differently. I handle the touch events in my custom view now, pass them up the responder chain (to the table cell), the cell handles the touch events as well, using them to enable/disable scrolling on the superview (the table).
Turning off "Cancellable Content Touches" in the UITableView solved this for me (in the UITableView attributes inspector under Scroll View / Touch). I got this from this SO question: Scrolling a UITableView inside a UIScrollView
From the UIScrollView:canCancelContentTouches doc:
If the value of this property is NO, the scroll view does not scroll
regardless of finger movement once the content view starts tracking.
The most common method I use in such cases is to delegate information up the event chain. Set delegates in manner:
subview->cell->table
The second thing you can do is to send a notification via Notificaion Center. Table would listen to any events that forbid normal scrolling. It is overshoot but it will leave your code consistent.
I have no more ideas at the moment.

How to synchronize scrolling of a NSScrollView and a WebView?

My app has two main views: a NSTextView subclass within a NSScrollView and a WebView. What the WebView displays is dependent on what the user enters into the text view - so I would like when the user scrolls either the text view or the web view the other scrolls proportionately to it.
I found this article which mentions how to do it with 2 scroll views. My problem is that WebKit doesn't seem to use normal Scroll views anywhere.
How should I implement this? What am I missing?
This is not a trivial problem to solve perfectly, as it's difficult to know whether the amount of text being edited in your text view corresponds to a similar amount of scrolling in the web view.
However, to answer your question about scroll views in WebView, they are used but as far as I know not documented extensively. You have to take advantage of the fact that you can obtain the scrollview being used, using public API, by asking the appropriate WebView subview for its "enclosingScrollView". Something like this works for me in a WebView where I know that there is only one frame:
[[[[myWebView mainFrame] frameView] documentView] enclosingScrollView];
If they are scrolling proportionately, probably the simplest solution would be to override touches events on your UIScrollview and impliment stringByEvaluatingJavaScriptFromString: on the UIWebView with (js) window.scroll(x,y). However, scrolling from the webView to the textview will require more work. The webView eats touches events, so you would need to
create a top level UIView or UIScroll view which captures the touches and sends them on to both the scrollView and the webView (via javascript) for ALL touches events, or
use a gesture recognizer to do the same.

touchesEnded:withEvent: from UIScrollView First Responder

I've made a UIScrollView the first responder. I need to maintain touch events to a -touchesEnded:withEvent: method on a view behind it. I've tried using the -nextResponder method and that failed. I've tried forwarding -touchesEnded:withEvent: to the view behind it and that fails.
How do I get this to work? The UIScrollView wont work unless it is the first responder or gets events some other way.
Thank you for any help. Shame Apple's documentation and APIs are terrible in areas.
UIScrollView handles touches in a slightly unusual way. From the class reference:
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...
A subview within the scroll view's content view will eventually receive non-scrolling touches to the scroll view.
A superview of a scroll view is unlikely to see touch events as its -hitTest:withEvent: will return the scroll view so touch events will be sent to the scroll view which is not required to forward them back up the responder chain to the parent view.
First responder does not influence the delivery of touch events, see http://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/EventsiPhoneOS/EventsiPhoneOS.html#//apple_ref/doc/uid/TP40009541-CH2-SW5
The first responder is the responder object in an application (usually a UIView object) that is designated to be the first recipient of events other than touch events...