Hooking up UIButton to closure? (Swift, target-action) Hooking up UIButton to closure? (Swift, target-action) ios ios

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")}