I cannot figure out what is wrong. Below is my code, and it calls the delegate methods once then stops.
What should I do? I haven;t been able to find the sample code that uses these delegate methods. All I've found were gesture recognizers for swipes and taps, using different delegates.
Code so far:
-(void)initTouchesRecognizer{
DLog(#"");
recognizer = [[UIGestureRecognizer alloc] init];
[self addGestureRecognizer:recognizer];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
DLog(#"");
NSSet *allTouches = [event allTouches];
for (UITouch *touch in allTouches)
{
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
DLog(#"");
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
I call initTouchesRecognizer from initwithrect for my image view.
What am i doing fundamentally wrong?
UIGestureRecognizer is an abstract class, you're not supposed to add it directly to your view. You need to use a concrete subclass that inherits from UIGestureRecognizer, like UITapGestureRecognizer or UIPanGestureRecognizer for example. You could also make your own concrete subclass but that usually isn't necessary.
Here is an example of adding a UIPanGestureRecognizer to your view (in your view class code, often the gesture is added to the view from the controller):
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(mySelector:)];
[self addGestureRecognizer:panGesture];
In this case, the selector will be called when ever the user pans in this view. If you added a UITapGestureRecognizer, the selector would be called when the user tapped.
You can check out the apple docs for more info:
http://developer.apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html#//apple_ref/doc/uid/TP40009541-CH2-SW2
Also, I find Paul Hagerty's Stanford lectures to be great, here's one on gesture recognizers:
https://itunes.apple.com/ca/course/6.-views-gestures-january/id593208016?i=132123597&mt=2
You should also understand that none of the methods that you posted are delegate methods, and none of them have anything to do with the UIGestureRecognizer that you added in your code. Those are instance methods of UIResponder (a class that UIView inherits from) that you're overriding. The abstract UIGestureRecognizer also has instance methods with those same names, but it is not the UIGestureRecognizer methods that are getting called in your class.
There was no need to add the gesture recognizer. By overriding the touchesMoved, touchesEnded and touchesBegan methods, I was able to track the user's finger across the screen.
simply do not call the:
-(void)initTouchesRecognizer
code, and the code I originally posted will work.
Related
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
I have a UIViewController that contains a UIScrollView, which has a UIView inside of its contentview.
I have the following code that does not work, keyboard is not dismissed, why?:
#pragma mark -
#pragma mark Touch Events
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([touch view] == scrollView || [touch view] == self.view)
{
[usernameTextField resignFirstResponder];
[passwordTextField resignFirstResponder];
}
}
touchesBegan:withEvent: is a UIView method, not a UIViewController method. What are you trying to achieve here? You should very seldom have a UI reaction to touchesBegan:. You probably mean to use UITapGestureRecognizer instead.
Make sure to use accessors (self.scrollView) rather than accessing your ivars directly. Direct ivar access is the #1 cause of memory management crashes.
I'm posting this message because I've been reading the forum and I haven't been able to find a similar problem. I need to be able to discriminate taps and double taps (this is a standard thing) BUT my problem is that for whatever reasons I have a Scroll View inside another ScrollView. So, I had to sub-class my ScrollView in order to get touchedBegin method called.
I have a class called PhotoViewController (a sub-class of BaseViewController) this class contains another class called CustomScrollView (a subclass of ScrollView). I needed to sub-class this CustomScrollView from ScrollView in order to override the touchesBegin method, and to be able to capture the touches made by the user.
I tried calling the touchesBegin method from CustomScrollView using something like return [super touchesBegan:touches withEvent:event] inside the touchesBegin method, but when the touchesBegin method inside PhotoViewController gets called it's parameters are empty (and I can't discriminate if the user made a single or double tap, which is exactly what I need)
I have a class, called PhotoViewController:
#class PhotoViewController
#interface PhotoViewController : BaseViewController <UIScrollViewDelegate> {
CustomScrollView* myScrollView;
}
#implementation PhotoViewController
...
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSUInteger tapCount = [touch tapCount];
switch (tapCount) {
case 1:
[self performSelector:#selector(singleTapMethod) withObject:nil afterDelay:.4];
break;
case 2:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(singleTapMethod) object:nil];
[self performSelector:#selector(doubleTapMethod) withObject:nil afterDelay:.4];
break;
default:
break;
}
}
the class CustomScrollView is (CustomScrollView.h):
#interface CustomScrollViewPhoto : UIScrollView {
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
#end
and it's implementation is this(CustomScrollView.m):
#implementation CustomScrollViewPhoto
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.superview touchesBegan:[NSSet set] withEvent:event];
return [super touchesBegan:touches withEvent:event];
}
Am I going in the wrong direction with what I want to do? Maybe, I should capture the taps/double taps inside the CustomScrollView class(this works fine!), and from there using a #selector or something call the appropiate methods in PhotoViewController?
Thanks for reading!
I think you're going the wrong route (only slightly!). I do a very similar thing in a photo viewer and I capture the touches in the CustomScrollView. You shouldn't need to do anything in PhotoViewController.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
UITouch *touch = [touches anyObject];
if(touch.tapCount == 2)
{
if (self.zoomScale == self.minimumZoomScale)
{
//Zoom where the user has clicked from
CGPoint pos = [touch locationInView:self];
[self zoomToRect:CGRectMake((pos.x - 5.0)/self.zoomScale, (pos.y-5.0)/self.zoomScale, 10.0, 10.0) animated:YES];
}
else
{
//Zoom back out to full size
[self setZoomScale:self.minimumZoomScale animated:YES];
}
}
}
I want to make a transparent mask-view over the current window, which just tracks touch events and passing them to the visible views below. However if I set userInteractionEnabled=YES to this mask, this blocks the events and won't be passed below.
Is there any way that I can prevent this view from blocking the events, or manually passing the events below?
Thanks,
I just recently did this for one of my apps and it turned out to be quite simple.
Get ready to subclass UIView:
I called my Mask View the catcher view and this is how the protocol looks:
#interface CatcherView : UIView {
UIView *viewBelow;
}
#property(nonatomic,retain)UIView *viewBelow;
#end
Here you are just subclassing UIView AND keeping a reference to the view bellow.
On the implementation you need to fully implement at least 4 methods to pass the touches to the view, or views bellow, this is how the methods look:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touch Began");
[self.viewBelow touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touch Moved");
[self.viewBelow touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touch Ended");
[self.viewBelow touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touch Cancelled");
//Not necessary for my app but you just need to forward it to your view bellow.
}
Just remember to set the view or views that are bellow when you create the view; it is also very important to set the background color to clear, so it acts as a mask. THis is how that looks:
CatcherView *catchView=[[CatcherView alloc] initWithFrame:[self.view bounds]];
catchView.backgroundColor=[UIColor clearColor];
catchView.viewBelow=myViewBellow;
[self.view addSubview:catchView];
Hope it helps and comment if you need more info.
UIKit determines the target view for an event by sending -hitTest:withEvent: messages down the responder chain
Once the target has been found, the event is sent up the responder chain until a responder that handles it is found (often the view that was touched, but not always)
Thus, if you override -[NSView hitTest:withEvent:] in a suitably high up view (perhaps by using a custom window!) you can note all incoming events and call super to have them behave as normal.
I wish to drag from one subview to another within my application (and connect them with a line), and so I need to keep track of the current view being touched by the user.
I thought that I could achieve this by simply calling [UITouch view] in the touchesMoved method, but after a quick look at the docs I've found out that [UITouch view] only returns the view in which the initial touch occurred.
Does anyone know how I can detect the view being touched while the drag is in progress?
And my way:
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([self pointInside:[touch locationInView:self] withEvent:event]) {
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
} else {
[self sendActionsForControlEvents:UIControlEventTouchUpOutside];
}
}
After a bit more research I found the answer.
Originally, I was checking for the view like so:
if([[touch view] isKindOfClass:[MyView* class]])
{
//hurray.
}
But, as explained in my question, the [touch view] returns the view in which the original touch occurred. This can be solved by replacing the code above with the following:
if([[self hitTest:[touch locationInView:self] withEvent:event] isKindOfClass:[MyView class]])
{
//hurrraaay! :)
}
Hope this helps
UIView is subclass of UIResponder. So you can override touches ended/ began methods in your custom class, that inherits from UIView. Than you should add delegate to that class and define protocol for it. In the methods
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
or whatever interactions you need just send appropriate message to object's delegate also passing this object. E.g.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[delegate touchesBeganInView: self withEvent: event];
}