I have two view controllers appearing on the screen at the same time using view controller containment, implemented the same as in Apple's code example. Call them view controller A (vcA) and view controller B (vcB), and the container view controller (containerVC).
Each vcA and vcB both have a grid of objects, and I want to be able to drag objects from vcA to vcB. More specifically, I want the touch that originates in vcA to hit the touchesMoved:withEvent method in vcB once it is within the bounds vcB.
I have overridden the touchesMoved:withEvent method on the containerVC, track the touch via a hit test, and tried forwarding the touch down the UIView hierarchy like so:
// in the containerVC
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint locationPoint = [[touches anyObject] locationInView:self.view];
UIView* touchIsInViewControllerA = [vcA hitTest:locationPoint withEvent:event];
if (touchIsInViewControllerA) {
NSLog(#"Touch is in vcA");
return;
}
UIView* touchIsInViewControllerB = [vcB hitTest:locationPoint withEvent:event];
if (touchIsInViewControllerB) {
NSLog(#"Touch is in vcB");
[vcB touchesMoved:touches withEvent:event]; // this causes a crash
}
}
This seems to be recursive, with containerVC pushing the touch event down the hierarchy, then vcB passing the touch event back up the hierarchy.
My question: Is there a way to keep vcB from passing the touch event back up the responder chain to containerVC? Or should I approach this a different way - make vcB a delegate of vcA and leave containerVC out of the equation?
Note: I'm guessing a common response will be to give up the VC containment pattern and keep it all in one view controller, but for reasons not shown in this example I think keeping them separate will work better for me - unless it's utter insanity and super hacky to do so...
This problem can be solved easily by setting individual gesture object for each view controller.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (touchIsInViewControllerA) {
// calls a delegate of view controller A
}
else
{
// calls a delegate of view controller B
}
}
Related
I have an OpenGL View with sprites in it which have UIViews with the same position and size on top of the OpenGL view, to make them accessible. The OpenGL View should be able to receive touch events and the UIView on top too. In my UIView class I override the hitTest:withEvent method so the UIViews get the touch input, otherwise only the OpenGL gets the touch:
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
NSEnumerator *reverseE = [self.subviews reverseObjectEnumerator];
UIView *iSubView;
while ((iSubView = [reverseE nextObject]))
{
UIView *viewWasHit = [iSubView hitTest:[self convertPoint:point toView:iSubView] withEvent:event];
if (viewWasHit)
{
return viewWasHit;
}
}
return [super hitTest:point withEvent:event];
}
Now my UIViews receive touch-input but the OpenGL elements underneath don't. How can I change that so both of them receive the touch.
In my opinion in such cases it is best if only your main view receives touches in which you then check if any of the subviews was touched using CGRectContainsPoint for instance. If you really want to override the subviews then I suggest you override the touch methods to call the superview touch methods, for instance to override touchesBegin:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.superview touchesBegan:touches withEvent:event];
//do additional stuff
}
This will work with touches but probably not with gestures since they cancel touch events and have a different pipeline.
For example
I have a custom UIView Class, on the view I put a UIButton.
In the View Class
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
NSLog(#"touch view");
}
But when I touch the button, it intercepted view touch message. Is there a way to recieve touches when user touch view's subview?
I noticed when using UIGestureRecognizer bind to the superview, the event can recieved when user touched subview.
There is no (Apple-allowed or stable) way to "pass" touches to another view. Instead, call a method in your other view from your touches method.
I have an UIView high 30, than i add an UITableView as subview with 30 as origin.y. Because the table remains “under” the main view I cannot select the cells. Consequently I have implemented this method:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Tocco intercettato");
if (oldSet && oldEvent) {
[table touchesEnded:oldSet withEvent:oldEvent];
}
oldSet = touches;
oldEvent = event;
[table touchesBegan:touches withEvent:event];
[table touchesEnded:touches withEvent:event];
}
The taps work, but the slides does not work and consequently I cannot slide the table. Some idea in order to resolve?
Passing the touches from an overlying UIView to an underlying UITableView does not work (as you're seeing, the taps go through but not the scrolling movements). This is because UITableView (as a subclass of UIScrollView) does all sorts of under-the-hood weirdness with the responder chain (your code is also kind of odd, where you call both touchesBegan and touchesEnded from the overlying view's touchesBegan method, but this isn't why you can't scroll the table).
A simple way to achieve what you want (assuming I've understood that correctly) is to override the hitTest:withEvent: method on your overlying view and have the method return the underlying UITableView (instead of self, which is the default implementation):
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UITableView *tv = (UITableView *)[self.superview.subviews objectAtIndex:0];
return tv;
}
Note that this code assumes the UITableView is the first view added to your view controller, and that the overlying UIView is added after that.
Edit: OK, after re-reading your question, I think I understand your question. You have a main view with a height of 30, and to that view you've added a UITableView at y = 30. I think adding this method to your main view will do what you need:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (point.y < self.frame.size.height) {
return self;
} else {
return myTableView; // assumes myTableView is a reference to the
// UITableView that you've added
}
}
Essentially, you're telling the OS to pass all touches on to myTableView if they're outside the bounds of your main view.
However, a simpler way would be to add both the 30-pixel-high main view (the orange thing) and the UITableView as subviews of another view (or view controller). That way, you wouldn't have to do anything kludgy to get your table to behave like a normal table.
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#" touches ");
}
The above method is not calling in my apps. My application description is as under.
I have a MainViewController which loads a ContentViewController in it. ContentViewController has a webview which loads a pdf file.
How to listen the tap of MainViewController's View.
Regards.
I may be not 100% accurate here, but if you place -touchesBegan:withEvent: in your view controller (or its main view) then you will get only those touches that have not been handled by some subviews in the view hierarchy. To intercept all touches you should use UIView subclass for your controller view and override hitTest:withEvent: method in it:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
touchedView = [super hitTest:point withEvent:event];
NSSet* touches = [event allTouches];
// handle touches if you need
return touchedView;
}
For more information see Event delivery section in "Event handling guide" for iOS
I'm using a custom UIGestureRecognizer subclass to track gestures on my InfoView class. The InfoView class is a subview of a custom UITableViewCell subclass called InfoCell.
I've added my gesture recognizer to my root view (the parent view of everything else on screen, because the purpose of my custom gesture recognizer is to allow dragging of InfoCell views between tables). Now, everything works as it should except one thing. I'm using the following code in my UIGestureRecognizer subclass to detect touches on the InfoView view:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UIView *touchView = [[touches anyObject] view];
if ([touchView isKindOfClass:[InfoView class]]) {
// Do stuff
}
The problem here is that the touches on the InfoView object are being intercepted, therefore they are not being forwarded to the UITableView which contains the InfoCell, which is the parent view of the InfoView. This means that I can no longer scroll the table view by dragging on the InfoView view, which is an issue because the InfoView covers the entire InfoCell.
Is there any way I can forward the touches onto the table view so that it can scroll? I've tried a bunch of things already:
[super touchesBegan:touches withEvent:event];
[touchView.superview.superview touchesBegan:touches withEvent:event]; (touchView.superview.superview gets a reference to its parent UITableView)
But nothing has worked so far. Also, the cancelsTouchesInView propery of my UIGestureRecognizer is set to NO, so thats not interfering with the touches.
Help is appreciated. Thanks!
Check out the UIGestureRecognizerDelegate method: - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
If this returns YES it will prevent your gesture recognizer from stomping on the one that UIScrollView is using to detect scrolling.
UIGestureRecognizer has a property "cancelsTouchesInView" which is set to YES by default. This means that touches in a UIView are cancelled when a gesture is recognized. Try to set it to NO to allow the UIScrollView to receive further touch events.
I had a line in my touchesBegan method that set the state property of the gesture recognizer to UIGestureRecognizerStateBegan. Removing this line seems to fix the problem.
You can try add this notification
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer class] == [UIPanGestureRecognizer class]) {
UIPanGestureRecognizer *panGestureRec = (UIPanGestureRecognizer *)gestureRecognizer;
CGPoint point = [panGestureRec velocityInView:self];
if (fabsf(point.x) > fabsf(point.y) ) {
return YES;
}
}
return NO;
}