Adding a closure as target to a UIButton

With iOS 14 Apple has finally added this feature to UIKit. However, someone might still want to use this extension because Apple’s method signature is suboptimal.

iOS 14:

extension UIControl {
    func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping()->()) {
        addAction(UIAction { (action: UIAction) in closure() }, for: controlEvents)
    }
}

pre-iOS 14:

extension UIControl {
    func addAction(for controlEvents: UIControl.Event = .touchUpInside, _ closure: @escaping()->()) {
        @objc class ClosureSleeve: NSObject {
            let closure:()->()
            init(_ closure: @escaping()->()) { self.closure = closure }
            @objc func invoke() { closure() }
        }
        let sleeve = ClosureSleeve(closure)
        addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
        objc_setAssociatedObject(self, "\(UUID())", sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
    }
}

Usage:

button.addAction {
    print("Hello, Closure!")
}

or:

button.addAction(for: .touchUpInside) {
    print("Hello, Closure!")
}

or if avoiding retain loops:

self.button.addAction(for: .touchUpInside) { [unowned self] in
    self.doStuff()
}

(Extension is included here: https://github.com/aepryus/Acheron)

Also note, in theory .primaryActionTriggered could replace .touchUpInside, but it seems to be currently bugged in catalyst, so I’ll leave it as is for now.

Leave a Comment