patternswiftMinor
Closure as UIControlEvents handler
Viewed 0 times
uicontroleventshandlerclosure
Problem
I'm expanding the functionality of this SO answer.
Mainly I'm focusing on:
```
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
func on(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(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(s
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
senderparameter 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
func on(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(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(s
Solution
Consider for a moment, if you will, how we typically handle a user tapping a button. We typically will do so with an
The default is to give us a method that takes an object of type
I don't think it's useful to allow objects that aren't of type
But moreover, I actually think it'd also be useful if we also had the option of passing a reference to the handler itself (the
I'd change your
and remove the
For starters, I'd do this because even though the version you've implemented is easier, there's not a particularly good reason for this aspect of the implementation to be private.
But moreover, by making the
Instead, we should have a method that accepts an
The reason, by the way, I prefer
Other than these two observations, my main complaint here would be naming. Large, descriptive variable names don't make your compiled program take up any more space on the disk, or any more RAM, or run any slower. The only drawback to large, descriptive variable names is that it makes your code more readable. Wait... that's not a drawback.
Moreover, this isn't Objective-C. Something declares as
Well, I was going to post some sample code... but now I'm going to be distracted with writing a bug report because the compiler keeps crashing trying to figure out the sample I wrote. And that means there's something wrong with it, but the compiler doesn't feel like telling me, so I can't figure it out...
@IBAction method. What are the Apple suggested defaults when we hook up a button this way?The default is to give us a method that takes an object of type
AnyObject as its only parameter. We can, of course, change the type to specify UIButton (or whatever UIControl subclass our element actually is). We can also change the sent arguments. We can choose between None, Sender, and Sender and Event.I don't think it's useful to allow objects that aren't of type
UIControl as the parameter. But, I do feel like we're missing methods to accept a handler that just passes the Sender argument. For me, this is the most common way I hook up methods (mostly because it's the default, but sometimes because I want the sender). Meanwhile, I don't think I've ever cared about the Event argument.But moreover, I actually think it'd also be useful if we also had the option of passing a reference to the handler itself (the
EventHandlerId returned when the handler is added) so that potentially we could then remove the event conditionally and without having to keep track of the handler reference on our own. (Although admittedly, I haven't played too terribly much with this aspect of your code, so I'm not sure how possible or manageable that might be.)I'd change your
typealias to this:typealias EventHandlerRef = UnsafePointerand remove the
private access modifier from your EventHandler class, and add version of all your methods that take an EventHandler argument.For starters, I'd do this because even though the version you've implemented is easier, there's not a particularly good reason for this aspect of the implementation to be private.
But moreover, by making the
EventHandler class private, you've tricked yourself into writing the off function in a not-so-great way. The off function takes an EventHandler pointer, finds the event, then removes it as a target of the UI element. And that's fine for everything you've done... except for your "oneshot" implementation... where the EventHandler object finds its own reference to pass to the control's off method so that method can use the pointer to find the object (which we already knew) and remove it.Instead, we should have a method that accepts an
EventHandler object. The method that accepts the EventHandlerRef grabs the EventHandler that the reference points to and calls the method that accepts the EventHandler.The reason, by the way, I prefer
EventHandlerRef to EventHandlerId is two-fold. First of all, the value it holds is hardly an id in the sense that one might think in a database sense, but most importantly, suffixes pointer typedefs with Ref is just how Apple does it throughout their own libraries. Any other iOS developer will see EventHandlerRef and know that this is a pointer to an EventHandler thing, even if they don't know what sort of thing EventHandler is. Meanwhile, EventHandlerId seems slightly arbitrary.Other than these two observations, my main complaint here would be naming. Large, descriptive variable names don't make your compiled program take up any more space on the disk, or any more RAM, or run any slower. The only drawback to large, descriptive variable names is that it makes your code more readable. Wait... that's not a drawback.
Moreover, this isn't Objective-C. Something declares as
private truly is private in Swift. The underscores just make things look weird here. So, here's my take on your code, including the name changes I'd make:Well, I was going to post some sample code... but now I'm going to be distracted with writing a bug report because the compiler keeps crashing trying to figure out the sample I wrote. And that means there's something wrong with it, but the compiler doesn't feel like telling me, so I can't figure it out...
Code Snippets
typealias EventHandlerRef = UnsafePointer<EventHandler>Context
StackExchange Code Review Q#73364, answer score: 3
Revisions (0)
No revisions yet.