On iOS, if a superview's userInteractionEnabled is NO, then all subviews are disabled as well?
Asked Answered
F

4

34

I thought when a view is touched or tapped on, its handler get called first, and then its superview's handler is called (propagate upward).

But is it true that if the superview's userInteractionEnabled is set to NO, then all subviews and offspring is also disabled for user interaction? What if we want to disable for just the main view but don't want to disable for the subviews?

Felon answered 4/6, 2012 at 11:57 Comment(2)
aha, I was wondering what rule or what mechanism of the UIResponder causes this... even though right now I memorize this as a "fact"Felon
You just adjust hitTest as you need it - it is the most common thing in iOS programmingDisney
T
16

You can't do that,

Instead you would change the arrangment of your views like following:

Main View -> subViews

To

Container View -> Main View that you want to set as inactive
               -> other views that you want to still be active

So your current main view and you current subviews will become siblings, children of a new container view

Tempting answered 4/6, 2012 at 12:7 Comment(2)
"cant do that" What is "that"? Which part of the question are you referring to?Brewmaster
This is completely wrong. You just adjust hitTest as you need it - it is the most common thing in iOS programming. All the other answers explain how to do it, and indeed, it is the single most duplicated QA in iOS on SO!Disney
G
40

You can override hitTest(_:withEvent:) to ignore the view itself, but still deliver touches to its subviews.

class ContainerStackView : UIStackView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let result = super.hitTest(point, with: event)
        if result == self { return nil }
        return result
    }
}
Godsey answered 29/6, 2016 at 1:56 Comment(2)
Very good. Solves a lot of problems. I used it to get an Navigation bar that's passes through touches without ignoring the buttons. I modified this to: "return result is UIControl ? result : nil"Viscountcy
Very good and clean solution. Works perfectlyVista
F
28

If this may be helpful, I found this in Programming iOS 5 by Matt Neuburg, p. 467:

userInteractionEnabled

If set to NO, this view (along with its subviews) is excluded from receiving touches. Touches on this view or one of its subviews "fall through" to a view behind it.

Further more, Apple's Event Handling Guide for iOS says:

The window object uses hit-testing and the responder chain to find the view to receive the touch event. In hit-testing, a window calls hitTest:withEvent: on the top-most view of the view hierarchy; this method proceeds by recursively calling pointInside:withEvent: on each view in the view hierarchy that returns YES, proceeding down the hierarchy until it finds the subview within whose bounds the touch took place. That view becomes the hit-test view.

and Programming iOS 5 by Matt Neuburg, p.485 mentioned that if a view is marked userInteractionEnabled as NO, or hidden as YES, or opacity is close to 0, then the view and its subview will not be traversed by HitTest (and therefore not considered for any touch).

Updated: I suppose it also works this way if we think about parent-child situation in other scenario. For example, in HTML, if there is a div and there are children all under this div, and now this div is set to display: none, then it makes sense that all the children are not displayed as well. So if a parent is set to not interact with the user, it also makes sense that the children do not interact with the user as well.

Felon answered 5/6, 2012 at 6:19 Comment(2)
Excellent info for those people implementing their own hitTest:withEvent:. I wasn't sure whether I needed to check if(subview.userInteractionEnabled) at the superview level, but it appears the default implementation checks its own userInteractionEnabled property.'Hiroshige
what does "fall through" to a view behind it." mean? Does that mean tapping on buttons that are subviews won't work?Brewmaster
T
16

You can't do that,

Instead you would change the arrangment of your views like following:

Main View -> subViews

To

Container View -> Main View that you want to set as inactive
               -> other views that you want to still be active

So your current main view and you current subviews will become siblings, children of a new container view

Tempting answered 4/6, 2012 at 12:7 Comment(2)
"cant do that" What is "that"? Which part of the question are you referring to?Brewmaster
This is completely wrong. You just adjust hitTest as you need it - it is the most common thing in iOS programming. All the other answers explain how to do it, and indeed, it is the single most duplicated QA in iOS on SO!Disney
S
3

First Method

- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
  if ([touch.view.superview isKindOfClass:[SuperViewParent class]]) return FALSE;
  return TRUE;
}

Second Method

UITapGestureRecognizer *r = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(agentPickerTapped:)];
    r.cancelsTouchesInView = NO;
    [agentPicker addGestureRecognizer:r];
Suppository answered 24/10, 2014 at 7:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.