Detecting all touches in an app - objective-c

In an iPad app, wherever a user is touching the screen I want to display an image, highlighting the points they are touching. The app contains a number of nested views, all of which should receive touches and behave normally.
Seems simple, but I've failed to find a good way to do it. Using the touches began: with event and related functions on the root view controller does not work because if a subview is touched the events are not fired. I've also created a 'dummy' gesture recognizer which just passes touch events to another class which draws images. That works great-ish and buttons work, but breaks UIScrollViews and I'm guessing other subviews with gesture reconizers.
Is there nowhere you can just access all touch events without affecting where those touches are headed?
thanks.

Your dummy gesture recognizer should be ok. Just watch out for setting states. possible -> began -> ...
Basically your gesture recognizer is forwarding all touches so it can be in began or possible state all the time while any touch exists.
To get rid of problems with other gesture recognizers return YES in this delegate method.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
Other option is to subclass main UIWindow in your app and override this method
- (void)sendEvent:(UIEvent *)event
Here you should have access to all events. It's quite easy to filter them.

You can apply a UITapGestureRecognizer to the entire view and set the cancelsTouchesInView property to NO. This will allow you to be notified of all taps on the view and its subviews without intercepting all of the touch events.
Additionally, you can implement the -gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: delegate method to keep this gesture recognizer from stomping on the ones used by views like UIScrollView.

you can try overriding hitTest:withEvent:
-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
This maybe what you are looking for.

Related

Get UIEvent to cancel UIGestureRecognizer

I have a simple swipe gesture that I want to be very low priority. I want it to be cancelled by events that happen for controls on the view. At first, I thought this would be simple. It's easy to cancel events when a gesture happens, but I can't seem to do the opposite.
My solution is to cancel the gesture if it conflicts with any thing that is touchable. Here is the code I hacked together:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint touch = [gestureRecognizer locationInView:self.view];
return [self.view hitTest:touch withEvent:nil] == self.view;
}
I feel this is the wrong solution to the problem. What am I missing? What is the right way to get events to cancel gestures?
For more context, I have two UISwipeGestureRecognizers (swipe left and swipe right) added to the view. There is also a UISlider in the view (part of an embedded MPVolumeView). When I moved the slider to change the volume, the left or right swipe would fire.
This is the correct way to do what you want. You are telling the gesture recognizer that it should only begin if the touch is directly in the view, not any subviews (according to hitTest: which is good because it allows the views to decide if they are hit or not).
It's always better to prevent it from starting rather than trying to cancel it afterwards. However, if you do want to cancel the gesture after it has started, set enabled = NO and then back to YES again.
If you need to allow the gesture for some subviews but not controls you can test if the view returned by hitTest: is a subclass of UIControl (for example) using isKindOfClass:.
I don't know what type of gesture this is for, but usually you don't need to do this because UIKit will automatically find the deepest view that wants the touches and that view will 'eat' them so outer gesture recognizers won't get them - however I can imagine this doesn't hold true for some combinations of recognizer/control.

UIScrollView on UIWebView and UITapGestureRecognizer conflicts

I have a UITapGestureRecognizer on a UIViewController, which has a UIScrollView and UIWebView inside. It recognizes the tap gesture only after I scroll the UIWebView. How could I prevent this ?. Basically I want the tap gesture to be detected, when I am not scrolling the web view. I looked around and the closest I found is this:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer {
return YES;
}
but not sure how can I use this to disable the tap while scrolling. Any idea?
Another thing I want to do is to disable the UITapGestureRecognizer, when a link on the UIWebView is clicked (shouldStartLoadWebRequest is called). I checked that the tap gesture recognizer is called, before the shouldStartLoadWebRequest is called. Basically when clicking on a link on a UIWebView, it shouldn't trigger the action invoked by the UITapGestureRecongnizer. Any idea on how to do this?
So Apple's documentation strongly recommends you don't nest a UIWebView inside a scroll view:
Important: You should not embed UIWebView or UITableView objects in UIScrollView objects. If you do so, unexpected behavior can result because touch events for the two objects can be mixed up and wrongly handled.
It is possible on iOS 5 and above to get direct access to the underlying scroll view on a UIWebView (using the scrollView) property - playing around with this might help you.

touchesShouldBegin Returns NO in ScrollView: Then What?

I'm getting exactly the behavior I want in a UIScrollView: when I return NO for touchesShouldBegin, the scrolling behavior happens. Otherwise, the content views get the event.
However, I'd like to show something on touch down and touch up when the scrolling behavior is occurring. Unfortunately, returning NO for touchesShouldBegin blocks the touchesBegan and touchesEnded methods.
The delegate method:
-(void) scrollViewWillBeginDragging:(UIScrollView *)scrollView {
doesn't work because sometimes the user touches but doesn't drag. How can I register touchUp and touchDown events yet preserve scrolling behavior?
You cannot. While a UIScrollview is scrolling, the subview(s) (and the view itself) are not updated or redrawn. It only scrolls. It is just how the UIResponder chain works in this scenario.

UIResponder chain question

Im not too informed on the nitty gritty of modifying the responder chain, so if this is stupid pls dont bash me ;)
basically i have 2 view stacks(blue) as a subview of a parent view(red)
they are both occupying the full frame of parent at one point in time, so obviously only the one on top is getting touch events, which travel upstream to the parent view(red) and on to window.
During some cases i want the touch input to be picked up by the eligible child view of 'other' view stack. That is, the view that woud be receiving these inputs if the current topmost view had userinteractionenabled set to no.
Setting userinteractionenabled works but i feel like its a dirty hack. The gist is this topmot view is mostly transparent and i want the events when touched in the specified region to end up on the other stack.
here is a picture to help visually explain, and keep in mind both blue views are 100% of parent.
http://c.crap.ps/35a5
You can override hitTest:withEvent: in each of the views to control who gets to "consume" the touch.
In your example I am assuming that the greenish areas are subviews that you want to consume the touch events. If so, then try this for the hitTest method:
-(UIView*) hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = [super hitTest:point withEvent:event];
return (hitView == self) ? nil : hitView;
}
This method checks if the touch hits any of the subviews. If it does then it lets that subview consume the touch, otherwise it lets the touch continue through the hierarchy.

Intercept UITableView scroll touches

Is it possible to control when the UITableView scrolls in my own code.
I am trying to get behaviour where a vertical swipe scrolls and a horizontal swipe gets passed through to my code (of which there are many example)
BUT
I want a DIAGONAL swipe to do nothing, i.e the UITableView should not even begin scrolling.
I tried catching it in here
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
but the scrollView.contentOffset.x is always 0 so I cannot detect a horizontal movement.
I also tried subclassing UITableView and implementing
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
etc..
but the UITableView (and I guess it's parent UIScrollView) start to scroll before the touches are notified?
To re-iterate, I want the UITableView scrolling to be locked if a diagonal swipe is made, but to scroll vertically normally.
(This behaviour can be seen in Tweetie(Twitter) for the iPhone)
Thanks for any help!
If you can work with 3.2 or later, the UIGestureRecognizer suite should allow this. There are a series of calls allowing some gestures to cancel or interoperate with other gestures, and you should be able to create a custom diagonal swipe gesture that cancels other gestures but does not actually do anything itself.
Prior to 3.2 the UIScrollView gesture handling is essentially undocumented. You can detect taps but not movements through the standard touchesBegan UIResponder calls. I think that once it detects movement it commandeers the run loop and captures all events, bypassing the normal event chain.
Note that setContentOffset is always called. You can create a subclass of UIScrollView and when you detect a diagonal shift during event tracking do not pass it to super. I do not know if, or how well this would work but it is somewhere to start.