event handling in objective c - iphone

i try to inspect the low-level of the uiresponder,Because i try to get to the bottom of what happen when i press a button(uibutton), if there is any other methods that called when a button is clicked or only the IBaction that i had defined.

If you want to know nearly everything that happens on the Cocoa level, create a category for UIResponder like this:
#implementation UIResponder (Inspecting)
- (BOOL)respondsToSelector:(SEL)aSelector {
NSLog(#"%# respondsToSelector: %#", [self class], NSStringFromSelector(aSelector));
return [super respondsToSelector:aSelector];
}
#end
It will print just everything that is invoked on UIResponder subclasses in runtime.

There are several ways you could get more information about that button, and the button press. First of all, if you just need the button, any time you create a method for an interface builder object, you follow this format:
-(IBAction)someMethod:(id)sender
Well, that (id)sender is actually your button. So, you can do pretty much anything you want with it, just give it a cast to stop the compiler from whining at you:
UIButton *myButton = (UIButton *)sender;
If you wanted to be super careful to make sure it actually IS a button before you cast it as such, (id basically means it could be anything) you can check any object's class with the isMemberOfClass method:
if ([sender isMemberOfClass:[UIButton class]]) {
UIButton *myButton = (UIButton *)sender;
}
If you need to know which button it is, you could, for instance, give each button a unique tag, and then access that tag like so:
int theTag = [sender tag];
From within your method.
If you need to do more, like know more about the touch itself, you can create your own subclass of UIButton, and then within interface builder, set the class of the button subclass you created to your custom subclass. Then, within that button, override (implement) any of the
UIResponder methods that deal with event handling, such as:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *aTouch = [touches anyObject];
[super touchesBegan:touches withEvent:event];
NSLog(#"Yay! A touch was down, at x: %f and y:%f", [touch locationInView].x, [touch locationInView].y);
}
For event handling, you can access the UIEvent object supplied, in this case as event, but most often, you're interested in the touches.

Related

How get UITableView IndexPath from UITableView iphone?

In my iPhone app I have one messaging screen. I have added UITapGestureRecognizer on the UIViewController and also I have a UITableview on the screen. I want to select the UITableViewCell but I can't select the UITableView because of UITapGestureRecognizer. When I touch the screen, only the tap gesture action is called but UITableView delegate didSelectRowAtIndexPath: is not called. Could anyone please help me to work on both tap gesture and UITableView:didSelectRowAtIndexPath:. Thanks in advance.
While I prefer Matt Meyer's suggestion or my other suggestion of using a custom gesture recognizer, another solution, not involving custom gesture recognizers, would be to have your tap gesture recognizer identify whether you tapped on a cell in your tableview, and if so, manually invoke didSelectRowAtIndexPath, e.g.:
- (void)handleTap:(UITapGestureRecognizer *)sender
{
CGPoint location = [sender locationInView:self.view];
if (CGRectContainsPoint([self.view convertRect:self.tableView.frame fromView:self.tableView.superview], location))
{
CGPoint locationInTableview = [self.tableView convertPoint:location fromView:self.view];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:locationInTableview];
if (indexPath)
[self tableView:self.tableView didSelectRowAtIndexPath:indexPath];
return;
}
// otherwise proceed with the rest of your tap handling logic
}
This is suboptimal because if you're doing anything sophisticated with your tableview (e.g. in cell editing, custom controls, etc.), you lose that behavior, but if you're just looking to receive the didSelectRowAtIndexPath, then this might do the job. The other two approaches (separate views or the custom gesture recognizer) let you retain the full tableview functionality, but this could work if you just need something simple and you don't need the rest of the tableview's built-in capabilities.
You can use the TagGesture delegate:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch.view isDescendantOfView:yourTableView]) {
return NO;
}
return YES;
}
Hope this helps.
An easier way to do this is to have two views: one containing the view that you want the tap gesture to be on, and one containing the tableview. You can attach the UITapGestureRecognizer to the view you want it to work on, and then it won't block your UITableView.
Assuming you want the tap gesture to work everywhere except over the tableview, you could subclass the tap gesture recognizer, creating a recognizer that will ignore any subviews included in an array of excludedViews, preventing them from generating a successful gesture (thus passing it on to didSelectRowAtIndexPath or whatever):
#import <UIKit/UIGestureRecognizerSubclass.h>
#interface MyTapGestureRecognizer : UITapGestureRecognizer
#property (nonatomic, strong) NSMutableArray *excludedViews;
#end
#implementation MyTapGestureRecognizer
#synthesize excludedViews = _excludedViews;
- (id)initWithTarget:(id)target action:(SEL)action
{
self = [super initWithTarget:target action:action];
if (self)
{
_excludedViews = [[NSMutableArray alloc] init];
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
CGPoint location = [[touches anyObject] locationInView:self.view];
for (UIView *excludedView in self.excludedViews)
{
CGRect frame = [self.view convertRect:excludedView.frame fromView:excludedView.superview];
if (CGRectContainsPoint(frame, location))
self.state = UIGestureRecognizerStateFailed;
}
}
#end
And then, when you want to use it, just specify what controls you want to exclude:
MyTapGestureRecognizer *tap = [[MyTapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[tap.excludedViews addObject:self.tableView];
[self.view addGestureRecognizer:tap];

UIScrollView sending touches to subviews

Note: I already read some questions about the UIScrollView sending touches to the subviews (this included and although I have up voted, it's not working as I intended anymore).
What I have: I have a UIScrollView with a Custom UIView (let's call it A) inside which covers the entire UIScrollView. I am also allowed to put other custom UIViews inside the A.
On the code I am doing this:
[scrollView setDelaysContentTouches:YES];
scrollView.canCancelContentTouches = NO;
What is happening: At the moment my only issue is that, if I want to move a subview inside A, I have to touch it, wait, and then move it. Exactly as stated here:
Now, the behaviour changes depending on the "length in time" of the
first touch on the UIView. If it's short, then the relative dragging
is managed as it was a scroll for the UIScrollView. If it's long, then
I'm getting the touchesMoved: events inside my UIView.
What I want: The subviews inside A should always receive priority and I shouldn't have to touch and wait. If I touch A and not a subview of it, I want the UIScrollView to receive the touches, like panning and moving around (the contentSize is bigger than the frame).
Edit 1.0
The only reason for me to have this A view inside a generic UIScrollView, is because I want to be able to zoom in/out on the A view. So I am doing the following:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return customView; // this is the A view
}
In the beginning I didn't had the A view inside the UIScrollView and the only thing I did was adding the A as a subView of my UIViewController's root view and everything went well. If there is another way to enable zoom in/out I will gladly accept the answer.
Note: Thank you all for your contributions, specially to Aaron Hayman.
I was able to figure it out by doing the following on the UIScrollView sub-class I had:
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint pointOfContact = [gestureRecognizer locationInView:self];
// The view with a tag of 200 is my A view.
return (![[self hitTest:pointOfContact withEvent:nil] isEqual:[self viewWithTag:200]]);
}
I haven't tested this, but I believe how you are handling the touch events in View A (or it's subviews) will determine how touch events are passed on. Specifically, if you're trying to use the methods: touchesBegan, touchesMoves, touchesEnded, etc instead of a UIGestureRecognizer you won't receive the touches in the way you want. Apple design the UIGestureRecognizer to handle problems like the one you're facing. Specifically, the UIScrollView uses UIPanGestureRecognizer to handle the scrolling. If you add a UIPanGestureRecognizer to each of the subviews of View A any "panning" that occurs on one of those subviews should be sent to that subview instead of the UIScrollView. However, if you're simply using the "raw" touches methods, the UIPanGestureRecognizer in UIScrollView will never be cancelled.
In general, it's almost always best to use a UIGestureRecognizer instead of processing the touches directly in the view. If you need touches processed in a way that no standard UIGestureRecognizer can provide, subclass UIGestureRecognizer and process the touches there. That way you get all the the functionality of a UIGestureRecognizer along with your own custom touch processing. I really think Apple intended for UIGestureRecognizer to replace most (if not all) of the custom touch processing code that developers use on UIView. It allows for code-reuse and it's a lot easier to deal with when mitigating what code processes what touch event.
Jacky, I needed a similar thing: Within a building plan (your A, in my case a subclass of UIScrollView), let the user place and resize objects (call them Bs). Here's a sketch of what it took me to get at this behavior:
In the superview's (A) initWithFrame: method, set these two:
self.canCancelContentTouches = YES;
self.delaysContentTouches = NO;
This will ensure taps on B are immediately routed to the Bs.
In the embedded B, stop the superview A from cancelling taps, so it does not interfere with a gesture started on the B.
In the touchesBegan: method, search the view hierarchy upwards (using superview property of the views) until you find a UIScrollView, and set its canCancelContentTouches to NO. Remember the superview you changed, and restore this property in the touchesEnded and touchesCancelled methods of B.
I'd be interested whether this works for you as well. Good Luck!
nobi
I think you had better use "touchesBegan,touchesMoved,touchesEnded" to pass the event.
you can do like this:
you should make a mainView . It has 2 property. One is yourScrollView A , and One is yourCustomView.
`[yourScrollView addSubviews:yourCustomView];
[mainView addSubviews:yourScrollView];`
and then write your touches method in the mainView.m like this (ignor the scrollView statment)
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
if ([[touches allObjects] isKindOfClass:[yourCustomView class]])
{
//do whatever you want
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
if ([[touches allObjects] isKindOfClass:[yourCustomView class]])
{
//do whatever you want
}
}
The last step: pass the event to the subview of the scrollView(your A).
#import "yourScrollView.h"
#implementation yourScrollView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code.
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
if(!self.dragging)
[[self nextResponder] touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesMoved:touches withEvent:event];
if(!self.dragging)
[[self nextResponder] touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesEnded:touches withEvent:event];
if(!self.dragging)
[[self nextResponder] touchesEnded:touches withEvent:event];
}
- (void)dealloc {
[super dealloc];
}
#end
wish to help you

Recognise all touches in interface

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.

iPhone SDK: detect view that was tapped

In my application I have a lot of little UIImageViews and a view off to the side. What I need is that view off to the side to be able to detect which image view was tapped and give me all the information about it (much like with (id)sender) as well as its coordinates. What is the best way to do this?
This is the internal class from my project. I think you get the idea.
As an option you can create protocol for the owner (or delegate).
You can obtain coords using
-[UITouch locationInView: someView]
Here is the code:
#interface _FaceSelectView : UIImageView {
#protected
VIFaceSelectVC* _owner;
}
-(id) initWithOwner:(FaceSelectVC*) owner;
#end
#implementation _FaceSelectView
-(id) initWithOwner:(FaceSelectVC*) owner {
if( self = [super init] ) {
_owner = owner;
self.userInteractionEnabled = YES;
}
return self;
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[_owner _touchesBeganIn: self withTouch: (UITouch*)[touches anyObject]];
}
-(void) touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event {
[_owner _touchesEndedIn: self withTouch: (UITouch*)[touches anyObject]];
}
-(void) touchesMoved:(NSSet*) touches withEvent:(UIEvent*) event {
[_owner _touchesMovedIn: self withTouch: (UITouch*)[touches anyObject]];
}
#end
I would subclass UIImageView and add a new property for the target view you would like to message. Then reenable userInteractionEnabled and add a action to touchesUpInside.
In the action method of the custom subclass call a method on the target view in which you also give the object of the custom subview. Basically you use delegation and pass in the delegate call all parameters you need.
Add tag to UIImageView like myUIImageView.tag = intNumber;
and in side touchesBegan or you can call any common method for all your UIImageView and use tag to identify which view is tapped.

