What's the simplest way to receive tap events on a disabled UIButton?
Asked Answered
A

4

7

I have a UIButton on a form, and want to put it in a disabled state when the form is incomplete. However, I still want to be able to detect if a user attempts to press the button even in its disabled state so that the interface can let the user know that certain required fields on the form are not filled-in yet (and perhaps scroll to that field and point it out, etc.).

There doesn't seem to be any straightforward way to do this. I tried simply attaching a UITapGestureRecognizer to the UIButton but it doesn't respond when the button is in a disabled state.

I'd like to avoid subclassing UIButton if possible, unless it's the only way.

Adonai answered 9/9, 2014 at 22:11 Comment(2)
What you want is to change the appearance of the button, not disable it. Then use form validation to determine what to do when the button is in fact tapped.Ketchup
To the user that distinction is irrelevant. How to best implement it is why I asked the question.Adonai
W
22

Create a fallback button. Put it behind the main button. Set its background and text colors to [UIColor clearColor] to ensure it won't show up. (You can't just set its alpha to 0 because that makes it ignore touches.) In Interface Builder, the fallback button should be above the main button in the list of subviews, like this:

buttons in subview list

Give it the same frame as the main button. If you're using autolayout, select both the main and fallback buttons and create constraints to keep all four edges equal.

When the main button is disabled, touches will pass through to the fallback button. When the main button is enabled, it will catch all the touches and the fallback button won't receive any.

Connect the fallback button to an action so you can detect when it's tapped.

