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.
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'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.
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.
Here's the setup.
I have a custom UIScrollView as a subview of PrimeView.
The custom UIScrollView can be regarded as overlaid scrollview and it gets all touch events initially.
Now, when the scroll view isn't dragging, I want it to pass touch events to other responders.
Below is my code at present, but I'm not sure about the difference between self.nextResponder and super here.
I don't get why touchesBegan is passed to the superview correctly, but touchesMoved isn't passed to the superview.
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
if (!self.dragging)
{
[self.nextResponder touchesBegan: touches withEvent:event];
}
[super touchesBegan: touches withEvent: event];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.nextResponder touchesBegan: touches withEvent:event]; //
[super touchesMoved:touches withEvent:event];
}
Apple's stance is that you should never do something like this:
[self.nextResponder touchesBegan: touches withEvent:event];
If you do this, you're going outside UIKit's own forwarding of messages up the responder chain, and the result is undefined.
Also, in your touchesMoved:withEvent:, you're sending touchesBegan:withEvent: to nextResponder, which seems suspicious.
Also, in your touchesMoved:withEvent:, you're passing on the event twice, which seems like a bad idea.
If you want to handle drags conditionally, your best bet is to use a UIPanGestureRecognizer. If the recognizer doesn't accept the event, it will be forwarded up the responder chain normally.
If you are a registered (paid) iOS developer, you should have access to the WWDC 2012 videos. I strongly recommend you watch the “Enhancing User Experience with Scroll Views” video. I won't say more about it because its contents are still under NDA.
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