iOS Swift - How to assign a default action to all buttons programmatically

浪子不回头ぞ 提交于 2019-12-04 05:46:31

Well what you are trying to achieve can be done. I have done this using a UIViewController extension and adding a closure as the target of a button which does not have a target. In case the button does not have an action an alert is presented.

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.checkButtonAction()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

    }
    @IBAction func btn_Action(_ sender: UIButton) {

    }

}

extension UIViewController{
    func checkButtonAction(){
        for view in self.view.subviews as [UIView] {
            if let btn = view as? UIButton {
                if (btn.allTargets.isEmpty){
                    btn.add(for: .touchUpInside, {
                        let alert = UIAlertController(title: "Test 3", message:"No selector", preferredStyle: UIAlertControllerStyle.alert)

                        // add an action (button)
                        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))

                        // show the alert
                        self.present(alert, animated: true, completion: nil)
                    })
                }
            }
        }

    }
}
class ClosureSleeve {
    let closure: ()->()

    init (_ closure: @escaping ()->()) {
        self.closure = closure
    }

    @objc func invoke () {
        closure()
    }
}

extension UIControl {
    func add (for controlEvents: UIControlEvents, _ closure: @escaping ()->()) {
        let sleeve = ClosureSleeve(closure)
        addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
        objc_setAssociatedObject(self, String(format: "[%d]", arc4random()), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
    }
}

I have tested it. Hope this helps. Happy coding.

As you cannot override methods in extensions, the only remaining options are:
1. subclass your buttons - but probably not what you're looking for, because I assume you want to use this functionality for already existing buttons
2. method swizzling - to change the implementation of existing function, i.e. init

How can I assign a default action to UIButton to show an alert unless another action is assigned via interface builder or programmatically?

I would suggest to do method swizzling:

Through swizzling, the implementation of a method can be replaced with a different one at runtime, by changing the mapping between a specific selector(method) and the function that contains its implementation.

https://www.uraimo.com/2015/10/23/effective-method-swizzling-with-swift/

Remark: I would recommend to check the article above.

As an exact answer for your question, the following code snippet should be -in general- what are you trying to achieve:

class ViewController: UIViewController {
    // MARK:- IBOutlets
    @IBOutlet weak var lblMessage: UILabel!

    // MARK:- IBActions
    @IBAction func applySwizzlingTapped(_ sender: Any) {
        swizzleButtonAction()
    }

    @IBAction func buttonTapped(_ sender: Any) {
        print("Original!")
        lblMessage.text = "Original!"
    }
}

extension ViewController {
    func swizzleButtonAction() {
        let originalSelector = #selector(buttonTapped(_:))
        let swizzledSelector = #selector(swizzledAction(_:))

        let originalMethod = class_getInstanceMethod(ViewController.self, originalSelector)
        let swizzledMethod = class_getInstanceMethod(ViewController.self, swizzledSelector)

        method_exchangeImplementations(originalMethod, swizzledMethod)
    }

    func swizzledAction(_ sender: Any) {
        print("Swizzled!")
        lblMessage.text = "Swizzled!"
    }
}

After calling swizzleButtonAction() -by tapping the "Apply Swizzling" button (applySwizzlingTapped)-, the selector of "Button" should changed from buttonTapped to swizzledAction.


Output:

I would implement this kind of function by using IBOutlet collection variable, and then removing buttons from that collection as soon as the feature is implemented. something like this:

class ViewController {
    @IBOutlet var inactiveBtns: [UIButton]!

    func viewDidLoad() {
        inactiveBtns.forEach { (button) in
           button.addTarget(self, action: #selector(showDialog), for: UIControlEvents())
        }
    }

    func showDialog() {
        // show the dialog
    }
}

That IBOutlet is visible in the interface builder, so you can add multiple buttons in there.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!