I want to be able to recognise ALL touches in an interface, no matter what was touched.
I've tried:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
.. but this just recognises when the user taps on something that doesn't respond to taps (uiimages for instance)
The reason i need this ability is that I want to kick in a slide show if the user doesn't touch the screen for 5 minutes, so I want to reset the timer whenever they touch. It seems wrong to put this reset code in each UI event individually.
There are several possible solutions, but as said #omz - overriding the sendEvent: it is the best one.
#interface YourWindow : UIWindow {
NSDate timeOfLastTouch;
}
#end
#implementation YourWindow
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event];
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
if( touch.phase == UITouchPhaseEnded ){
timeOfLastTouch = [NSDate date];
}
}
#end
Do not forget replace UIWindow with YourWindow.
You could subclass UIWindow and override the sendEvent: method.
you could use a tap gesture
In your interface add the UIGestureRecognizerDelegate
#interface ViewController : UIViewController <UIGestureRecognizerDelegate> {
then in your viewDidLoad add this
UITapGestureRecognizer *tapped = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapMethod)];
tapped.delegate=self;
tapped.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tapped];
then do your timer code in the tapped method
-(void)tapped {
//timer code
}
Make sure you UI elements have setUserInteractionEnabled:YES
You can subclass the UIWindow and override the sendEvent: method like this:
- (void)sendEvent:(UIEvent *)event {
if (event.type == UIEventTypeTouches) {
// You got a touch, do whatever you like
};
[super sendEvent:event]; // Let the window do the propagation of the event
}
As #Alladinian said in one of the comments, iOS Reference Documentation mentions that subclassing UIApplication is the right application and thus, seems preferred to subclassing UIWindow. cf. https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIApplication_Class/Reference/Reference.html :
You might decide to subclass UIApplication to override sendEvent: or
sendAction:to:from:forEvent: to implement custom event and action
dispatching.
Related
I am using a custom gesture recognizer (as given in the link Intercepting/Hijacking iPhone Touch Events for MKMapView) for detecting touch events on MKMapView. gesture recognizer is defined in WildcardGestureRecognizer.h and implemented in WildcardGestureRecognizer.m file. When this gesture recognizer is added to MKMapview, one can read from following method any touch events
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (touchesBeganCallback)
touchesBeganCallback(touches, event);
NSLog(#"touchesBegan");
}
Based on this touch detection I want to call method tapMethod from MainViewController(Containing MKMapView).
-(void) tapMethod
{
dontUpdateLocation =1;// a variable to check stoppoing of location update.
NSLog(#" map tapped");
}
I tried following
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (touchesBeganCallback)
touchesBeganCallback(touches, event);
NSLog(#"touchesBegan");
MainViewController *updateController = [[MainViewController alloc]init ];
[updateController tapMethod];
[updateController release];
}
It does print "map tapped" but doesn't change value of variable dontUpdateLocation.
How can I do it?
From what I understand from your comments, I think the problem is due to the fact that when you do this:
MainViewController *updateController = [[MainViewController alloc]init ];
[updateController tapMethod];
[updateController release];
you're not creating a reference to the existing mainviewcontroller, but you are creating a different pointer that points to another object in memory.
You may use the appDelegate to store(set #property, and #synthesize) the variable and then access like this:
YourAppDelegate *appDel=(YourAppDelegate *)[[UIApplication sharedApplication] delegate];
appDel.dontUpdateLocation=1;
I suggest you take a look and depth of these patterns: Singletons, MVC, and Delegation
Hope this helps.
I implemented gesture recognizer in UIMapView just as described in the accepted answer to this question: How to intercept touches events on a MKMapView or UIWebView objects?
Single touches are recognized correctly. However, when I changed the superclass of my class from UIGestureRecognizer to UIPinchGestureRecognizer in order to recognize map scaling, everything stopped working.
Now TouchesEnded event occurs only when the user double tap the annotation on map (don't know, why!) and doesn't occur when the user pinches the map (zoom in or out doesn't matter).
PS I'm using iOS SDK 4.3 and testing my app in simulator if that matters.
The code of mapViewController.m - viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
MapGestureRecognizer *changeMapPositionRecognizer = [[MapGestureRecognizer alloc] init];
changeMapPositionRecognizer.touchesEndedCallback = ^(NSSet * touches, UIEvent * event)
{
...
};
[self.mapView addGestureRecognizer:changeMapPositionRecognizer];
[changeMapPositionRecognizer release];
}
The code of MapGestureRecognizer.h:
#import <UIKit/UIKit.h>
typedef void (^TouchesEventBlock) (NSSet * touches, UIEvent * event);
#interface MapGestureRecognizer : UIPinchGestureRecognizer
#property(nonatomic, copy) TouchesEventBlock touchesEndedCallback;
#end
The code of MapGestureRecognizer.m:
#import "MapGestureRecognizer.h"
#implementation MapGestureRecognizer
#synthesize touchesEndedCallback = _touchesEndedCallback;
- (id)init
{
self = [super init];
if (self) {
self.cancelsTouchesInView = NO;
}
return self;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.touchesEndedCallback)
{
self.touchesEndedCallback(touches, event);
NSLog(#"Touches ended, callback done");
}
else
{
NSLog(#"Touches ended, callback skipped");
}
}
- (void) dealloc
{
[super dealloc];
}
#end
What should I correct in to make pinch gesture to be recognized?
I'm not sure why you need to subclass UIPinchGestureRecognizer instead of using it directly as-is.
Also not sure why you need the gesture recognizer to detect map scaling which you could do by using the delegate methods regionWillChangeAnimated and regionDidChangeAnimated and comparing the span before and after. Unless you are trying to detect the scaling as it is happening (and not wanting to wait until user finishes the gesture)
The gesture recognizer may not be getting called because the map view's own pinch gesture recognizer is getting called instead.
To have your recognizer called as well as the map view's, implement the UIGestureRecognizer delegate method shouldRecognizeSimultaneouslyWithGestureRecognizer and return YES:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Make sure the gesture recognizer's delegate property is set or that method won't get called either.
On UIWebview, how can I detect a touch?
But not when user clicks some URL or touching a control.
Is it possible to handle it?
Use UIGestureRecognizerDelegate method:
Add UIGestureRecognizerDelegate in declaration file (i.e. your .h file)
Step 1: Just set the delegate of gestureRecognizer: (in .m file viewDidLoad)
UITapGestureRecognizer *webViewTapped = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapAction:)];
webViewTapped.numberOfTapsRequired = 1;
webViewTapped.delegate = self;
[offScreenWebView addGestureRecognizer:webViewTapped];
[webViewTapped release];
Step 2: Override this function: (in .m file)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Step 3: Now implement the tapAction function:
- (void)tapAction:(UITapGestureRecognizer *)sender
{
NSLog(#"touched");
// Get the specific point that was touched
CGPoint point = [sender locationInView:self.view];
}
The accepted answer is great if you only need to detect taps. If you need to detect all touches, the best way is to create a new UIView subclass and place it over the webview. In the subclass you can detect touches using hitTest:
TouchOverlay.h
#class TouchOverlay;
#protocol TouchOverlayDelegate <NSObject>
#optional
- (void)touchOverlayTouched:(TV4TouchOverlay *)touchOverlay;
#end
#interface TouchOverlay : UIView
#property (nonatomic, unsafe_unretained) id <TouchOverlayDelegate> delegate;
#end
Touchoverlay.m
#implementation TouchOverlay
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
return self;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self) {
if (self.delegate && [self.delegate respondsToSelector:#selector(touchOverlayTouched:)]) {
[self.delegate touchOverlayTouched:self];
}
return nil; // Tell the OS to keep looking for a responder
}
return hitView;
}
#end
Note that the accepted answer above will only capture tap gestures (touchDown and touchUp without a drag in between), and that swipe gestures will be ignored.
For my purposes I needed to be informed of both, and so I added swipe gesture recognizers appropriately. (Note that despite being a bit field, you can't OR together swipe gesture recognizers' direction property, so 4 gesture recognizers are required to detect any swipe).
// Note that despite being a bit field, you can't `OR` together swipe gesture
// recognizers' `direction` property, so 4 gesture recognizers are required
// to detect any swipe
for (NSNumber * swipeDirection in #[#(UISwipeGestureRecognizerDirectionUp), #(UISwipeGestureRecognizerDirectionDown), #(UISwipeGestureRecognizerDirectionLeft), #(UISwipeGestureRecognizerDirectionRight)]) {
UISwipeGestureRecognizer * swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(timerReset:)];
swipe.direction = [swipeDirection integerValue];
swipe.delegate = self;
[rootWebView addGestureRecognizer:swipe];
}
Everything that inherits from UIResponder can handle touches (so does UIWebView). Read the doc:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIResponder_Class/Reference/Reference.html
You'll have to use:
touchesBegan:withEvent:
Edit: Adding the comment here for clarity-
I believe then there's no clean way of doing it, you can either override the hittest withEvent method like this or do a hack like this: overriding UIView
Do you mean you want to override the options that popup when they hold down on a link? I managed to get one to work with this tutorial/guide but the one posted here is still slightly buggy and needs you to do some fine tuning:
http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/
I've seen some threads about how to dismiss the Keyboard when a UITextField loses focus, but it did not work for me and I don't know how. The "touchesBegan:withEvent:" in the following code, never gets called. Why?
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
if ([self.textFieldOnFocus isFirstResponder] && [touch view] != self.textFieldOnFocus) {
[textFieldOnFocus resignFirstResponder];
}
[super touchesBegan:touches withEvent:event];
}
P.S.: This code has been inserted in the view controller which has a UITableView. The UITextField is in a cell from this table.
So, my opinion is: this method is not being called, cause the touch occurs on the UITableView from my ViewController. So, I think, that to I should have to subclass the UITableView, to use this method as I have seen on other Threads, but it may have a easier way.
Could you please help me? Thanks a lot!
Make sure you set the delegate of the UITextField to First Responder in IB. And I just put a custom (invisible) UIButton over the screen and set up an IBAction to hide the keyboard. Ex:
- (IBAction)hideKeyboard {
[someTextField resignFirstResponder];
}
With that hooked up to a UIButton.
Here is my solution, somewhat inspired by several posts in SO: Simply handle the tap gesture in the context of the View, the user is 'obviously' trying to leave the focus of the UITextField.
-(void)handleViewTapGesture:(UITapGestureRecognizer *)gesture
{
[self endEditing:YES];
}
This is implemented in the ViewController. The handler is added as a gesture recognizer to the appropriate View in the View property's setter:
-(void) setLoginView:(LoginView *)loginView
{
_loginView = loginView;
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self.loginView action:#selector(handleTapGesture:)];
[tapRecognizer setDelegate:self]; // self implements the UIGestureRecognizerDelegate protocol
[self.loginView addGestureRecognizer:tapRecognizer];
}
The handler could be defined in the View as well. If you are unfamiliar with handling gestures, see Apple's docs are tons of samples elsewhere.
I should mention that you will need some additional code to make sure other controls get taps, you need a delegate that implements the UIGestureRecognizerDelegate protocol and this method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch.view isKindOfClass:[UIButton class]]) // Customize appropriately.
return NO; // Don't let the custom gestureRecognizer handle the touch
return YES;
}
-(void)touchesEnded: (NSSet *)touches withEvent: (UIEvent *)event
{
for (UIView* view in self.view.subviews)
{
if ([view isKindOfClass:[UITextField class]])
[view resignFirstResponder];
}
}
I have created a subclass of UIWebView , and have implemented the
touchesBegan, touchesMoved and touchesEnded methods.
but the webview subclass is not handling the touch events.
Is there any method to handle the touch events inside the UIWebView subclass ???
No subclassing needed, just add a UITapGestureRecognizer :
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapMethod)];
[tap setNumberOfTapsRequired:1]; // Set your own number here
[tap setDelegate:self]; // Add the <UIGestureRecognizerDelegate> protocol
[self.myWebView addGestureRecognizer:tap];
Add the <UIGestureRecognizerDelegate> protocol in the header file, and add this method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
If all you need is to handle gestures, while leaving the rest of the UIWebView functionality intact, you can subclass UIWebView and use this strategy:
in the init method of your UIWebView subclass, add a gesture recognizer, e.g.:
UISwipeGestureRecognizer * swipeRight = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(handleSwipeGestureRightMethod)];
swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
[self addGestureRecognizer:swipeRight];
swipeRight.delegate = self;
then, add this method to your class:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
Add and handle your designated selector to the class, in this case "handleSwipeGestureRightMethod" and you are good to go...
You could put an UIView over your UIWebView, and overide the touchesDidBegin etc, then send them to your webview. Ex:
User touches your UIView, which provokes a
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Execute your code then send a touchesBegan to your webview like so:
[webView touchesBegan:touches withEvent:event];
return;
}
your UIView has to be over the webview.
I'm not sure if this is what you want (it's not what you asked for, but it might work depending on what your end game is), but you could instead interpret the touches in JavaScript from inside the UIWebView, and get javascript to do
document.location='http://null/'+xCoord+'/'+yCoord; // Null is arbitrary.
Then you can catch that using the UIWebView's delegate method
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
And if the request.URL.host (or whatever it is) isEqualToString:#"null" take the relevant action (and return NO instead of YES). You can even add the JS to each page by doing something like:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[webView stringByEvaluatingJavaScriptFromString:#"window.ontouchstart=function(/* ... */);"];
}
Hope this helps?
Handling gestures on a UIWebView is discussed in this Apple Developer forum thread.
Using the info given there, there will be no need for an extra view in most or all cases, and as mentioned here before, overriding UIWebView is not the way to go.
Copypaste of the most important post in the thread:
This is a known issue. The UIWebView has its own UITapGestureRecognizers, and they're on a private subview of the UIWebView itself. UIGestureRecognizer precedence defines that gestures attached to views deeper in the view hierarchy will exclude ones on superviews, so the web view's tap gestures will always win over yours.
If it's okay in your case to allow your tap to happen along with the web view's normal tap your best solution would be to implement the UIGestureRecognizerDelegate method gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer and return YES for other tap gestures. This way you'll get your tap handler called, and the web view will still get its called.
If you need to be the only one handling the tap you'll have to subclass UITapGestureRecognizer so you can use the one-way overrides in UIGestureRecognizerSubclass.h, an you can then return NO from canBePreventedByGestureRecognizer: when asked if the web view's tap gesture recognizer can prevent yours.
In any case, we know about this and hope to make it easier in the future.
I've just found that UIWebView does check whether it responds to the - (void)webViewDidNotClick: (id)webBrowserView selector, once one taps on the view area (not on hyperref, or any other area that should be handled specifically). So you may implement that selector with your handling code :)
Do you mean your sub-classed implementation is not called when touchesBegan, touchesMoved and touchesEnded are called?
It sounds like a problem with how you've created an instance of the object. More details are required I think.
(taken form comments)
Header File
#import <UIKit/UIKit.h>
#interface MyWebView : UIWebView { } #end
Implementation File
#import "MyWebView.h"
#implementation MyWebView
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) { } return self;
}
- (void)drawRect:(CGRect)rect {
NSLog(#"MyWebView is loaded");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"touches began");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touches ended");
}
- (void)dealloc {
[super dealloc];
}
#end
I would try overriding -sendEvent: on UIWindow, to see if you can intercept those touch events.
Following on from what Unfalkster said, you can use the hitTest method to achieve what you want, but you don't have to subclass UIWindow. Just put this in your web view subclass. You will get a compile time warning but it does work:
- (void)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (event.type == UIEventTypeTouches) {
// get location info
CGFloat x = point.x;
CGFloat y = point.y;
// get touches
NSSet *touches = [event allTouches];
// individual touches
for (UITouch *touch in touches) {
if (touch.phase == UITouchPhaseBegan) {
// touches began
} else if (touch.phase == UITouchPhaseMoved) {
}
// etc etc
}
}
// call the super
[super hitTest:point withEvent:event];
}
Hope that helps!
If you want to detect your own taps but disable the UIWebView's taps then you can use my solution:
-(void)recursivelyDisableTapsOnView:(UIView*)v{
for(UIView* view in v.subviews){
for(UIGestureRecognizer* g in view.gestureRecognizers){
if(g == self.ownTapRecognizer){
continue;
}
if([g isKindOfClass:[UITapGestureRecognizer class]] ||
[g isKindOfClass:[UILongPressGestureRecognizer class]] ||
[g isKindOfClass:NSClassFromString(#"UITapAndAHalfRecognizer")]){
g.enabled = NO;
}
}
[self recursivelyDisableTapsOnView:view];
}
}
- (void)webViewDidFinishLoad:(UIWebView *)webView{
[self recursivelyDisableTapsOnView:webView];
//disable selection
[webView stringByEvaluatingJavaScriptFromString:#"document.documentElement.style.webkitUserSelect='none';"];
// Disable callout
[webView stringByEvaluatingJavaScriptFromString:#"document.documentElement.style.webkitTouchCallout='none';"];
}