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.

Leave a Comment