Using Swift3 Function Parameter in Selector [duplicate] - ios

This question already has answers here:
Passing closure in swift as parameter to be used by selector in function
(2 answers)
Closed 5 years ago.
I'm attempting to pass in a function to add as a target to a UIButton in Swift3, but I'm getting the following error:
Argument of '#selector' does not refer to an '#objc' method, property, or initializer
Here's my code:
func updateButtonAction(_ action: () -> (), button: UIButton) {
button.addTarget(self, action: #selector(customFunction(action)), for: .touchUpInside)
}
func customFunction(_ function: () -> ()) {
function()
}
I'm wrapping a passed in function in another function so I know the class that owns it.
I think the issue is that first class functions in Swift aren't supported in Objective-C and the selector keyword only uses Objective-C functions.
Does anyone know a way around this?

Selector is just a fancy synonym for method name. It's not a closure or a function. It's the name of a method on an Obj-C object. Technically, a selector is a string.
customFunction(action) is not a selector. The selector is customFunction(_:).
You are trying to pass an implicit parameter to the selector. That's not how selectors work. You are not calling the method, you are just telling the UIButton which method it should call - UIButton is calling the method and the button is also specifying the parameters.
If you need to pass some code to the event handler, save it on your instance.
var function: (() -> Void)?
func customFunction() {
self.function?()
}
In general it's a good idea to have only one event handler and not change it.
Also note that your example has no need for first class functions.

Related

Call function as string in Swift

I am trying to call a function from another view controller with NSSelectorFromString. I have tried this:
MainScreenViewController().perform(NSSelectorFromString("function"))
But the application crashes. Thanks in advance.
You should add an #objc inference to the function, otherwise your function will not be expressable in Objective-C, which is what NSSelectorFromString uses.
#objc func function() {
[...]
}
Learn more about the limited #objc inference in Swift 4 here.

How to Update addTarget method to swift 4 [duplicate]

