Can I make #selector refer to a closure in Swift?
Solution 1
As @gnasher729 notes, this is not possible because selectors are just names of methods, not methods themselves. In the general case, I'd use dispatch_after
here, but in this particular case, the better tool IMO is UIView.animateWithDuration
, because it's exactly what that function is for, and it's very easy to tweak the transition:
UIView.animateWithDuration(0, delay: 0.5, options: [], animations: {
self.view.backgroundColor = UIColor.whiteColor()
self.view.alpha = 1.0
}, completion: nil)
Solution 2
Not directly, but some workarounds are possible. Take a look at the following example.
/// Target-Action helper.
final class Action: NSObject {
private let _action: () -> ()
init(action: @escaping () -> ()) {
_action = action
super.init()
}
@objc func action() {
_action()
}
}
let action1 = Action { print("action1 triggered") }
let button = UIButton()
button.addTarget(action1, action: #selector(action1.action), forControlEvents: .TouchUpInside)
Solution 3
I tried this for UIBarButtonItem at least:
private var actionKey: Void?
extension UIBarButtonItem {
private var _action: () -> () {
get {
return objc_getAssociatedObject(self, &actionKey) as! () -> ()
}
set {
objc_setAssociatedObject(self, &actionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
convenience init(title: String?, style: UIBarButtonItemStyle, action: @escaping () -> ()) {
self.init(title: title, style: style, target: nil, action: #selector(pressed))
self.target = self
self._action = action
}
@objc private func pressed(sender: UIBarButtonItem) {
_action()
}
}
Then you can do this:
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Test", style: .plain, action: {
print("Hello World!")
})
Solution 4
It is now possible. I've created a gist for block-based selectors in Swift 4.
https://gist.github.com/cprovatas/98ff940140c8744c4d1f3bcce7ba4543
Usage:
UIButton().addTarget(Selector, action: Selector { debugPrint("my code here") }, for: .touchUpInside)`
Solution 5
You can use ActionClosurable which support UIControl, UIButton, UIRefreshControl, UIGestureRecognizer and UIBarButtonItem. https://github.com/takasek/ActionClosurable
Bellow show example of UIBarButtonItem
// UIBarButtonItem
let barButtonItem = UIBarButtonItem(title: "title", style: .plain) { _ in
print("barButtonItem title")
}
Blaszard
I'm here to gain knowledge and insights on a variety of fields I'm interested in. Specifically, Programming & Software Development (Python and R; no longer use Swift and JavaScript/node.js) Data Science, Machine Learning, AI, & statistics Travel (started in 2016) Language (普通话, français, español, italiano, русский, 한국어) Politics, Economics, and Finance Currently (in 2020), my primary interest is Korean and Russian😈 PS: I'm not a native-English speaker. If you find any errors in my grammar and expressions, don't hesitate to edit it. I'll appreciate it👨🏻💼
Updated on July 09, 2022Comments
-
Blaszard almost 2 years
I want to make a
selector
argument of my method refer to a closure property, both of them exist in the same scope. For example,func backgroundChange() { self.view.backgroundColor = UIColor.blackColor() self.view.alpha = 0.55 let backToOriginalBackground = { self.view.backgroundColor = UIColor.whiteColor() self.view.alpha = 1.0 } NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(backToOriginalBackground), userInfo: nil, repeats: false) }
However, this shows an error:
Argument of #selector cannot refer to a property
.Of course I can define a new, separate method and move the implementation of the closure to it, but I want to keep it frugal for such a small implementation.
Is it possible to set a closure to
#selector
argument? -
ryantxr about 8 yearsI would be interested in seeing what that looks like. That would be useful in so many ways.
-
Prajeet Shrestha about 8 yearsYeah! That's it. And we were pondering with the syntax rather than the problem OP was facing and it's better solution.
-
Sulthan about 8 yearsCreating that target is a tiny bit tricky because
NSTimer
retains itstarget
but definitely possible. Better to usedispatch_after
though. -
Charlton Provatas over 6 yearsPlease see my post below. It is possible w/ some objc-runtime magic :)
-
andrewmclean over 6 yearsThis is very handy and nice to keep as a Swift implementation. Thank you
-
hexaflexagonal about 5 yearsThis is slick! Wish they'd add a real way to do it to Cocoa, but this is the least hacky workaround I've seen.
-
timeSmith almost 5 yearsNote: you have to keep an instance of the action that will be used by a button; the button does not take ownership of it.
-
Charlton Provatas over 4 yearsYou have to import the obj-c class for this to work
-
clearlight about 2 yearsCool! But what a hassle. It needs to be integrated into iOS platform natively and cleanly to be practical in most cases IMO. In my case I wanted to move a couple of @objc selector-target functions from class scope to become sub-functions of a function, bringing them closer in file/scope to 1 place they were invoked, but xcode said #selector can't call local functions... so wanted a way to let a selector run a closure... but this is wayyy too awkward a solution for a minor issue like that. As of this writing WWDC 2022 is just 2wks away... who knows? Maybe some sympathetic Apple developer...?
-
clearlight about 2 yearsCat / hamburger. Nice example. u a riot