Dynamically performing an action based on string passed in textfield - swift

Say I have a textfield - inputTextFieldand a button on UI with action performActionDynamically, in same class I define two functions: 1. firstFunc, 2. secondFunc, now I want to achieve this behavior:
If user types "firstFunc" in textfield and then he taps on button it should invoke firstFunc function, and if he types "secondFunc" in textfield and then he taps on button it should invoke secondFunc function.
In objective-c I would have easily achieved it by following below pseudocode:
Within performActionDynamically pass inputTextField.text in NSSelectorFromString() to obtain selector
Invoke performSelector:withObject: on self to perform respective function
The only thing which I can think in implementing same behavior in swift is -
Define two closures with name firstFunc, secondFunc
Store the closures in a dictionary as values for keys - 'firstFunc' and 'secondFunc' respectively
Obtain respective closure from dictionary based on value in textfield
Invoke obtained closure
Is there any better way to achieve intended behavior? Please guide.

You could try something like this:
if self.respondsToSelector(Selector(selectorString)) {
NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(0), target: self, selector: Selector(inputTextField.text), userInfo: nil, repeats: false)
}
This is done as a callback, so you will have to take that into account.
You could use NSThread instead of NSTimer if you prefer:
NSThread.detachNewThreadSelector(Selector(inputTextField.text), toTarget: self, withObject: nil)

Related

swift 4 selector perform with multiple parameters

I have function definition like this.
#objc func getSearchList(forString: String?, forPaging: Bool){
and I want to perform this function after waiting 0.5 second .(after text change but when fast change , remove callbacks and perform last added)
But when I perform like below, forString parameter is nil and not cancel old performs.Can someone help me? How can I send multiple parameter to selector and cancel previousperformRequests?
let selector = #selector(SearchViewController.getSearchList(forString : forPaging:))
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: selector, object: previousSearchText)
self.perform(selector, with: forInputString, afterDelay: 0.5)
previousSearchText = forInputString

Understanding Swift 2.2 Selector Syntax - #selector()

I am switching over the syntax of my project toward Swift 2.2 (which xCode helps me do automatically); however, I do not understand the new #selector() syntax.
As an example:
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self,
selector: #selector(MyVC.timerCalled(_:)), //new selector syntax!
userInfo: nil, repeats: true)
This has the selector #selector(MyVC.timerCalled(_:))
What does the _: signify? Can you add other variables into this selector? Say, #MyVC.timerCalled(_:whateverVar).
General info on what is different in this syntax as opposed to the string based implementation from earlier versions of Swift are greatly appreciated.
The bit in parenthesis is a mechanism for identifying the argument list for the selector that you want.
I recommend you look at the Generalized Naming proposal from Swift Evolution. It covers cases where you have a number of functions that differ only by their parameter labels and need to refer to them. The example from that document is:
extension UIView {
func insertSubview(view: UIView, at index: Int)
func insertSubview(view: UIView, aboveSubview siblingSubview: UIView)
func insertSubview(view: UIView, belowSubview siblingSubview: UIView)
}
If you wanted to get a function value for one of those the result is ambiguous:
let fn = someView.insertSubview // ambiguous: could be any of the three methods
The solution implemented is to add the argument labels, without any type information to the code that generates the function value to disambiguate which you want:
let fn = someView.insertSubview(_:at:)
let fn1 = someView.insertSubview(_:aboveSubview:)
See how the labels are added in the parens?
This proposal played a role in the one that most directly applies to your question:
Referencing the Objective-C selector of a method
In this particular case the selector you want to refer to is timerCalled: which is a function of one parameter that has no label. Hence (_:). The underscore means the label is not specified and the colon.
Swift 2.2 has deprecated Stringified selectors: In swift 2.0, we use to write the selector as a String i.e "buttonClicked". The disadvantage with this approach is that the compiler can't check whether the method really exists or not at compile time(Even if you have misspelled it).
EX:1
func buttonClicked(){
}
So the above method in the new approach can be called as #selector(buttonClicked)
EX:2
func buttonClicked(stringValue : String){
}
So the above method in the new approach can be called as #selector(buttonClicked(_:))
EX:3
func buttonClicked(stringValue : String, indexValue : Int){
}
So the above method with parameters in the new approach can be called as #selector(buttonClicked(_:indexValue:))
Consider below code for adding target to button in swift 3 using #selector
button.addTarget(self, action: #selector(self.buttonAction(sender:)),
for: UIControlEvents.touchUpInside)
func buttonAction(sender:UIButton!){
}
This syntax worked for me when migrating to swift 3
This is the way Swift method signatures are represented in documentation, and it is now beginning to be used in new language features such as the #selector() syntax to express methods by their argument lists.
The each colon (:) represents a method parameter. For named parameters, the colon is preceded by the external parameter name; for unnamed parameters, an underscore (_) is used.
So for example, MyVC.timerCalled(_:)) indicates a method on the MyVC type with one unnamed parameter, which might be declared like this:
func timerCalled(timer: NSTimer) { ... }
(Note that timer is the internal parameter name, since by default the first parameter of a method is unnamed)
The type (MyVC in your example) can also be omitted if it is within the same scope of the #selector() declaration.
A more complex example might look like this:
let sel = #selector(aMethodWithSeveralParameters(_:secondParam:thirdParam:))
...
func aMethodWithSeveralParameters(firstParam: Int, secondParam: Int, thirdParam: Int) { ... }

Swift Selector with default argument

