Pass parameters to button action in swift - ios

When setting the action with the "addTarget" method on a button in Swift, is there a way for me to pass a parameter to the function I want to trigger?
Say I had a simple scenario like this:
let button = UIButton()
button.addTarget(self, action: #selector(didPressButton), for: .touchUpInside)
#objc func didPressButton() {
// do something
}
Obviously the above code works fine, but say I wanted the 'didPressButton' function to take in a parameter:
#objc func didPressButton(myParam: String) {
// do something with myParam
}
Is there a way I can pass a parameter into the function in the 'addTarget' method?
Something like this:
let button = UIButton()
button.addTarget(self, action: #selector(didPressButton(myParam: "Test")), for: .touchUpInside)
#objc func didPressButton(myParam: String) {
// do something with myParam
}
I'm coming from a JavaScript background and in JavaScript it's pretty simple to achieve this behavior by simply passing an anonymous function that would then call the 'didPressButton' function. However, I can't quite figure how to achieve this with swift. Can a similar technique be used by using a closure? Or does the '#selector' keyword prevent me from doing something like that?
Thank you to anyone who can help!

The short answer is no.
The target selector mechanism only sends the target in the parameters. If the string was a part of a subclass of UIbutton then you could grab it from there.
class SomeButton: UIButton {
var someString: String
}
#objc func didPressButton(_ button: SomeButton) {
// use button.someString
}

It is not possible to do that in iOS. You can get the View in the selector in your case it is a button.
button.addTarget(self, action: #selector(buttonClick(_:)), for: .touchUpInside)
#objc func buttonClick(_ view: UIButton) {
switch view.titleLabel?.text {
case "Button":
break
default:
break
}
}

Related

Why is the new iOS 14 UIControl action syntax so terrible?

New in iOS 14, we can attach an action handler directly to a UIControl:
let action = UIAction(title:"") { action in
print("howdy!")
}
button.addAction(action, for: .touchUpInside)
That's cool in its way, but the syntax is infuriating. I have to form the UIAction first. I have to give the UIAction a title, even though that title will never appear in the interface. Isn't there a better way?
First, you don't need to supply the title. This is (now) legal:
let action = UIAction { action in
print("howdy!")
}
button.addAction(action, for: .touchUpInside)
Second, you don't really need the separate line to define the action, so you can say this:
button.addAction(.init { action in
print("howdy!")
}, for: .touchUpInside)
However, that's still infuriating, because now I've got a closure in the middle of the addAction call. It ought to be a trailing closure! The obvious solution is an extension:
extension UIControl {
func addAction(for event: UIControl.Event, handler: #escaping UIActionHandler) {
self.addAction(UIAction(handler:handler), for:event)
}
}
Problem solved! Now I can talk the way I should have been permitted to all along:
button.addAction(for: .touchUpInside) { action in
print("howdy!")
}
[Extra info: Where's the sender in this story? It's inside the action. UIAction has a sender property. So in that code, action.sender is the UIButton.]

"addTarget" action function of UIButton not called (strikethrough)? [duplicate]