(iPhone) How to handle touches on a UITextView?

I'm trying to handle touches on a iPhone's UITextView. I successfully managed to handle taps and other touch events by creating a subclass of UIImageViews for example and implementing the touchesBegan method...however that doesn't work with the UITextView apparently :(
The UITextView has user interaction and multi touch enabled, just to be sure...no no joy. Anyone managed to handle this?
UITextView (subclass of UIScrollView) includes a lot of event processing. It handles copy and paste and data detectors. That said, it is probably a bug that it does not pass unhandled events on.
There is a simple solution: you can subclass UITextView and impement your own touchesEnded (and other event handling messages) in your own versions, you should call[super touchesBegan:touches withEvent:event]; inside every touch handling method.
#import "MyTextView.h" //MyTextView:UITextView
#implementation MyTextView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"touchesBegan");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
NSLog(#"touchesMoved");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"****touchesEnded");
[self.nextResponder touchesEnded: touches withEvent:event];
NSLog(#"****touchesEnded");
[super touchesEnded:touches withEvent:event];
NSLog(#"****touchesEnded");
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
[super touches... etc];
NSLog(#"touchesCancelled");
}
If you want to handle single/double/triple tap on UITextView, you can delegate UIGestureRecongnizer and add gesture recognizers on your textview.
Heres sameple code (in viewDidLoad):
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap)];
//modify this number to recognizer number of tap
[singleTap setNumberOfTapsRequired:1];
[self.textView addGestureRecognizer:singleTap];
[singleTap release];
and
-(void)handleSingleTap{
//handle tap in here
NSLog(#"Single tap on view");
}
Hope this help :D
Better solution (Without swizzling anything or using any Private API :D )
As explained below, adding new UITapGestureRecognizers to the textview does not have the expected results, handler methods are never called. That is because the UITextView has some tap gesture recognizer setup already and I think their delegate does not allow my gesture recognizer to work properly and changing their delegate could lead to even worse results, I believe.
Luckily the UITextView has the gesture recognizer I want already setup, the problem is that it changes according to the state of the view (i.e.: set of gesture recognizers are different when inputing Japanese than when inputing English and also when not being in editing mode).
I solved this by overriding these in a subclass of UITextView:
- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
[super addGestureRecognizer:gestureRecognizer];
// Check the new gesture recognizer is the same kind as the one we want to implement
// Note:
// This works because `UITextTapRecognizer` is a subclass of `UITapGestureRecognizer`
// and the text view has some `UITextTapRecognizer` added :)
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)gestureRecognizer;
if ([tgr numberOfTapsRequired] == 1 &&
[tgr numberOfTouchesRequired] == 1) {
// If found then add self to its targets/actions
[tgr addTarget:self action:#selector(_handleOneFingerTap:)];
}
}
}
- (void)removeGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
// Check the new gesture recognizer is the same kind as the one we want to implement
// Read above note
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)gestureRecognizer;
if ([tgr numberOfTapsRequired] == 1 &&
[tgr numberOfTouchesRequired] == 1) {
// If found then remove self from its targets/actions
[tgr removeTarget:self action:#selector(_handleOneFingerTap:)];
}
}
[super removeGestureRecognizer:gestureRecognizer];
}
- (void)_handleOneFingerTap:(UITapGestureRecognizer *)tgr
{
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:tgr forKey:#"UITapGestureRecognizer"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"TextViewOneFingerTapNotification" object:self userInfo:userInfo];
// Or I could have handled the action here directly ...
}
By doing this way, no matter when the textview changes its gesture recognizers, we will always catch the tap gesture recognizer we want → Hence, our handler method will be called accordingly :)
Conclusion:
If you want to add a gesture recognizers to the UITextView, you have to check the text view does not have it already.
If it does not have it, just do the regular way. (Create your gesture recognizer, set it up, and add it to the text view) and you are done!.
If it does have it, then you probably need to do something similar as above.
Old Answer
I came up with this answer by swizzling a private method because previous answers have cons and they don't work as expected. Here, rather than modifying the tapping behavior of the UITextView, I just intercept the called method and then call the original method.
Further Explanation
UITextView has a bunch of specialized UIGestureRecognizers, each of these has a target and a action but their target is not the UITextView itself, it's an object of the forward class UITextInteractionAssistant. (This assistant is a #package ivar of UITextView but is forward definition is in the public header: UITextField.h).
UITextTapRecognizer recognizes taps and calls oneFingerTap: on the UITextInteractionAssistant so we want to intercept that call :)
#import <objc/runtime.h>
// Prototype and declaration of method that is going be swizzled
// When called: self and sender are supposed to be UITextInteractionAssistant and UITextTapRecognizer objects respectively
void proxy_oneFingerTap(id self, SEL _cmd, id sender);
void proxy_oneFingerTap(id self, SEL _cmd, id sender){
[[NSNotificationCenter defaultCenter] postNotificationName:#"TextViewOneFinderTap" object:self userInfo:nil];
if ([self respondsToSelector:#selector(proxy_oneFingerTap:)]) {
[self performSelector:#selector(proxy_oneFingerTap:) withObject:sender];
}
}
...
// subclass of UITextView
// Add above method and swizzle it with.
- (void)doTrickForCatchingTaps
{
Class class = [UITextInteractionAssistant class]; // or below line to avoid ugly warnings
//Class class = NSClassFromString(#"UITextInteractionAssistant");
SEL new_selector = #selector(proxy_oneFingerTap:);
SEL orig_selector = #selector(oneFingerTap:);
// Add method dynamically because UITextInteractionAssistant is a private class
BOOL success = class_addMethod(class, new_selector, (IMP)proxy_oneFingerTap, "v#:#");
if (success) {
Method originalMethod = class_getInstanceMethod(class, orig_selector);
Method newMethod = class_getInstanceMethod(class, new_selector);
if ((originalMethod != nil) && (newMethod != nil)){
method_exchangeImplementations(originalMethod, newMethod); // Method swizzle
}
}
}
//... And in the UIViewController, let's say
[textView doTrickForCatchingTaps];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textViewWasTapped:) name:#"TextViewOneFinderTap" object:nil];
- (void)textViewWasTapped:(NSNotification *)noti{
NSLog(#"%#", NSStringFromSelector:#selector(_cmd));
}
You need to assign the UITextView instance.delegate = self (assuming you want to take care of the events in the same controller)
And make sure to implement the UITextViewDelegate protocol in the interface... ex:
#interface myController : UIViewController <UITextViewDelegate>{
}
Then you can implement any of the following
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView;
- (BOOL)textViewShouldEndEditing:(UITextView *)textView;
- (void)textViewDidBeginEditing:(UITextView *)textView;
- (void)textViewDidEndEditing:(UITextView *)textView;
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
- (void)textViewDidChange:(UITextView *)textView;
- (void)textViewDidChangeSelection:(UITextView *)textView;
I'm using a textview as a subview of a larger view. I need the user to be able to scroll the textview, but not edit it. I want to detect a single tap on the textview's superview, including on the textview itself.
Of course, I ran into the problem that the textview swallows up the touches that begin on it. Disabling user interaction would fix this, but then the user won't be able to scroll the textview.
My solution was to make the textview editable and use the textview's shouldBeginEditing delegate method to detect a tap in the textview. I simply return NO, thereby preventing editing, but now I know that the textview (and thus the superview) has been tapped. Between this method and the superview's touchesEnded method I have what I need.
I know that this won't work for people who want to get access to the actual touches, but if all you want to do is detect a tap, this approach works!
How about make a UIScrollView and [scrollView addSubview: textview] which makes it possible to scroll textview?
You can also send a Touch Down event. Wire-up this event through the Interface Builder.
Then add code in your event handler
- (IBAction)onAppIDTap:(id)sender {
//Your code
}