Can I make #selector refer to a closure in Swift?
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)
How to pass closure as a parameter in perform(selector, withObject)
Your closure is not an Objective-C block, so it can't be passed through the ObjC runtime. You have to mark it as a block using @convention
.
let closure: @convention(block) () -> Void = { ... }
You can convert an existing closure into a block by assigning it:
let closure = { ... }
let block: @convention(block) () -> Void = closure
perform(#selector(foo(param:)), with: block)
Objective-C blocks are actually objects, and participate in ARC. The crash occurs because perform
tries to call Block_copy
on a non-block.
Of course, as a rule selectors are not the right tools in Swift, and you should convert any selector-based interface to just take a function argument in the first place. If you find yourself using perform
, you're probably on the wrong road in Swift. But it is still available if you need it.
The #selector is not compatible with the closure?
A selector is a string that's used to identify methods, properties, initializers in the Objective C runtime. When you use a notation like #selector(SomeClass.SomeMethod(withParam:AndParam:)
, you're specifying the selector in a format that the compiler can parse easily and verify to be correct. But ultimately, that would just be reduced down into a C string like: "SomeMethodwithParam:AndParam:"
.
Essentially, every class has a dictionary which maps selectors to the function pointers of the code that implements them. When a selector is used to invoke a function, the Objective C runtime searches the method table for the class in question, and looks up the method implementation corresponding to the given selector.
There's no way for this process to work with closures, which are anonymous by definition. Thus, you can only use selectors to refer to methods, properties, initializers that are registered with the Objective C runtime (which is what @objc
does, implicitly or explicitly).
Passing closure in swift as parameter to be used by selector in function
The problem is that the target-action mechanism is an Objective-C mechanism, and therefore is predicated on the notion that the action selector is a method of an object. You need, therefore, to have some NSObject-based object that has this function as a method, and which can then serve as the target.
Thus, if what differs in every case is the target and the action, what you need to pass is a reference to the target along with the selector string. Swift will squawk at this, but if you know how to form a selector string correctly you can certainly get away with it; you just won't be able to use the #selector
syntax, and so you will risk crashing if you form the selector string incorrectly. But it's the kind of thing we used to do all the time in the old Objective-C days, so go right ahead if that's your aim.
Totally artificial but working example:
func buttonMaker(target:NSObject, selectorString:String) -> UIButton {
let b = UIButton(type:.system)
b.setTitle("Testing", for: .normal)
b.addTarget(target, action: Selector(selectorString), for: .touchUpInside)
b.sizeToFit()
return b
}
And here's how to call it from a view controller:
func doButton(_ sender:Any) {
print("ha!")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let b = buttonMaker(target:self, selectorString:"doButton:")
b.frame.origin = CGPoint(x:100, y:100)
self.view.addSubview(b)
}
And when we tap the button, we don't crash (rather, we print "ha"), because I know how to make selector strings correctly. But, as you can see, to accomplish this I had to give up the use of #selector
altogether, so safety is out the window. If I had written my selector string incorrectly — for instance, if I had spelled it wrong, or omitted the colon — we'd have crashed on the button tap, just like we used to all the time before Swift #selector
and Objective-C @selector
were invented.
Adding a closure as target to a UIButton
Do Not Use This Answer, See Note Below
NOTE:
like @EthanHuang said
"This solution doesn't work if you have more than two instances. All actions will be overwrite by the last assignment."
Keep in mind this when you develop, i will post another solution soon.
If you want to add a closure as target to a UIButton
, you must add a function to UIButton
class by using extension
Swift 5
import UIKit
extension UIButton {
private func actionHandler(action:(() -> Void)? = nil) {
struct __ { static var action :(() -> Void)? }
if action != nil { __.action = action }
else { __.action?() }
}
@objc private func triggerActionHandler() {
self.actionHandler()
}
func actionHandler(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
self.actionHandler(action: action)
self.addTarget(self, action: #selector(triggerActionHandler), for: control)
}
}
Older
import UIKit
extension UIButton {
private func actionHandleBlock(action:(() -> Void)? = nil) {
struct __ {
static var action :(() -> Void)?
}
if action != nil {
__.action = action
} else {
__.action?()
}
}
@objc private func triggerActionHandleBlock() {
self.actionHandleBlock()
}
func actionHandle(controlEvents control :UIControlEvents, ForAction action:() -> Void) {
self.actionHandleBlock(action)
self.addTarget(self, action: "triggerActionHandleBlock", forControlEvents: control)
}
}
and the call:
let button = UIButton()
button.actionHandle(controlEvents: .touchUpInside,
ForAction:{() -> Void in
print("Touch")
})
Related Topics
How to Dispatch Functions in Swift the Right Way
Is There a Prefix Header (Or Something with This Functionality) in Swift
Sort Array by Calculated Distance in Swift
Making Simple Accordion Tableview in Swift
Swift 2.0: Protocol Extensions: Two Protocols with the Same Function Signature Compile Error
Nsimage to Nsdata as Png Swift
Using Function Parameter Names in Swift
Class 'Viewcontroller' Has No Initializers in Swift
Convert Avaudiopcmbuffer to Nsdata and Back
Hide/Show Tab Bar When Push/Back. Swift
How to Get Rgb Components from Color in Swiftui
Is There a Zip Function to Create Tuples with More Than 2 Elements
Swift - Custom Setter on Property
Whats the Swift Animate Withduration Syntax
Compile Error in Swift 4 on Parameter Passing
Swift 5: What's 'Escaping Closure Captures Mutating 'Self' Parameter' and How to Fix It