This question already has answers here:
How can I deal with #objc inference deprecation with #selector() in Swift 4?
(5 answers)
Closed 5 years ago.
when update project code to swift 4 get some error for add.target method
how i can fix this error?
//swift3
var chatLogController: ChatLogController? {
didSet {
sendButton.addTarget(chatLogController, action: #selector(ChatLogController.handleSend), for: .touchUpInside)
uploadImageView.addGestureRecognizer(UITapGestureRecognizer(target: chatLogController, action: #selector(ChatLogController.handleUploadTap)))
}
}
The hint message is telling you what to do, add #obj before declaring your function.
#objc func handleSend(_ sender: UIGestureRecognizer){
...
}
The reason is because:
In Objective-C, a selector is a type that refers to the name of an
Objective-C method. In Swift, Objective-C selectors are represented by
the Selector structure, and can be constructed using the #selector
expression. To create a selector for a method that can be called from
Objective-C, pass the name of the method, such as
#selector(MyViewController.tappedButton(_:)). To construct a selector for a property’s Objective-C getter or setter method, pass the
property name prefixed by the getter: or setter: label, such as
#selector(getter: MyViewController.myButton).
Read more here, at Apples documentation.

calling class_respondsToSelector for a Swift class method from Objective-C code

I have a Swift class as below
class ViewController: UIViewController {
func helloWorld(a: String)
{
print(a);
}
}
Assuming my target name is Pebble, from an objective-c class, I need to find out, if the class ViewController responds to selector helloWorld:. I have tried the following statements:
class_respondsToSelector(NSClassFromString(#"Pebble.ViewController"), NSSelectorFromString(#"helloWorld"))
class_respondsToSelector(NSClassFromString(#"Pebble.ViewController"), NSSelectorFromString(#"helloWorld:"))
class_respondsToSelector(NSClassFromString(#"ViewController"), NSSelectorFromString(#"helloWorld:"));
class_respondsToSelector(NSClassFromString(#"ViewController"), NSSelectorFromString(#"helloWorld"));
However,
1) In ViewController when I write let responds = self.responds(to: Selector("helloWorld:")) - it returns true.
2) In AppDelegete when I write let responds = ViewController.responds(to: Selector("helloWorld:")) - it returns false.
3) In AppDelegete when I write let responds = ViewController.instancesRespond(to: Selector("helloWorld:")) - it returns false.
All of the above returns NO. What should be done to fix this or what is the error?
Try this:
class_respondsToSelector(NSClassFromString(#"{YOUR_MODULE_PRODUCT_NAME}.ViewController"), NSSelectorFromString(#"helloWorldWithA:"))
In Swift 3, the first argument label is a part of method signature and when generating Objective-C selector it is concatenated with "With", so, the default Objective-C selector for func helloWorld(a: String) becomes helloWorldWithA:.
If you do not like this behaviour, you can write your helloWorld as func helloWorld(_ a: String), and its Objective-C selector becomes helloWorld:.
Or you can specify Objective-C selector explicitly with #objc annotation.
Writing like #objc(helloWorld:) func helloWorld(a: String), the Objective-C selector for it becomes helloWorld: as specified.
Some suggestions I can think of:
Annotate your class, `#objc(ViewController), to make sure it is exposed with the right name
Qualify your selector with the parameter name, i.e. helloWorld(a:)

How to discriminate same method selector on Swift 3.0? [duplicate]

[NOTE This question was originally formulated under Swift 2.2. It has been revised for Swift 4, involving two important language changes: the first method parameter external is no longer automatically suppressed, and a selector must be explicitly exposed to Objective-C.]
Let's say I have these two methods in my class:
#objc func test() {}
#objc func test(_ sender:AnyObject?) {}
Now I want to use Swift 2.2's new #selector syntax to make a selector corresponding to the first of these methods, func test(). How do I do it? When I try this:
let selector = #selector(test) // error
... I get an error, "Ambiguous use of test()." But if I say this:
let selector = #selector(test(_:)) // ok, but...
... the error goes away, but I'm now referring to the wrong method, the one with a parameter. I want to refer to the one without any parameter. How do I do it?
[Note: the example is not artificial. NSObject has both Objective-C copy and copy: instance methods, Swift copy() and copy(sender:AnyObject?); so the problem can easily arise in real life.]
[NOTE This answer was originally formulated under Swift 2.2. It has been revised for Swift 4, involving two important language changes: the first method parameter external is no longer automatically suppressed, and a selector must be explicitly exposed to Objective-C.]
You can work around this problem by casting your function reference to the correct method signature:
let selector = #selector(test as () -> Void)
(However, in my opinion, you should not have to do this. I regard this situation as a bug, revealing that Swift's syntax for referring to functions is inadequate. I filed a bug report, but to no avail.)
Just to summarize the new #selector syntax:
The purpose of this syntax is to prevent the all-too-common runtime crashes (typically "unrecognized selector") that can arise when supplying a selector as a literal string. #selector() takes a function reference, and the compiler will check that the function really exists and will resolve the reference to an Objective-C selector for you. Thus, you can't readily make any mistake.
(EDIT: Okay, yes you can. You can be a complete lunkhead and set the target to an instance that doesn't implement the action message specified by the #selector. The compiler won't stop you and you'll crash just like in the good old days. Sigh...)
A function reference can appear in any of three forms:
The bare name of the function. This is sufficient if the function is unambiguous. Thus, for example:
#objc func test(_ sender:AnyObject?) {}
func makeSelector() {
let selector = #selector(test)
}
There is only one test method, so this #selector refers to it even though it takes a parameter and the #selector doesn't mention the parameter. The resolved Objective-C selector, behind the scenes, will still correctly be "test:" (with the colon, indicating a parameter).
The name of the function along with the rest of its signature. For example:
func test() {}
func test(_ sender:AnyObject?) {}
func makeSelector() {
let selector = #selector(test(_:))
}
We have two test methods, so we need to differentiate; the notation test(_:) resolves to the second one, the one with a parameter.
The name of the function with or without the rest of its signature, plus a cast to show the types of the parameters. Thus:
#objc func test(_ integer:Int) {}
#nonobjc func test(_ string:String) {}
func makeSelector() {
let selector1 = #selector(test as (Int) -> Void)
// or:
let selector2 = #selector(test(_:) as (Int) -> Void)
}
Here, we have overloaded test(_:). The overloading cannot be exposed to Objective-C, because Objective-C doesn't permit overloading, so only one of them is exposed, and we can form a selector only for the one that is exposed, because selectors are an Objective-C feature. But we must still disambiguate as far as Swift is concerned, and the cast does that.
(It is this linguistic feature that is used — misused, in my opinion — as the basis of the answer above.)
Also, you might have to help Swift resolve the function reference by telling it what class the function is in:
If the class is the same as this one, or up the superclass chain from this one, no further resolution is usually needed (as shown in the examples above); optionally, you can say self, with dot-notation (e.g. #selector(self.test), and in some situations you might have to do so.
Otherwise, you use either a reference to an instance for which the method is implemented, with dot-notation, as in this real-life example (self.mp is an MPMusicPlayerController):
let pause = UIBarButtonItem(barButtonSystemItem: .pause,
target: self.mp, action: #selector(self.mp.pause))
...or you can use the name of the class, with dot-notation:
class ClassA : NSObject {
#objc func test() {}
}
class ClassB {
func makeSelector() {
let selector = #selector(ClassA.test)
}
}
(This seems a curious notation, because it looks like you're saying test is a class method rather than an instance method, but it will be correctly resolved to a selector nonetheless, which is all that matters.)
I want to add a missing disambiguation: accessing an instance method from outside the class.
class Foo {
#objc func test() {}
#objc func test(_ sender: AnyObject?) {}
}
From the class' perspective the full signature of the test() method is (Foo) -> () -> Void, which you will need to specify in order to get the Selector.
#selector(Foo.test as (Foo) -> () -> Void)
#selector(Foo.test(_:))
Alternatively you can refer to an instance's Selectors as shown in the original answer.
let foo = Foo()
#selector(foo.test as () -> Void)
#selector(foo.test(_:))
In my case (Xcode 11.3.1) the error was only when using lldb while debugging. When running it works properly.

Proper way to use selectors in Swift

I'm creating a view programatically, and adding a function so the action responds to the UIControlEvents.TouchUpInside event:
button.addTarget(self, action: action, forControlEvents:
UIControlEvents.TouchUpInside)
So, by going into the documentation I've added this action as a selector:
#selector(ViewController.onRegularClick)
XCode then complaints about:
Argument of #selector refers to a method that is not exposed to
Objective-C
So I have to set up the handler function with:
#objc func onRegularClick(sender: UIButton)
Can some one please put this noob on the right direction by guiding me to the documentation, or even give a short explanation, on:
why can't I no longer pass simply the function name String to the action?
how is the proper way to implement this following the Swift Way? Using the Selector class?
why do we need to pass the #objc keyword and how it affects the function?
Thank you!
why can't I no longer pass simply the function name String to the action?
Using strings for selectors has been deprecated, and you should now write #selector(methodName)instead of "methodName". If the methodName() method doesn't exist, you'll get a compile error – another whole class of bugs eliminated at compile time. This was not possible with strings.
how is the proper way to implement this following the Swift Way? Using the Selector class?
You did it the right way:
button.addTarget(self, action: #selector(ClassName.methodName(_:)), forControlEvents: UIControlEvents.TouchUpInside)
why do we need to pass the #objc keyword and how it affects the function?
In Swift the normal approach is to bind method's calls and method's bodies at compile time (like C and C++ do). Objective C does it at run time. So in Objective C you can do some things that are not possible in Swift - for example it is possible to exchange method's implementation at run time (it is called method swizzling). Cocoa was designed to work with Objective C approach and this is why you have to inform the compiler that your Swift method should be compiled in Objective-C-like style. If your class inherits NSObject it will be compiled ObjC-like style even without #objc keyword.
Well, it is called evolution
When there are some arguments in the method, you should declare the selector as:
let selector = #selector(YourClass.selector(_:))
You can type only #selector(selector(_:)) if the selector is in the same class of the caller. _: means that accept one parameter. So, if it accept more parameters, you should do something like: (_:, _:) and so on.
I found out that the #objc is needed only when the function is declared as private or the object doesn't inherit from NSObject
1: Currently you can, but it will create a deprecated warning. In Swift
3 this will be an error, so you should fix it soon. This is done
because just using a String can not be checked by the compiler if
the function really exists and if it is a valid Objective C function
which can be resolved dynamically during runtime.
2: Do it in this way:
button.addTarget(self, action: #selector(MyViewControllerClass.buttonPressed(_:)), forControlEvents: UIControlEvents.TouchUpInside)
3: Usually you not have to use the #objc attribute. I assume your class ViewController is (for any reason) not derived from UIViewController. If it derives from UIViewController is inherits also the needed ObjC behavior for calling selectors on functions.
For swift3.0 just do like below code :
yourButton.addTarget(self, action: #selector(yourButtonPressed), for: .touchUpInside)
and yourButtonPressed method
#IBAction func yourButtonPressed(sender:UIButton) {
// Do your code here
}
Everyones's answers are perfect but I have a better approach. Hope you gonna like it.
fileprivate extension Selector {
static let buttonTapped =
#selector(ViewController.buttonTapped(_:))
}
...
button.addTarget(self, action: .buttonTapped, for: .touchUpInside)
here in this file private will help to show buttonTapped only in file.
Programmatically
button.addTarget(self, action: #selector(returnAction), for: .touchUpInside)
// MARK: - Action
#objc private func returnAction(sender: UIButton) {
print(sender.tag)
}

Resources