iOS Swift - 如何以编程方式为所有按钮分配默认操作

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

我正在开发一个处于原型开发阶段的应用程序。某些界面元素没有通过情节提要或编程方式分配给它们的任何操作。

根据用户体验指南,我想在应用程序中找到这些 "inactive" 按钮,并让它们在测试期间点击时显示 "feature not available" 警报。这可以通过扩展 UIButton 来完成吗?

除非通过界面生成器或以编程方式分配另一个动作,否则如何将默认动作分配给 UIButton 以显示警报?

由于您不能覆盖扩展中的方法,所以剩下的选项只有:
1. 子类化你的按钮 - 但可能不是你要找的,因为我假设你想对已经存在的按钮使用这个功能
2. method swizzling - 更改现有函数的实现,即 init

我会通过使用 IBOutlet 集合变量来实现这种功能,然后在实现该功能后立即从该集合中删除按钮。像这样:

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
    }
}

IBOutlet 在界面生成器中可见,因此您可以在其中添加多个按钮。

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

我建议做 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/

备注:建议查看上面的文章

作为您问题的准确答案,以下代码片段通常应该是您想要实现的目标:

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!"
    }
}

调用 swizzleButtonAction() 后 - 通过点击 "Apply Swizzling" 按钮 (applySwizzlingTapped)-,"Button" 的选择器应从 buttonTapped 更改为 swizzledAction.


输出:

嗯,你想达到的目标是可以实现的。我使用 UIViewController 扩展并添加闭包作为没有目标的按钮的目标来完成此操作。如果按钮没有动作,则会显示警报。

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)
    }
}

我已经测试过了。希望这可以帮助。编码愉快。