22

My application has two overlapping UIButtons. The top button may be disabled at times. However, in this case any touch events it receives seem to be passed down to the underlying view, which in my case is the other button.

What I need is the top button intercepting all touches and preventing them from reaching the bottom button, even in disabled state (I would be happy even if it's designated action is invoked in the disabled state).

So far I've tried:

[topButton setUserInteractionEnabled:YES];

and

[topButton setExclusiveTouch:YES];

though the later case is probably undesirable since I still need the bottom button responding to events if it's the first view clicked. Either way none of them work.

Mihai Damian
  • 11,193
  • 11
  • 59
  • 81

6 Answers6

17

I added the following method to superview of the disabled button:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (!self.watchButton.enabled &&
        [self.watchButton pointInside:[self convertPoint:point toView:self.watchButton]
                            withEvent:nil]) {
        return nil;
    }
    return [super hitTest:point withEvent:event];
}

This works well with UITableView cells.

kolyuchiy
  • 5,465
  • 2
  • 23
  • 31
9

I managed to get this working as I needed with a slightly modified version of @kolyuchiy's answer above. I overrode hitTest:withEvent: method in my UIButton subclass, returning self when disabled and the point is within the view's frame so that the touch is consumed, but the button's event handling isn't invoked.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if ( !self.enabled && [self pointInside:[self convertPoint:point toView:self] withEvent:event] )
    {
        return self;
    }
    return [super hitTest:point withEvent:event];
}

Swift version:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    if !isEnabled, self.point(inside: convert(point, to: self), with: event) {
        return self
    }
    return super.hitTest(point, with: event)
}
Leon
  • 3,614
  • 1
  • 33
  • 46
Patrick Lynch
  • 2,742
  • 1
  • 16
  • 18
4

After trying @kolyuchiy (which requires to modify UIButton behaviour outside of its scope) and @patrick-lynch (which just didn't work for me) solutions came with more elegant one, which applies to UIButton subclass:

private static let dummyView: UIView = {
    let view = UIView(frame: .zero)
    view.userInteractionEnabled = true
    return view
}()

public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
    if !enabled && pointInside(point, withEvent: event) {
        return self.dynamicType.dummyView
    }
    return super.hitTest(point, withEvent: event)
}
rshev
  • 4,086
  • 1
  • 23
  • 32
4

I know that is late, but I was reading a lot of similar questions and none of the answers I read was helpful for me (some of them because didn't work and others because I didn't like that solution), so I found a better solution forme and I share it here to help possible future users which read this question.

Solution

I always have a container with some (or only one) buttons inside. So I connect the IBOutlet of the buttons container to the UIViewController or UIView which I'm working with:

@property (weak, nonatomic) IBOutlet UIView *buttonsContainer;

And I set a nil single tap gesture recognizer like below:

[self.buttonsContainer addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:nil]];

With this workaround, if a button is disabled, the buttonsContainer captures this event and do nothing with it.

Hope this helps.

Marc Estrada
  • 1,657
  • 10
  • 20
3

What I did is placing UIImageView of same size with UIButton just under the UIButton. Then even though you disable UIButton, touch events do not propagate UIImageView.

heemin
  • 321
  • 3
  • 12
1

Create a subclass of the button, then override these methods so that the button will catch events but ignore them if some property of the button is set to NO:

touchesBegan:withEvent:
touchesCancelled:withEvent:
touchesEnded:withEvent:
touchesMoved:withEvent:

These are methods of UIResponder.

Have them just call their [super ...] method if you want the event handled, otherwise just don't call it and return if you want the events "eaten".

Also see this in case it's necessary: Observing pinch multi-touch gestures in a UITableView

Community
  • 1
  • 1
Nimrod
  • 5,168
  • 1
  • 25
  • 39
  • 1
    Looks like these methods aren't called at all when the button is disabled. – Mihai Damian Jan 14 '10 at 08:10
  • Yea, that's what I figured (which is why I said "some property") which is why you may need to either use the second method, or do something other than disable the button like simulate it being disabled somehow. The second method probably will be a pain since the UIApplication (which calls sendEvent in the window) probably is what's ignoring disabled UIControls. – Nimrod Jan 14 '10 at 16:42