Triggering function with parameters upon UIButton press - swift

I'm trying to call a function with parameters on a button press, from what I understand #selector just checks that a function is there and then runs it. I've seen other answers where the button can be sent to the function but sadly I don't think that will solve my problem.
If I run this code (func is any function and a: 14 is just an example of a parameter being given):
myButton.addTarget(self, action: #selector(func(a: 14)), for: .touchUpInside)
I get an error saying 'Argument of #selector does not refer to an #objc method, property, or initializer
A workaround that I've been using:
myButton.addTarget(target: self, action: #selector(myFunc), for: .touchUpInside)
func myFunc() {
someOtherFunc(args)
}
The problem with this is that unless the argument that was going to be passed is global or class wide and known, you wont be able to use it.
Main Question:
Is there a way to have it run a function with parameters when a button is clicked without setting a class wide variable and assessing that instead of using a parameter?
My Solution:
Simplified, and buttons aren't setup and such...
var personName:String!
func scrollViewClicked(name:String) {
personName = name
myButton.addTarget(target: self, action: #selector(myFunc), for: .touchUpInside)
}
func myFunc() {
do something with personName
}
So pretty much I have a way of 'solving' the problem but it feels like a bit of a hack/improper way. Just trying to figure out if there is a 'real' way to do this or if it isn't meant to happen.

No, there's no way to have a UIButton run a function with arbitrary parameters.
But there is a way to have it run with some parameters, which may be useful to you.
The documentation for addTarget says that it takes a selector, which is essentially just a reference to a method. If you pass it a method with the right set of arguments, it will call it and pass whatever it's designed to pass. If you send a method with other arguments, you'll get an "unrecognized selector" error.
UIControl's addTarget understands three kinds of selectors:
func myFunc()
func myFunc(sender: UIButton)
func myFunc(sender: UIButton, forEvent event: UIEvent)
So you can set it to run a function with parameters, but the only parameters it knows how to send are the button that was pressed and the event it generated.
This is still potentially useful though, if you can use information about the button and/or the event to determine your action. For example you can set up your handler:
func myFunc(sender: UIButton, forEvent event: UIEvent) {
switch(sender) {
case myButton:
print("myButton was pressed")
default:
print("Something else was pressed.")
}
}
Depending on your use case, you could make use of the button's storyboard restoration ID, its title or other identifier, or you could even subclass UIButton and give it an instance variable to hold your parameter, like this:
class MyButtonClass: UIButton {
var argument:String = ""
}
Then when you're setting up your button you specify the argument:
myButton.argument = "Some argument"
And you can access it from your handler like this:
func myFunc(sender: UIButton, forEvent event: UIEvent) {
if let button = sender as? MyButtonClass {
print(button.argument)
}
}
It's still not as neat as just specifying your parameter in the selector, but as far as I know that's not possible.

Related

Weird behavior when I try to use a closure as the target of a UIButton

I know I need to replace the let keyword with lazy var for accessing the property otherwise I cannot access the 'self'.
But I found that the button.addTarget can build successfully as below,
Why? Normally if you try to access the property from a closure that needs to be a lazy variable, am I right?
For comparison, The testProperty shows red error message:
Cannot convert value of type (testController) -> () -> testController to specified type UITabBarController
import UIKit
class testController: UIViewController {
let actionButton: UIButton = {
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(actionButtonTapped), for: .touchUpInside)
return button
}()
let testProperty: UIViewController = {
let obj: UIViewController = self
return obj
}()
#objc func actionButtonTapped() {
}
}
If you check the addTarget signature, you will see the following:
open func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event)
First parameter target is Any?, so passing (Function) as target compiles fine. It even will work but can lead to weird issues, like opening keyboard will stop the button from calling the action.
You currently think that self refers to the instance.
But no, for an NSObject, it refers to partial application of this method. Actually running ViewController.self() before one has been instantiated, e.g. within the context of this closure, will crash your app, but self() used to still present itself as being available prior to Xcode 13.3.
As of now, Swift cannot cope with referring to this method without generating a warning. The warning tells you to use ViewController.self, which Swift can only interpret as a metatype, not a method. It doesn't understand what's going on, but at least it informs you that what you're doing is incorrect—the method is not actually the target.
Regardless of the warning, I don't know Objective-C well enough to tell you why a message sent to the method will trickle down to an instance of the related type. But don't do it.

Why my custom #objc function in swift have a strikethrough in the autofill?

I am currently learning swift 5.2 and I found a problem when I try to use #selector and #objc function.
I created a #objc function and I call it in the #selector, but the function has a strikethrough in the autofill. Just like some other functions that is deprecated. It doesn't really affect anything, the app work just fine. But I really want to know why the strikethrough appears.
Someone says that is because the function recommend a return value. And I tried to return something, like a Bool, in the #objc function. And the strikethrough is gone, but it still affect nothing, no matter what the function returns.
This really confused me. Is there any one who can tell me why? Thanks!
Here is my minimal reproducible example:
let refreshControl_A = UIRefreshControl()
let refreshControl_B = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
refreshControl_A.addTarget(self, action: #selector(refresh_A(sender:)), for: .valueChanged)
refreshControl_B.addTarget(self, action: #selector(refresh_B(sender:)), for: .valueChanged)
}
#objc func refresh_A(sender: UIRefreshControl) {
print("Have strikethrough!")
}
#objc func refresh_B(sender: UIRefreshControl) -> Bool {
print("No strikethrough!")
return true
}
The refresh_A function has the strikethrough in the autofill.
But the refresh_B function doesn't have the strikethrough in the autofill.
The only difference between these two functions is the return type, one is Viod, and the other is Bool. Both functions work fine.

Very strange bug in this 1 line function

