Reputation: 3058
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!
Upvotes: 0
Views: 1596
Reputation: 3058
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.
Upvotes: 3
Reputation: 457
You need to supply a Selector
requirement in the protocol. This is because you can only apply the @objc
attribute to an NSObject
. The only reason to mark a protocol @objc
is for optional methods.
@objc protocol SomeProtocol {
var action: Selector { get }
}
Change the extension to this:
extension SomeProtocol where Self: UIViewController {
func addSomeButton() {
let someButton = UIButton()
someButton.addTarget(self, action: action, for: .touchUpInside)
view.addSubview(someButton)
}
}
Now, this works:
extension UIViewController: SomeProtocol {
@objc func buttonPressed(sender: UIButton) {
print("Button pressed")
}
var action: Selector {
return #selector(buttonPressed(sender:))
}
}
Usage:
let vc: myViewController: MyViewController!
func doSomething() {
vc.addSomeButton()
}
Upvotes: -1