Hooking up UIButton to closure? (Swift, target-action)
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).
Swift 3 and 4
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.
The general approach for anything you think should be in the libraries but isn't: Write a category. There's lots of this particular one on GitHub but didn't find one in Swift so I wrote my own:
=== Put this in its own file, like UIButton+Block.swift ===
import ObjectiveCvar ActionBlockKey: UInt8 = 0// a type for our action block closuretypealias BlockButtonActionBlock = (sender: UIButton) -> Voidclass ActionBlockWrapper : NSObject { var block : BlockButtonActionBlock init(block: BlockButtonActionBlock) { self.block = block }}extension UIButton { func block_setAction(block: BlockButtonActionBlock) { objc_setAssociatedObject(self, &ActionBlockKey, ActionBlockWrapper(block: block), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) addTarget(self, action: "block_handleAction:", forControlEvents: .TouchUpInside) } func block_handleAction(sender: UIButton) { let wrapper = objc_getAssociatedObject(self, &ActionBlockKey) as! ActionBlockWrapper wrapper.block(sender: sender) }}
Then invoke it like this:
myButton.block_setAction { sender in // if you're referencing self, use [unowned self] above to prevent // a retain cycle // your code here}
Clearly this could be improved, there could be options for the various kinds of events (not just touch up inside) and so on. But this worked for me.It's slightly more complicated than the pure ObjC version because of the need for a wrapper for the block. Swift compiler does not allow storing the block as "AnyObject". So I just wrapped it.
This is not necessarily a "hooking," but you can effectively achieve this behavior by subclassing UIButton:
class ActionButton: UIButton { var touchDown: ((button: UIButton) -> ())? var touchExit: ((button: UIButton) -> ())? var touchUp: ((button: UIButton) -> ())? required init?(coder aDecoder: NSCoder) { fatalError("init(coder:)") } override init(frame: CGRect) { super.init(frame: frame) setupButton() } func setupButton() { //this is my most common setup, but you can customize to your liking addTarget(self, action: #selector(touchDown(_:)), forControlEvents: [.TouchDown, .TouchDragEnter]) addTarget(self, action: #selector(touchExit(_:)), forControlEvents: [.TouchCancel, .TouchDragExit]) addTarget(self, action: #selector(touchUp(_:)), forControlEvents: [.TouchUpInside]) } //actions func touchDown(sender: UIButton) { touchDown?(button: sender) } func touchExit(sender: UIButton) { touchExit?(button: sender) } func touchUp(sender: UIButton) { touchUp?(button: sender) }}
Use:
let button = ActionButton(frame: buttonRect)button.touchDown = { button in print("Touch Down")}button.touchExit = { button in print("Touch Exit")}button.touchUp = { button in print("Touch Up")}