So I stumbled onto something really weird.
Here is a 1 line function
#objc func foo(value: Int = 1) {
print("value is \(value)")
}
I'm gonna set up a tap gesture in viewDidLoad to call that function:
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
self.view.addGestureRecognizer(tap)
Here's the handleTap method. Note how I call foo without any arguments.
#objc func handleTap() {
foo()
}
As you'd expect, it prints out a 1 every time.
Enter The Dragon 🐉
Here's where it gets weird. Let's change handleTap to only post a notification. And we'll add an observer for that notification in viewDidLoad to call foo, like so.
The notification:
extension Notification.Name {
static let didSomething = Notification.Name("didSometing")
}
The new handleTap method:
#objc func handleTap() {
NotificationCenter.default.post(name: .didSomething, object: nil)
}
And our addition to viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: .didSomething, object: nil)
Now every time I tap, I get all kinds of values, here's what my output looks like:
value is 10748457248
value is 10748611248
value is 10748564928
value is 10748456000
value is 10748564256
value is 10748612688
value is 10748611824
value is 10748609616
value is 10748612496
value is 10748564592
Now if I change the notification observation to call a new method fooCaller (which is just a method that calls foo()), then I get all 1's.
So I figure it's something to do with obj-c, selectors and arguments, but thing is that my foo can be called without any arguments, like foo(), so I would reasonably expect #selector(foo) to have the same behaviour but it doesn't, and I don't know why.
In actuality, my function (which is represented here by foo) never took any arguments and was only called from notification observations. Then I later needed to call foo directly, passing something. So I gave it a default value so as not to disturb the current use of foo elsewhere, but it opened up the dark dimension instead.
There is sort of a bug, in the sense that, in my opinion, your code should not compile. And it does indeed, as you suspect, have to do with the tricky interface between Objective-C and Swift.
Let's look at the declaration of addObserver in Objective-C:
- (void)addObserver:(id)observer selector:(SEL)aSelector
name:(NSNotificationName)aName object:(id)anObject;
According to the docs, the selector must refer to a method that has
one and only one argument (an instance of NSNotification)
That's because the NSNotification can contain important information, such as its userInfo, that you might need to receive.
In other words, your foo, if it is to be the method called by the notification center to let you know of the notification, should have this signature:
#objc func foo(_ notification: Notification) {
And if it did, you'd be able to do stuff like this:
#objc func foo(_ notification: Notification) {
print(notification) // name = didSomething, object = nil, userInfo = nil
}
Great. But your foo does not have that signature; it types its parameter as Int instead. This is not identically what you have, but it might as well be:
#objc func foo(_ value: Int) {
Now, in my opinion, that should not be permitted. If you declare your foo that way, then when the time comes to use it as the selector...
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: .didSomething, object: nil)
...the compiler should complain: "No, you can't do that, foo has the wrong type as its parameter!" But it doesn't.
So the Notification object arrives and is interpreted as an Int in accordance with its memory location — and that is what you are printing.
Your console log print these numbers because of this line:
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: .didSomething, object: nil)
This function is passing the Notification as a parameter which causes these logs. Modify your function to receive the notification and handle it.
#objc func foo(notification: Notification) {
print("received notification: \(notification)")
}
Try passing the foo function as parameter for selector in tapGestureRecognizer, like this:
let tap = UITapGestureRecognizer(target: self, action: #selector(foo))
view.addGestureRecognizer(tap)
You'll see a similar result it's because because here the UITapGestureRecognizer is being passed to the foo function.

Different definition for sender

So I used sender a lot in swift but it confuses what exactly it does.
#IBAction func btnPressed(_sender: AnyObject){
performSegue(withIdentifier: "newScreen", sender: code)
}
Someone explain the difference between the two senders pls. It kind of confuses me because it has the same name but different functions.
The parameter of an IBAction tells you who caused the action. In your case, when you push the button that triggered this IBAction to be invoked, sender will be set to that button.
The word sender is nothing more than a name. It's a typical convention.
In your case, it's better to use a more strongly typed argument, with a more descriptive name, such as:
#IBAction func btnPressed(_ button: NSButton) { // or UIButton for iOS
performSegue(withIdentifier: "newScreen", sender: code)
}

Using TapGestureRecognizer Proper Syntax

Can anyone help me out with this error? Not sure what it's asking for...
TapGestureRecognizer Syntax
override func viewDidLoad() {
super.viewDidLoad()
let tapStart = UITapGestureRecognizer(target: self, action:#selector(tapped(gesture:)))
self.view.addGestureRecognizer(tapStart)
func tapped(gesture: UITapGestureRecognizer){
print("It actually worked")
}
}
my end goal seems like it should be fairly simple:
I want to segue to another view when the user taps anywhere on the screen. I'm creating a TapGestureRecognizer and for now am simply printing to the logs as the method so I can easily see if it works.
Thanks!
Try using #selector(tapped(gesture:)) if you're on Swift 3. If you're on Swift 2 the selector will probably be tapped(_:) or something instead.
The compiler will ensure the existence of the symbol you're referencing when you use #selector. If you use a string and let the compiler create a Selector from the string literal, all you get is a warning like this. Same disadvantage goes for using the Selector("funcname") constructor.
Try this code: Tested in Swift 3
Note: Syntax changed in Swift 3.
You can auto fix by holding function+control+option+command+f on your keyboard then Xcode will fix the issue for you.
Is nothing wrong with your code.Just, The way you using it.You,should placing your code like this...
override func viewDidLoad() {
super.viewDidLoad()
let tapStart = UITapGestureRecognizer(target: self, action:#selector(tapped(gesture:)))
self.view.addGestureRecognizer(tapStart)
}
func tapped(gesture: UITapGestureRecognizer){ // func tapped(_:) this will works to
print("Your in Right track mate")
}