This question already has an answer here:
Why does Xcode line-out autocomplete methods for selector?
(1 answer)
Closed 2 years ago.
I have a UIButton within a UITableViewCell. When I call addTarget on the button and attempt to put a function in, the autocomplete has the function crossed out with a white line (see first image). Xcode still allows me to put the function in and run the app; however, when the button is tapped the function isn't called.
Button initialization:
private let infoButton: UIButton = {
let button = UIButton()
button.backgroundColor = .clear
button.adjustsImageWhenHighlighted = false
button.setImage(Images.sizeGuide, for: .normal)
button.addTarget(self, action: #selector(infoButtonPressed(_:)), for: .touchUpInside)
return button
}()
Function (infoButtonPressed):
#objc private func infoButtonPressed(_ sender: UIButton) {
print("Button Pressed")
}
Because I reuse this cell multiple times and only one of these cells needs to have this button, I have a variable that dictates whether or not to show the button:
var hasInfoButton: Bool = false {
didSet {
if hasInfoButton {
setupInfoButton()
}
}
}
The function that is called above simply sets up the button using autoLayout. Something to mention: when I tried calling addTarget in this function, the app crashed with Unrecognized selector sent to instance...
The tableView in which this is embedded in is only static and displays data. Therefore, allowSelection and allowsMultipleSelection are both set to false.
Does anyone have a solution to this?
You shouldn't need (_ sender: UIButton) the method should just be:
#objc func infoButtonPressed() {
print("button pressed")
}
EDIT:
The button initializer is also a little strange. The way I generally go about this kind of thing is like this:
let button = UIButton()
private func configureButton() {
button.backgroundColor = .clear
button.adjustsImageWhenHighlighted = false
button.setImage(Images.sizeGuide, for: .normal)
button.addTarget(self, action: #selector(infoButtonPressed(_:)), for: .touchUpInside)
}
Then call configureButton() in viewDidLoad()

UIButton add target without sender

I have a UIDatePicker. After a date is selected I call this method
func goToNextScreen(selectedDate: Date) {
//...
}
Now I have added a UIButton. In that button action I want to call the same method goToNextScreen without any date value. Date value is optional in next screen. I tried the following code
btn.addTarget(self, action: #selector(goToNextScreen), for: .touchUpInside)//goToNextScreen(_:)
#objc func goToNextScreen(selectedDate: Date? = nil) {
//...
}
When the button is tapped the app crashes.
How to solve this without adding another method? If it is not possible why my approach doesn't work
What is happening here is, the button's internal logic is trying to pass the sender, which is a UIButton into your method's Date parameter. However, the sender parameter won't get passed if your method don't have any arguments.
Optional parameters don't really work in this situation. What you can do however, is to create another parameterless overload for goToNextScreen:
#objc func goToNextScreen() {
goToNextScreen(selectedDate: nil)
}
And change
btn.addTarget(self, action: #selector(goToNextScreen), for: .touchUpInside)
to
btn.addTarget(self, action: #selector(goToNextScreen as () -> Void), for: .touchUpInside)
so that it different between the two overloads.
Note that the reason why just writing #selector(goToNextScreen) is ambiguous is because you have two methods named goToNextScreen, and Swift needs to resolve to one of them. But it can't with just the name. Here is a similar situation:
class Foo {
#objc func f() {}
func f(x: Int) {}
let selector: Selector = #selector(f) // ambiguous use of f
}
Edit: You can't really do this without creating another method. Selectors are inflexible things.
It's not possible in this case re-use the same method. You should create a new one without parameters or whose parameters are UIButton (or a more generic type, often is Any) and the UIEvent.
Here's the explanation of the Target-Action mechanism: UIControl.
I thought you've to try this
var selectedDate = Date()
btn.addTarget(self, action: #selector(goToNextScreen), for: .touchUpInside)//goToNextScreen(_:)
#objc func goToNextScreen(_ sender: UIButton)
{
selectedDate ?? self.datePickerDate : Date()
}
Using Any as sender type and casting to Date works
//add button Target
btn.addTarget(self, action: #selector(goToNextScreen(_:)), for: .touchUpInside)
//Call with date value
goToNextScreen(Date())
#objc func goToNextScreen(_ selectedDate: Any) {
nextVC.date = selectedDate as? Date
//...
}
sender paramter of UIButton is casted to Date which is reason of crash , it should be a UIButton
btn.addTarget(self, action: #selector(btnClicked), for: .touchUpInside)//goToNextScreen(_:)
#objc func btnClicked(_ sender:UIButton) {
// call next here
goToNextScreen()
}
func goToNextScreen(_ selectedDate: Date? = nil) {
if let date = selectedDate { }
}

How to perform selector with a nested function?

I have a nested function like this and I want to call childFunc when user tap to my button, but it does not work
class MyClass {
func parentFunc() {
button.addTarget(self, action: #selector(parentFunc().childFunc), for: .touchUpInside)
func childFunc() {
print("User tapped")
}
}
}
It raise error like this:
Value of tuple type '()' has no member 'childFunc'
Is there any way to perform childFunc with #selector ?
Edit 1:
I have use closure like this, But I think it's not a nice way because I have to make another function
class MyClass {
myClosure: () -> () = {}
func parentFunc() {
button.addTarget(self, action: #selector(runChildFunc), for: .touchUpInside)
func childFunc() {
print("User tapped")
}
myClosesure = childFunc
}
func runChildFunc() {
myClosure()
}
}
instead, you can try below code to achieve your goal
class MyClass {
let button = UIButton()
#objc public func parentFunc(_ sender : UIButton?)
{
func childFunc() {
print("User tapped")
}
if sender != nil && sender.tag == 100{
childFunc()
return
}
button.addTarget(self, action: #selector(parentFunc(_:)), for: .touchUpInside)
button.tag = 100
}
}
In above code, Sender is optional so you can pass nil when you don't want to call child function like parentFunc(nil)
Is there any way to perform childFunc with #selector
No. The entire idea makes no sense. Keep in mind that the nested (inner) func does not really exist. It only comes into existence, momentarily, when the outer func runs. The running of the outer func brings the inner func into existence at the moment the definition is encountered, effectively storing it in a local variable, so that it is callable from subsequent code in scope inside the func. And like any local variable, when the outer func code ends, the inner func goes back out of existence. Also, because it is a local variable inside the func, it is scoped in such a way that it couldn't be called into from outside, even if such a notion made sense — just as a local variable inside a func cannot be seen from outside the func, even if that notion made sense.
The selector must point to a method of a class instance (where the class derives from NSObject). This is a Cocoa Objective-C architecture; you have to obey Objective-C rules.

How to send additional parameter in addtarget function?

please anyone tell me, How to send additional parameter in addtarget function in swift 2.2?
Example:
button.addTarget(self, action: #selector(classname.myFunc(_:)), forControlEvents: UIControlEvents.TouchUpInside))
func myFunc(sender:UIButton){
print(“i am here“)
}
to
func myFunc(sender:UIButton,parameter:String){
print(“i am here“)
}
You can create a subclass of UIButton, add one property named parameter which you want to pass as an extra parameter.
In addTarget(), you can only choose passing the caller or not. In this case, the caller is a UIButton.
So use the subclass and call addTarget(), then you can get the string which you want send:
func myFunc(sender: customUIButton) {
// do something with sender.parameter
}
Hope it can help you.
In Swift 2.2 you can do something like this.
myUISlider.addTarget(self, action:#selector(self.onSliderValChanged(_:withEvent:)), forControlEvents: .ValueChanged)
func onSliderValChanged(slider: UISlider, withEvent event: UIEvent?) {
}
I use sender properties to send some parameters with it like
button.accessibilityHint = "param 1"
button.accessibilityValue = "param 2"
button.tag = 0
button.addTarget(self, action: #selector(buttonPressed(_:)), forControlEvents: UIControlEvents.TouchUpInside))
func myFunc(sender:UIButton){
print(sender.accessibilityHint) // param1
print(sender.accessibilityValue) // param2
print(sender.tag) // 0
}
It's not possible. You have to use a workaround.
button.addTarget(self, action: #selector(classname.myFunc(_:)), forControlEvents: UIControlEvents.TouchUpInside))
func myFunc(sender:UIButton){
self.myFunc2(sender, parameter: "My parameter")
}
func myFunc2(sender:UIButton,parameter:String){
}
Swift 5.0 code
theButton.addTarget(self, action: #selector(theFunc), for: .touchUpInside)
theButton.frame.name = "myParameter"
.
#objc func theFunc(sender:UIButton){
print(sender.frame.name)
}

Resources