I've got a bit of a construction here; I extended a UITableViewCell and am displaying a UIViewController modally from the UITableViewController class when the touchesBegin hits in the UITableViewCell. When the touchesEnded hits, I remove this UIViewController again.
So I'm showing something when you touch the cell, and when you release the screen I hide it again. All good, but it doesn't work if I quickly press and release on the UITableViewCell; in this case touchesEnded isn't called. If I hold it for a second or longer, the system works fine. 'touchesCancelled' is not called either.
Any thoughts on this? Since the focus is on the UITableViewCell, the Touch methods of the newly displayed UIViewController or the existing UITableViewController won't hit either (until I release and press again, but that defeats the purpose).
The relevant code:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
amDisplayingCapture = YES;
[self.showCaptureDelegate displayCapture:self.capture];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
if( amDisplayingCapture )
{
[self.showCaptureDelegate endDisplayCapture];
amDisplayingCapture = NO;
}
}
(didSelectRowAtIndexPath of the UITableView isn't called either once I present the new UIViewController, so can't use that)
From the UIResponder header file:
Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each touch it is handling (those touches it received in touchesBegan:withEvent:). You must handle cancelled touches to ensure correct behavior in your application. Failure to do so is very likely to lead to incorrect behavior or crashes.
Summary: you should also implement touchesCancelled:withEvent:.
In the event someone has a similar situation, I resolved this by adding the new viewcontroller's view as a subview rather than presenting the entire viewcontroller modally. That seemed to allow the touchesEnded to be called.
Related
I have subclassed UICollectionView as CoolGridCollectionView and I have overrides the touchesBegan: and touchesEnded: methods. But those are not getting called, when I tap the cell. And that cells are having single and double tap gesture recognisers. I need the position, where the tap has happened. I can get the position by using covertPoint: something else. But Why the touches methods are not called? Does iOS handles touches of UICollectionView specifically.
I’m not 100% sure, but you can try hitTest:withEvent: method instead of -touchesBegan:withEvent: method. Hope this will helpful to you.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
touchedView = [super hitTest:point withEvent:event];
NSSet* touches = [event allTouches];
// handle touches if you need
return touchedView;
}
I am trying to create a custom swipe gesture recognizer to set allowed angles and distances.
So far it works really well but needs a lot of code in the view controller. So my question is wether it is possible to somehow pass a method call like:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
in the view controller to my gesture recogniser object so that I don't have to write something like this
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.swipeGestureRecognizer touchesBegan:touches withEvent:event];
}
into my view controller. I think the build in UIGestureRecognizer does this somehow but I could not figure out how it works.
I really would appreciate your help.
[EDIT]
Generic example: I have an object A which creates an object B. A has a method c that gets called sometimes. What I want to achieve is that a method d of B gets called when c in A gets called. So to somehow forward or pass this method call.
Specific example: The following method gets called in my view controller every time there is a touch on the screen:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
but I actually want my GestureRecognizer object to process this information so I have the same method in my GestureRecognizer object and I want this method to get called every time the counterpart in the view controller gets called.
Sub class UIGestureRecognizer and create a class called CustomGestureRecognizer.
Its .m file will look like this
#implementation CustomGestureRecognizer
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// do your code here
}
#end
Some where in the subclass you have to change the state of the recognizer to
UIGestureRecognizerStateRecognized
when the current gesture is registered, else change to
UIGestureRecognizerStateFailed
.
Now in the view controller where you want to use this recognizer do :
CustomGestureRecognizer * recognizer = [[CustomGestureRecognizer alloc] initWithTarget:self action:#selector(handleChange:)];
recognizer.delegate = self;
[self.view addGestureRecognizer:recognizer];
Detailed explanation is given here Apple doc, under subclassing.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.swipeGestureRecognizer touchesBegan:touches withEvent:event];
}
The above will end up with Recursive calls, and your program will crash at some point of time.
I've tried subclassing UICollectionView and overriding touchesBegan:withEvent: and hitTest:WithEvent:, and both of those methods trigger when I touch a cell. However, if I touch the space between the cells, nothing happens at all. Here's what I've created:
#interface WSImageGalleryCollectionView : UICollectionView
#end
..and..
#implementation WSImageGalleryCollectionView
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Touches began");
[super touchesBegan:touches withEvent:event];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(#"Hit test reached");
return [super hitTest:point withEvent:event];
}
#end
Note: gesture recognizers seem to have the exact same issue, which is why I tried going lower-level with touchesBegan.
You just need to set a view as background view that you can then add a gesture recognizer to like:
collectionView.backgroundView = [[UIView alloc] init];
[collectionView.backgroundView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapOnBackgroundRecognized)]];
You must have got something else that is preventing touchesBegan from firing.
Subclassing UICollectionView and using that subclass inside of a UICollectionViewController will allow you to detect in-between cells taps in touchesBegan.
I have just tried it out by modifying Apple's CollectionView-simple sample.
In my case, the problem was that you can click on the background whenever it is "clear" colored. Otherwise simply setting a background color makes the background clickable. I don't quite know why this is, but the simple fix was to just give it a background color.
Edit: Actually, this may have had something to do with setting the "opaque" flag.
I have a subclass of UIView in which I've overridden hitTest:withEvent: as follows:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(#"Event = %#", event);
return self;
}
For each touch in the view, I am seeing three calls to hitTest:withEvent:. These three calls are made before touch up. The output is as follows:
2011-07-01 09:20:58.553 AppName[930:207] Event = <UITouchesEvent: 0x6a08360> timestamp: 4297.16 touches: {(
)}
2011-07-01 09:20:58.553 AppName[930:207] Event = <UITouchesEvent: 0x6a08360> timestamp: 4297.16 touches: {(
)}
2011-07-01 09:20:58.554 AppName[930:207] Event = <UITouchesEvent: 0x6a08360> timestamp: 4304.54 touches: {(
)}
Based on the timestamps and addresses, it appears as if a single UITouchesEvent object is being used and its timestamp isn't properly set until the third call. Can anyone explain why hitTest:withEvent: gets called three times like this? I'm not looking for a workaround. I just want to understand what's going on.
I had the same problem and was able to solve it with this code. Even though pointInside and hitTest get called 3 times, touchesBegan (or touchesEnded) of the UIView that was touched only gets called once.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (event.type == UIEventTypeTouches)
NSLog(#"%#", self);
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if ([self pointInside:point withEvent:event])
return self;
return [super hitTest:point withEvent:event];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint([self bounds], point))
{
if (event.type == UIEventTypeTouches)
{
return YES;
}
}
return NO;
}
Do you have more than one subview?
From the docs:
This method traverses the view hierarchy by sending the pointInside:withEvent: message to each subview to determine which subview should receive a touch event. If pointInside:withEvent: returns YES, then the subview’s hierarchy is traversed; otherwise, its branch of the view hierarchy is ignored. You rarely need to call this method yourself, but you might override it to hide touch events from subviews.
Yes, it’s normal. The system may tweak the point being hit tested between the calls. Since hitTest should be a pure function with no side-effects, this should be fine.
Refer to Apple Mailing List: Re: -hitTest:withEvent: called twice?
You should check to see if the subtype and type property are all the same. those 3 events do make sense since there's event that needs to be triggered in order for the OS to understand the nature of the touch event.
For example, swipe, pinch and tap all start with the same touch event. My guess is that the first two are fired 1 to register the tap event and the second to test for tap event to "move". the second is called not long afterwards probably to either cancel the pinching/zooming/whatever.
Bottom line, the documentations talks about 3 different type of events: touch, motion and remote events. UIEvent Class Reference
I have a UIViewController, on top I put an UIImageView, then a UIScrollView.
I can process touches on the UIScrollView just fine, however I want to process certain touches on the UIViewController.
I only need touches on the UIScrollView that are held for 5 seconds (This part works fine). Anything less than that I want to pass to the UIViewController.
On the UISCrollView I call this:
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
// some custom code to process a specific touch.
// all other touches
[super touchesBegan: touches withEvent: event];
}
Is there a way to pass touches from the UIScrollView to the bottom UIViewController, or do I have to pass a references to the UIViewController into the UIScrollView?
Solved!
Needed to add:
[self.nextResponder touchesEnded: touches withEvent:event];
to the top most controller.
Thank you to myself!
touchesBegan description in the Apple Documentation tells:
To forward the message to the next responder, send the message to super (the superclass implementation); do not send the message directly to the next responder. For example,
[super touchesBegan:touches withEvent:event];
If you override this method without calling super (a common use pattern), you must also override the other methods for handling touch events, if only as stub (empty) implementations.