My app has numerous buttons whose behavior depends on the events they generate: .touchDown
, .touchUpInside
, .touchUpOutside
, etc.
I use the typical Target-Action mechanism to set these behaviors:
class MyViewController: UIViewController {
let redButton = UIButton()
let blueButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
self.redButton.addTarget(self, action: #selector(redButtonTouchDown(_:)), for: .touchDown)
self.redButton.addTarget(self, action: #selector(redButtonTouchUpInside(_:)), for: .touchUpInside)
self.redButton.addTarget(self, action: #selector(redButtonTouchUpOutside(_:)), for: .touchUpOutside)
self.blueButton.addTarget(self, action: #selector(blueButtonTouchDown(_:)), for: .touchDown)
self.blueButton.addTarget(self, action: #selector(blueButtonTouchUpInside(_:)), for: .touchUpInside)
self.blueButton.addTarget(self, action: #selector(blueButtonTouchUpOutside(_:)), for: .touchUpOutside)
}
@objc func redButtonTouchDown(_ sender: UIButton) {
print("redButton touchDown")
}
@objc func redButtonTouchUpInside(_ sender: UIButton) {
print("redButton touchUpInside")
}
@objc func redButtonTouchUpOutside(_ sender: UIButton) {
print("redButton touchUpOutside")
}
@objc func blueButtonTouchDown(_ sender: UIButton) {
print("blueButton touchDown")
}
@objc func blueButtonTouchUpInside(_ sender: UIButton) {
print("blueButton touchUpInside")
}
@objc func blueButtonTouchUpOutside(_ sender: UIButton) {
print("blueButton touchUpOutside")
}
}
This is very verbose. My question is: is there a more efficient way to do this without requiring separate functions for each event type? Here is how the code may look:
class MyViewController: UIViewController {
let redButton = UIButton()
let blueButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
self.redButton.addActions(for: [.touchDown, .touchUpInside, .touchUpOutside], #selector(redButtonEvents(_:_:)))
self.blueButton.addActions(for: [.touchDown, .touchUpInside, .touchUpOutside], #selector(blueButtonEvents(_:_:)))
}
func redButtonEvents(_ sender: UIButton, _ event: UIControl.Event) {
switch event {
case .touchDown:
print("redButton touchDown")
case .touchUpInside:
print("redButton touchUpInside")
case .touchUpOutside:
print("redButton touchUpOutside")
default:
break
}
}
func blueButtonEvents(_ sender: UIButton, _ event: UIControl.Event) {
switch event {
case .touchDown:
print("blueButton touchDown")
case .touchUpInside:
print("blueButton touchUpInside")
case .touchUpOutside:
print("blueButton touchUpOutside")
default:
break
}
}
}
I have been looking into various solutions that don't use Selector
but instead wrap the method in a closure. I'm afraid I'm not yet experienced enough in obj-c or Swift to see how to refactor my code to do this and also capture my target and object weakly, identify my target, and disambiguate between the senders, etc.
As per this answer: Adding a closure as target to a UIButton
class ClosureSleeve {
let closure: ()->()
init (_ closure: @escaping ()->()) {
self.closure = closure
}
@objc func invoke () {
closure()
}
}
extension UIControl {
func addAction(for controlEvents: UIControl.Event, _ closure: @escaping ()->()) {
let sleeve = ClosureSleeve(closure)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
objc_setAssociatedObject(self, String(format: "[%d]", arc4random()), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
I'm trying to avoid constantly having to write something like this, which is also quite verbose
class MyViewController: UIViewController {
let redButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
// this is also quite verbose
for event in [UIControl.Event]([.touchDown, .touchUpInside, .touchUpOutside]) {
self.button.addAction(for: event) { [weak self] in
if let strongSelf = self {
strongSelf.redButtonActions(strongSelf.button, event)
}
}
}
}
func redButtonActions(_ sender: UIButton, _ event: UIControl.Event) {
// handle red button actions
}
}
Help is greatly appreciated. Thank you.