I have Write simple Code here
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Cancel, target: self, action: Selector("cancelClick"))
Actual Function
func cancelClick(isAlert:String = "yes"){
self.dismissViewControllerAnimated(true, completion: { () -> Void in
if isAlert == "yes" {
Functions.displayAlert("called")
}
})
}
self.cancelClick() - Worked but if i didn't pass the argument
self.cancelClick(isAlert:"no") - Crashed
So what should be my selector if i have to pass argument in default perameter
tried with both Selector("cancelClick") and Selector("cancelClick:") but no luck.
The thing is that the parameter is not up to you. It is always the button (the "sender"), and that is the only thing it can be.
In other words, if you want this function to have a parameter, then by all means you will need to set your selector string as "cancelClick:" - the colon means that it takes a parameter. But that parameter must be the button:
func cancelClick(bbi:UIBarButtonItem?) {
However, you will notice that I have cleverly made this UIBarButtonItem parameter an Optional. Why do you think I did that? Because now you can also call it directly and pass nil:
self.cancelClick(nil)
Thus, cancelClick: now has a way to know whether the call comes from the tapping of a button or by a direct call - if bbi is not nil, the button was tapped; if bbi is nil, we were called directly from code. Sneaky, eh?
Another sneaky approach is to make the parameter an AnyObject:
func cancelClick(sender:AnyObject) {
The beauty of this is that you can call it with any kind of class instance. cancelClick can check the type of the sender. If it is a UIBarButtonItem (sender is UIBarButtonItem), then we were called by tapping the button. Otherwise, if called in code, you can pass in a string or anything else that this function might be prepared to deal with.
Don't use Selector("cancelClick:"), try instead just "cancelClick:" with the colon so you can pass an argument.
And as Matt said, the argument you must pass is the sender (the button) itself.

Selector string from Swift function

Given a function reference, is there a way in Swift to get the name of that function as a string suitable for passing to NSSelectorFromString?
I'd like to wrap NSNotificationCenter's addObserver with a version that takes a function reference instead of a selector string:
addObserver(self, function: someCallback, name: "some notification", object: nil)
But addObserver takes a String selector argument.
You're reinventing an unnecessary wheel. NSNotificationCenter already has an observation method that takes a function (what Objective-C calls a "block"):
addObserverForName:object:queue:usingBlock:
So just use that.
I'd still like to find an answer to my original question, but based on #matt's suggestion, I'm currently using this extension:
extension NSNotificationCenter {
class func addObserver(function: (NSNotification!) -> (), name: String? = nil, object: AnyObject? = nil, queue: NSOperationQueue? = nil) {
defaultCenter().addObserverForName(name, object: object, queue: nil, usingBlock: function)
}
}
Since it implicitly uses defaultCenter() and provides defaults for object and queue, which I'd almost always pass nil for, it allows for a more succinct call:
NSNotificationCenter.addObserver(doSomething, name: "some notification")
I like that it links against the actual function (doSomething), rather than a string representation. It's not a general purpose extension, but I think it covers 90% of the cases where I register for notifications.
Edit: I'm leaving this here for interest, but it's way too complicated (got wrapped up in how the question was asked, rather than the goal). Beyond the existing block-based approach, there's also this handy extension to it.
I wouldn't do it this way. It's too limiting because it would exclude function literals (anonymous functions).
Instead, I would play this game:
Create an dictionary property mapping [String: Void -> ()] (string to function)
When you register a new function, make up a unique, random key and store the function you're passed in your dictionary with that key.
Register with the selector observer_dispatcher_<key> (or whatever prefix you like).
Implement resolveInstanceMethod: to dynamically create any observer_dispatcher_ method you're requested. They can all point to the same IMP, something like:
(assuming this is good Swift; I haven't tried it):
void _observer_dispatcher(self: AnyObject, _cmd: Selector) {
let name = // strip the leading stuff off of _cmd
if let f = self.dispatchTable[name] {
f()
}
}
(This is still pretty sloppy; I'll have to think about the correct correct impl more, but this is the direction I'd go in.)

Default Parameters

I have a function with one parameter. This parameter has a default value. (true)
func disableButton(animated: Bool = true) {
if (animated) {
...
} else {
...
}
}
So i can call the function as:
disableButton(animated: true)
or:
disableButton()
and they give me the same result.
Now I have an NSTimer running a selector on completion like this:
buttonFadeTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(1.5), target: self, selector: Selector("disableButton"), userInfo: nil, repeats: false)
It will send the disableButton() message on completion. However on completion it causes the app to crash.
If i do:
func disableButton() {
disableButton(animated: true)
}
Then the timer successfully sends that message and the app does not crash.
Of course this is really ugly and kind of limits the great Swift feature of default parameters.
Is this a bug (that I should report) or am I doing it wrong?
Thanks for any help!
There's a difference between -disableButton and -disableButton: selectors. But you can't just use your desired selector like this:
buttonFadeTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(1.5), target: self, selector: Selector("disableButton:"), userInfo: nil, repeats: false)
because NSTimer here assumes selector with no arguments or selector with single argument (which will be used to pass firing NSTimer instance).
I believe, you should migrate from using selectors, as you want to use all that great features Swift delivers, as far as selectors is outdated pattern.
Just a note: it could work if you use "disableButton:" selector, maybe NSTimer's pointer will be interpreted as true and maybe not, not sure what will happen (maybe it could crash due to Swift strong typing stuff). But depending on that behaviour is a bad practice which could lead to bugs which are extremely hard to exterminate.