I want to hook up a UIButton to a piece of code – from what I have found, the preferred method to do this in Swift is still to use the addTarget(target: AnyObject?, action: Selector, forControlEvents: UIControlEvents)
function. This uses the Selector
construct presumably for backwards compatibility with Obj-C libraries. I think I understand the reason for @selector
in Obj-C – being able to refer to a method since in Obj-C methods are not first-class values.
In Swift though, functions are first-class values. Is there a way to connect a UIButton to a closure, something similar to this:
// -- Some code here that sets up an object X let buttonForObjectX = UIButton() // -- configure properties here of the button in regards to object // -- for example title buttonForObjectX.addAction(action: {() in // this button is bound to object X, so do stuff relevant to X }, forControlEvents: UIControlEvents.TouchUpOutside)
To my knowledge, the above is currently not possible. Considering that Swift looks like it's aiming to be a quite functional, why is this? The two options could clearly co-exist for backwards compatibility. Why doesn't this work more like onClick() in JS? It seems that the only way to hook up a UIButton to a target-action pair is to use something that exists solely for backwards compatibility reasons (Selector
).
My use case is to create UIButtons in a loop for different objects, and then hook each up to a closure. (Setting a tag / looking up in a dictionary / subclassing UIButton are dirty semi-solutions, but I'm interested in how to do this functionally, ie this closure approach)
You can replace target-action with a closure by adding a helper closure wrapper (ClosureSleeve) and adding it as an associated object to the control so it gets retained.
This is a similar solution to the one in n13's answer. But I find it simpler and more elegant. The closure is invoked more directly and the wrapper is automatically retained (added as an associated object).
class ClosureSleeve { let closure: () -> () init(attachTo: AnyObject, closure: @escaping () -> ()) { self.closure = closure objc_setAssociatedObject(attachTo, "[\(arc4random())]", self, .OBJC_ASSOCIATION_RETAIN) } @objc func invoke() { closure() } } extension UIControl { func addAction(for controlEvents: UIControlEvents = .primaryActionTriggered, action: @escaping () -> ()) { let sleeve = ClosureSleeve(attachTo: self, closure: action) addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents) } }
Usage:
button.addAction { print("Hello") }
It automatically hooks to the .primaryActionTriggered
event which equals to .touchUpInside
for UIButton.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With