iOS Swift-как программно назначить действие по умолчанию всем кнопкам

Я работаю с приложением на стадии прототипа разработки. Некоторые элементы интерфейса не имеют никаких действий, назначенных им либо через раскадровку, либо программно.

согласно рекомендациям UX, я хочу найти эти "неактивные" кнопки в приложении и отобразить предупреждение "функция недоступна" при нажатии во время тестирования. Можно ли это сделать через расширение UIButton?

как я могу назначить действие по умолчанию для UIButton, чтобы показать предупреждение если другое действие не назначено через interface builder или программно?

4 ответов


хорошо, что вы пытаетесь достичь можно сделать. Я сделал это, используя 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)
    }
}

я протестировал его. Надеюсь, это поможет. Удачи в кодировании.


поскольку вы не можете переопределить методы в расширениях, единственными оставшимися параметрами являются:
1. подкласс ваших кнопок - но, вероятно, не то, что вы ищете, потому что я предполагаю, что вы хотите использовать эту функциональность для уже существующих кнопок
2. метод swizzling-изменить реализацию существующей функции, т. е. init


как я могу назначить действие по умолчанию для UIButton, чтобы показать предупреждение, если другое действие назначается через interface builder или программно?

Я бы предложил делать метод swizzling:

через swizzling, вставку метода можно заменить с другой во время выполнения, путем изменения отображения конкретного селектор(метод) и функция, содержащая его реализация.

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() - нажав кнопку" Применить Swizzling" (applySwizzlingTapped)-, селектор "Кнопка" должна изменяться с buttonTapped to swizzledAction.


выход:

enter image description here


Я бы реализовал эту функцию, используя переменную коллекции IBOutlet, а затем удалив кнопки из этой коллекции, как только функция будет реализована. что-то вроде этого:--2-->

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 отображается в построителе интерфейса, поэтому вы можете добавить туда несколько кнопок.