I'm expanding the functionality of this SO answer.
Mainly I'm focusing on:
- You can add as many handlers as you want.
- You can remove (i.e. cancel) the handler.
- You can receive the
sender
parameter in the handler closure. - You can add "oneshot" handler.
import UIKit
extension UIControl {
private class EventHandler {
let _cb: (UIControl, UIEvent) -> Void
let _oneshot: Bool
init(_ cb: (UIControl, UIEvent) -> Void, oneshot: Bool) {
_cb = cb
_oneshot = oneshot
}
@objc func invoke(sender: UIControl, event: UIEvent) {
_cb(sender, event)
if _oneshot {
sender.off(unsafeAddressOf(self))
}
}
}
typealias EventHandlerId = UnsafePointer<Void>
func on<T: UIControl>(events: UIControlEvents, _ callback: (T, UIEvent) -> Void) -> EventHandlerId {
assert(self.isKindOfClass(T), "The handler must receive \(NSStringFromClass(self.dynamicType)) or UIControl")
return self._on(events, EventHandler({ callback($0 as T, $1) }, oneshot: false))
}
func once<T: UIControl>(events: UIControlEvents, _ callback: (T, UIEvent) -> Void) -> EventHandlerId {
assert(self.isKindOfClass(T), "The handler must receive \(NSStringFromClass(self.dynamicType)) or UIControl")
return self._on(events, EventHandler({ callback($0 as T, $1) }, oneshot: true))
}
func on(events: UIControlEvents, _ callback: () -> Void) -> EventHandlerId {
return self._on(events, EventHandler({ _, _ in callback() }, oneshot: false))
}
func once(events: UIControlEvents, _ callback: () -> Void) -> EventHandlerId {
return self._on(events, EventHandler({ _, _ in callback() }, oneshot: true))
}
private func _on(events: UIControlEvents, _ handler: EventHandler) -> EventHandlerId {
let ptr = unsafeAddressOf(handler)
self.addTarget(handler, action: "invoke:event:", forControlEvents: events)
objc_setAssociatedObject(self, ptr, handler, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
return ptr
}
func off(identifier: EventHandlerId) {
if let handler = objc_getAssociatedObject(self, identifier) as? EventHandler {
self.removeTarget(handler, action: nil, forControlEvents: .AllEvents)
objc_setAssociatedObject(self, identifier, nil, 0)
}
}
}
Usage:
class MyViewController:UIViewController {
@IBOutlet weak var myButton:UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// add event handler
let handle = myButton.on(.TouchUpInside) { (sender, event) in
// `sender` is the control itself.
// `event` is the UIEvent object.
}
// remove event handler
myButton.off(handle)
// event handler can be () -> Void type
myButton.on(.TouchUpInside) { [unowned self] in
// your work
}
// "oneshot" event handler
myButton.once(.TouchUpInside) {
// your work
}
}
}
It's working OK... But, for example:
let btn = UIButton()
btn.on(.TouchUpInside) { (sender: UISwitch, _) in /* ... */ }
^^^^^^^^
This compiles, but I want to make this compile time error.
Currently, as a workaround, I'm doing "assertion":
assert(self.isKindOfClass(T), "The handler must receive \(NSStringFromClass(self.dynamicType)) or UIControl")
And, if possible, I wish the compiler could infer the type of the sender
, like this:
let btn = UIButton()
btn.on(.TouchUpInside) { sender, _ in
sender.titleLabel.text = "Pushed !!"
}
Any ideas?