UIView -- "user interaction enabled" false on parent but true on child?
Asked Answered
R

7

71

It appears that userInteractionEnabled=NO on a parent view will prevent user interaction on all subviews. Is this correct? Is there any way around this?

Riplex answered 4/5, 2011 at 17:18 Comment(4)
yes that is the case, and that's just completely stupid, like so many things in iOS, where are the days when Apple was making simple API's ? i guess that was almost 20 years ago, now the API's are written by philosophers.Mustard
. . . when they're not busy writing stack overflow comments.Eileneeilis
possible duplicate of How to get touches when parent view has userInteractionEnabled set to NO in iOSBlum
BEST ANSWER is here... #3428119Infirm
H
47

That's correct, userInteractionEnabled set to NO on a parent view will cascade down to all subviews. If you need some subviews to have interaction enabled, but not others, you can separate your subviews into two parent views: one with userInteractionEnabled = YES and the other NO. Then put those two parent views in the main view.

Herbie answered 4/5, 2011 at 17:24 Comment(7)
Thanks lazycs. My idea was to use a transparent view to manage the layout of a handful of related popup menu type views. But this won't work for me if the invisible layout view blocks interaction with the stuff beneath it. Hrrmmmm....Riplex
Yeah, the transparent view idea won't work in this case. Could you add the subviews to the parent view directly?Herbie
yeah, could do. It may be best to use a semitransparent background in any case. No biggie.Riplex
i wonder if the various touchesBegan: handlers in the parent view could be implemented to just say "i don't want this event, please pass it to someone below me"Blackett
@orion You could certainly do that, but it wouldn't be useful since touches in iOS bubble from subviews to parent views. If the parent view received a touch, that means that the child already ignored it.Herbie
Bad solution. Try this... #3428119Infirm
Not sure if this works for Apple, but in WPF, we can do this by setting the background to null instead of transparent. Think about it like a piece of glass... you can't see it but you can touch it, vs the glass not being there at all. Again, haven't tested on iOS but will try later.Gao
B
19

You can subclass UIView and override hitTest:withEvent: in a way to pass touch events to a view that you specify (_backView):

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *view = [super hitTest:point withEvent:event];

    if (view == self) {
        view = _backView;
    }

    return view;
}

If the touch event was to be handled by this view it would be passed to "_backView" (that can be an IBOutlet so that it can be set using interface builder) ; and if it was to be handled by any child view just return that child (the result of [super hitTest:point withEvent:event];)

This solution is fine as long as you know what view you need to pass the events to; besides don't know if it has problems since we are returning a view (_backView) that is not a subview of the current UIView !! but it worked fine in my case.

A better solution might be the one mentioned in Disable touches on UIView background so that buttons on lower views are clickable There its mentioned to use -pointInside:withEvent: ; compared to previous solution its better in the way that you don't need to specify a '_backView' to receive the events (the event is simply passed to the next view in chain)! drawback might be that we need to perform -pointInside:withEvent: on all subviews (might be of negligible overhead though)

Bahena answered 30/11, 2011 at 9:54 Comment(2)
thanks for the approach here and the "Disable touches.." link. the approach described there seems pretty clean.Blackett
hitTest is too late... this is the best answer... #3428119Infirm
H
4

Here is solution Create class that inherited from "UIView" and write func that override "inside point"

class TTSView: UIView {


    
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
          for view in self.subviews {
               if view.isUserInteractionEnabled, view.point(inside: self.convert(point, to: view), with: event) {
                   return true
               }
           }

           return false
     }

}

then in storyboard or xib, the superview have to use this class

Hospital answered 2/12, 2020 at 12:16 Comment(1)
That's what i exactly wanted. Very Precise and to the point answer.Overdue
F
2

I just ran into an odd situation. I have a UIView (call this, V), which has a UIButton as a subview. Call this UIButton, button X. Below is the method I'm using for the target/selector of button X. self below is the view V. The sender parameter is button X.

The situation that's causing me an issue is that if I touch another button on my UI (on the nav bar, call this button Y) and then very quickly touch button X, where the the button Y action disables view V, I still get the touch event sent to button X.

- (void) buttonAction: (UIButton *) sender
{
    NSLog(@"superview: %d", sender.superview.userInteractionEnabled);  
    NSLog(@"button itself: %d", sender.userInteractionEnabled);

    // <snip>
}

Here's the output:

2014-12-19 16:57:53.826 MyApp[6161:960615] superview: 0
2014-12-19 16:57:53.826 MyApp[6161:960615] button itself: 1

That is, the button action occurred and the button's superview had user interaction disabled! And the subview still had user interaction enabled!!

For those of you thinking that this seems artificial, on my app's UI, running on an iPad (running iOS 8.1.2), this came up by accident in my use of the app. It was not something I was originally trying to generate.

Thoughts?

My current workaround is given below, but it seems really odd that it's necessary!

- (void) buttonAction: (id) sender
{
    NSLog(@"superview: %d", sender.superview.userInteractionEnabled);  
    NSLog(@"button itself: %d", sender.userInteractionEnabled);
    if (! self.userInteractionEnabled) return;

    // <snip>
}
Fiftieth answered 19/12, 2014 at 23:44 Comment(0)
E
2

I made up a weird solution for this, I had a child view in a tableView cell that I wanted to be touchable but the parent shouldn't have...

Neither of above solutions worked for me, but I found another solution. Go to storyboard and add a tapGestureRecognizer to the parent view to absorb touches on parent view. Problem solved!

Ellington answered 22/4, 2019 at 6:3 Comment(1)
Works well on my textField with a rightView, thanksNanine
R
0

You can use hitTest function in transparent view, to carry out all gestures and taps "through" the view

class PassthroughView: UIView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        return view == self ? nil : view
    }
}
Recrudescence answered 12/1 at 14:43 Comment(0)
P
-1

I´m doing this in a xib which is my "custom alert controller"-view.

for (UIView *subview in self.superview.subviews) {

    if (![subview isEqual:self]) {
        subview.userInteractionEnabled = NO;
    }
}
Pitt answered 9/1, 2018 at 16:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.