Whereunto answered 9/9, 2014 at 22:20 Comment(5)
Hadn't thought of that! Let me give it a try.Adonai
Updated to clarify how to order the buttons.Whereunto
I disagree. It is a perfectly reasonable approach. It decouples the logic of the real button from the fake button, is simple to implement and still allows you to receive user interaction on the hidden button to perform some (non-dangerous) action. It doesn't require any special handling of the regular button. It's not a particularly reusable approach, but for a one-off as I needed it works well.Adonai
Just to clarify, if being done programmatically, the fallback button should be inserted below the main button (call insertSubview(fallbackButton, belowSubview: mainButton). In the Interface Builder, it appears above, but hierarchically it exists under the main button.Carnay
This works perfectly fine. Just make sure fallbackButton is behind your primary button. userInteraction = false will propagate the event to the fallback buttonArmentrout
M
2

Based on @rob idea, I sub-class a UIButton, and add a transparent button before someone addSubview on this button.

This custom UIButton will save many time about adjusting the UI components on the storyboard.


Update 2018/08

It works well, and add some enhanced detail to this sub-class. I have used it for 2 years.

    class TTButton : UIButton {

        // MARK: -
        private lazy var fakeButton : UIButton! = self.initFakeButton()
        private func initFakeButton() -> UIButton {
            let btn = UIButton(frame: self.frame)

            btn.backgroundColor = UIColor.clearColor()
            btn.addTarget(self, action: #selector(self.handleDisabledTouchEvent), forControlEvents: UIControlEvents.TouchUpInside)
            return btn
        }

        // Respect this property for `fakeButton` and `self` buttons
        override var isUserInteractionEnabled: Bool {
            didSet {
               self.fakeButton.isUserInteractionEnabled = isUserInteractionEnabled
            }
        }

        override func layoutSubviews() {
            super.layoutSubviews()

            // NOTE: `fakeButton` and `self` has the same `superView`. 
            self.fakeButton.frame = self.frame
        }

        override func willMoveToSuperview(newSuperview: UIView?) {
            //1. newSuperView add `fakeButton` first.  
            if (newSuperview != nil) {
                newSuperview!.addSubview(self.fakeButton)
            } else {
                self.fakeButton.removeFromSuperview()
            }
            //2. Then, newSuperView add `self` second.
            super.willMoveToSuperview(newSuperview)
        }

        @objc private func handleDisabledTouchEvent() {
            //NSLog("handle disabled touch event. Enabled: \(self.enabled)")
            self.sendActionsForControlEvents(.TouchUpInside)
        }

    }
Mistrustful answered 25/8, 2016 at 9:31 Comment(0)
P
0

Riffing off @AechoLiu's answer, here is how I implemented this in a UIButton subclass:

// Properties
private lazy var disabledButton: UIButton = {
    let disabledButton = UIButton(frame: frame)
    disabledButton.backgroundColor = .clear
    disabledButton.addTarget(self, action: #selector(disabledButtonTouchUpInside(_:)), for: .touchUpInside)
    
    return disabledButton
}()

// Method overrides
override func awakeFromNib() {
    super.awakeFromNib()
    
    // Do any other set up you need
    
    if let superview = superview {
        // Place the disabled button behind self in the hierarchy.
        // When `self` is disabled it will pass its touches through.
        superview.insertSubview(disabledButton, belowSubview: self)
    }
}

override func layoutSubviews() {
    super.layoutSubviews()
    
    // Sync `disabledButton` frame to main button's
    disabledButton.frame = frame
}

// Button handling
@objc private func disabledButtonTouchUpInside(_ sender: UIButton) {
    // Handle as required. You could:
    //  - Trigger the button's normal action using sendActions(for: .touchUpInside).
    //  - Add a delegate protocol to the button subclass and triggered one of its methods here.
}
Photon answered 3/11, 2023 at 14:8 Comment(0)
R
-8

You have a great misunderstanding of user experience.

If a button is disabled, it is meant to be non-interactable. You can not click on a disabled button, that is why it is disabled.

If you want to warn users about something when that button is clicked (e.g. form not filled correctly or completely), you need to make that button enabled. And just warn users when they click on it, instead of proceeding further with app logic.

Or you can keep that button disabled until form criteria are met, but show what is wrong with the form using another way, like putting exclamation marks near text fields, changing text field colors to red, or something like that...

But never try to add gesture recognizers, or hidden fallback buttons to a disabled button.

Check those and let me know if you see a disabled button:

https://airbnb.com/signup_login

https://spotify.com/us/signup/

https://netflix.com/signup/regform

https://reddit.com/register/

https://twitter.com/signup

https://facebook.com/r.php

https://appleid.apple.com/account

https://accounts.google.com/SignUp

https://login.yahoo.com/account/create

https://signup.live.com/signup

All the proceed buttons on these websites are always enabled, and you get feedback about what is wrong when you try to continue.

And here is really good answer: https://ux.stackexchange.com/a/76306

Long story short: disabled UI elements meant to be not-interactable. Trying to make them interactable while they are disabled is the same to making them enabled in the first place.

So, for your question's case, it is just a styling issue. Just try styling your button, instead of making it disabled/enabled.

Retch answered 9/9, 2014 at 22:26 Comment(14)
I disagree with your assessment. User experience is about covering all scenarios. If the button appears disabled and the user doesn't try to press it, nothing is lost. However if the user doesn't understand why the button is disabled, they may tap it anyway, at which point the program will politely inform them exactly why it is disabled. Pressing the button still doesn't accept the form, because it's disabled, but there's no reason why the app can't give the user additional feedback if they try.Adonai
Making the button enabled would be misleading, because it would imply there is nothing wrong, when in fact, there is.Adonai
Ok I get basic user interface theory. I've explained my reasons. There's no need to post this.Adonai
Check those and let me know if you see a disabled button: twitter.com/signup facebook.com/r.php appleid.apple.com/accountRetch
You can even check this "Add Comment" button when you are replying to my comment.Retch
It looks like you still have not checked links I provided. By the way, accepted answer is not exactly what you asked for. Adding another button does not make the disabled one to receive touches.Retch
dear downvoters, please read my answer one more time, see example links i added, and explain which point you still do not understand.Retch
I completely disagree with one point in particular: if the operation is not possible, do not indicate to the user that they can proceed and then tell them they can't. Communicate elsewhere on the interface what is missing, and keep the button disabled until it is valid.Adonai
Furthermore, it makes sense in my opinion, to display this information more prominently if the proceed button is tapped while disabled, as it would likely indicate that the user is not aware there is an error on the form.Adonai
The point here is that it is your own opinion and your opinion is not the correct UX practice. You can see it by visiting links in my answer, and this ux.stackexchange.com/a/76306Retch
If you think there is such a thing as "correct" UX practice, you clearly don't get UX.Adonai
The study is interesting and clearly there is something to be learned from it. But supposing the situations tested are the only valid solutions is a mistake. The goal of any UI should be to provide the user with the means to accomplish their goal with minimal friction. Waiting until a user tries to proceed before giving them useful information is a waste of their time and can be very frustrating.Adonai
You clearly don't get. UX is not set of strict laws or rules. It is just conventions and common practices preferred by the leading/innovating players, and adopted by users. Like masking password fields using asterisks or circles. The fact that there can be more then one "correct UX" does not mean there is no such thing as "correct UX". "Correct UX" do exist. I hope one day in the future you can understand. Thanks.Retch
Well good, we both don't get it. That's better than you dictating what "correct" UX is. UX is an art and people should be free to experiment and try new things. The idea that something like tapping a disabled button is necessarily "wrong" is an unhelpful attitude. Maybe it's wrong for your interface. Doesn't mean it is for mine.Adonai

© 2022 - 2024 — McMap. All rights reserved.