UIView, its superview and touchesBegan:
Asked Answered
S

2

6

Suppose I have a UIViewController subclass that takes care of some UIViews. These UIViews are added as subviews of the UIViewController's view property):

UIViewController:

- (void)viewDidLoad {
    UIImageView *smallImageView...
    [self.view addSubview:smallImageView];

    UIButton *button..
    [self.view addSubview:button];

    UIView *bigUIView.. // covers the entire screen (frame = (0.0, 0.0, 1024.0, 768.0))
    [self.view addSubview:bigUIView];
...
}

AFAIK, since the bigUIView is the frontmost view and covers the entire screen, it will receive the touchesBegan:withEvent: and the other views, such as button won't receive any touch event.

In my application bigUIView must be the topmost view because it holds the main user interface objects (CALayers, actually, the main game objects) that, when animated, must be on top of all other secondary UI elements (UIButtons, etc). However, I'd like to be able to still have the UIButtons and other objects in the Responder Chain.

I tried implementing the following in the bigUIView class:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    
    [self.superview touchesBegan:touches withEvent:event];

    ... hit test the touch for this specific uiview..
}

Notes:

  1. bigUIView's superview refers to the UIViewController's view property, does it propagate the touch event to all of it's subviews?
  2. bigUIView must have userInteractionEnabled = YES because it also handles user input.
  3. I can't bring the button/smallImageView to front because it would appear above the main game objects (sublayers of bigUIView).

Thanks in advance.

Stannary answered 7/2, 2011 at 14:43 Comment(0)
M
12

Try overriding pointInside:withEvent: in the bigUIView class to return NO if the point is inside the frame rectangle of one of the other subviews of the background view. Since it's a sibling of these other views, the hit testing mechanism will continue testing the other subviews and find the one corresponding to the point the user touched.

Alos, consider implementing the event phase methods (-touchesBegan:withEvent:, etc.) in your UIViewController subclass rather than in the bigUIView class. iOS inserts view controllers behind their views in the responder chain, so event messages will bubble up to the view controller unless one of the other subviews (for example, a button) intercepts it. The view controller can then send whatever messages you like to any of its views.

Minutes answered 7/2, 2011 at 16:7 Comment(5)
Thanks jlehr, pointInside:withEvent: is the solution. By returning NO when the point is inside the frame rectangle of one of the other subviews cause the bigUIView's -touchesBegan:withEvent: method not to be called and the button's action being triggered. Note that this cause a coupling between the bigUIView and the button. How can I intercept the event phase methods only in the UIViewController when the bigUIView cover the whole screen? Creating a different method signature in bigUIView class and have it delegated by the "catch all" UIViewController class?Stannary
No, don't implement the event phase methods in bigUIView at all. That way the messages will get forwarded up the responder chain until they reach an object that has an overridden implementation (for example UIButton or your custom UIViewController subclass).Minutes
With respect to coupling, consider implementing a delegate method in your view controller that the bigUIView instance can call to make the actual decision. That way the view controller would be responsible for calculating which view gets the message and there'd be no coupling at all.Minutes
Ok, but what if my bigUIView needs the -touchesBegan:withEvent: delegate method arguments to work properly? Overriding will cause the Responder Chain to be stopped, do you suggest creating another event phase 'sibling' methods signatures and implement them in the bigUIView (such methods would be called by the UIViewController)?Stannary
One option is to route all touch event messages through the controller, and then let the controller decide what happens in response (in part by sending messages to one or more views). Another option would be to include a call to [super touchesBegan:withEvent:] at the end of each of the overridden methods in bigUIView, which would allow the messages to continue up the responder chain so that they'd be received by both the bigUIView and its controller.Minutes
I
0

Normally in the responder chain, events are passes to superviews, not to subviews. Anyway, I would convert points in events to the button's local coordinate system, and if it hits, send an action to the controller.

Ir answered 7/2, 2011 at 16:9 Comment(2)
I appreciate the commentary, It's an interesting solution. However I'm trying to avoid this high coupling between the bigUIView (which belongs to the "game package" (CALayers)) and other UI elements (created in the IB). ThanksStannary
No problem. bigUIView still need not work with other UI elemets. But I agree jlehr's solution seems the way to go!Ir

© 2022 - 2024 — McMap. All rights reserved.