Search code examples
iosswiftuibuttonprotocolsselector

Protocol extension with button and selector


I have some protocol:

@objc protocol SomeProtocol { } 

that I extend for UIViewController instances. In this extension I want to create and add a button, whose selector is also defined in the protocol:

extension SomeProtocol where Self: UIViewController {

    func addSomeButton() {
        let someButton = UIButton()
        someButton.addTarget(self, #selector(someButtonPressed), for: .touchUpInside)
        view.addSubview(someButton)
    }

    @objc func someButtonPressed() {
    }

}

However, I get the error @objc can only be used with members of classes, @objc protocols, and concrete extensions of classes at the definition of someButtonPressed.

Is there any way to achieve this using protocols?

Thanks in advance for any suggestions!


Solution

  • A workaround is adding a closure sleeve to the UIButton instead of a target action as in shown here https://stackoverflow.com/a/41438789/5058116 and copied below for convenience.

    typealias Closure = () -> ()
    
    ///
    class ClosureSleeve {
        let closure: Closure
        init(_ closure: @escaping Closure) {
            self.closure = closure
        }
        @objc func invoke () {
            closure()
        }
    }
    
    extension UIControl {
        func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping Closure) {
            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)
        }
    }
    

    Then simply replace:

    someButton.addTarget(self, #selector(someButtonPressed), for: .touchUpInside)
    

    with:

    someButton.addAction { [weak self] in
            self?.someButtonPressed()
        }
    

    and hey presto.