126

I am trying to pass an extra parameter to the buttonClicked action, but cannot work out what the syntax should be in Swift.

button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)

Any my buttonClicked method:

func buttonClicked(sender:UIButton)
{
    println("hello")
}

Anyone any ideas?

Thanks for your help.

4
  • 1
    Refer to this link for a detailed answer and best practice
    – sheetal
    Sep 20, 2016 at 0:36
  • The question doesn't have a good answer because it already describes a problematic architecture. You don't need an extra parameter. The need for a n additional parameter is a problem in your architecture.
    – Sulthan
    Dec 27, 2016 at 11:00
  • WARNING TO INTERNET: please check out my answer at the bottom
    – Mr Heelis
    Oct 25, 2017 at 14:26
  • You can pass parameters in newer Swift versions by using addAction, which is much more flexible than addTarget, see example below
    – zzpaf
    Jan 15, 2023 at 1:55

14 Answers 14

191

You cannot pass custom parameters in addTarget:.One alternative is set the tag property of button and do work based on the tag.

button.tag = 5
button.addTarget(self, action: "buttonClicked:", 
    forControlEvents: UIControlEvents.TouchUpInside)

Or for Swift 2.2 and greater:

button.tag = 5
button.addTarget(self,action:#selector(buttonClicked),
    forControlEvents:.TouchUpInside)

Now do logic based on tag property

@objc func buttonClicked(sender:UIButton)
{
    if(sender.tag == 5){

        var abc = "argOne" //Do something for tag 5
    }
    print("hello")
}
5
  • 4
    Hi, If i want to get indexPath or cell of button in tableView, is it possible?
    – NKurapati
    Jun 4, 2015 at 10:10
  • 1
    Here's a hint: Don't make the method private.
    – OhadM
    Apr 10, 2016 at 7:31
  • 3
    Seems like it's type only can be an Int. Nothing else.
    – scaryguy
    Aug 4, 2016 at 22:29
  • 2
    what if I want to pass row and section together? Nov 15, 2016 at 6:33
  • 4
    just so you know, despite 118 upvotes (and counting) this is the wrong way to do this
    – Mr Heelis
    Jan 5, 2018 at 9:54
86

If you want to send additional parameters to the buttonClicked method, for example an indexPath or urlString, you can subclass the UIButton:

class SubclassedUIButton: UIButton {
    var indexPath: Int?
    var urlString: String?
}

Make sure to change the button's class in the identity inspector to subclassedUIButton. You can access the parameters inside the buttonClicked method using sender.indexPath or sender.urlString.

Note: If your button is inside a cell you can set the value of these additional parameters in the cellForRowAtIndexPath method (where the button is created).

2
  • 6
    This was the only way that worked for me. But I'm curious, is this anti-pattern or not?
    – scaryguy
    Aug 5, 2016 at 19:01
  • 1
    This is the best way, I think
    – Duy Hoang
    Jun 22, 2018 at 4:33
36

I appreciate everyone saying use tags, but really you need to extend the UIButton class and simply add the object there..

Tags are a hopeless way round this. Extend the UIButton like this (in Swift 4)

import UIKit
class PassableUIButton: UIButton{
    var params: Dictionary<String, Any>
    override init(frame: CGRect) {
        self.params = [:]
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        self.params = [:]
        super.init(coder: aDecoder)
    }
}

then your call may be call (NOTE THE colon ":" in Selector(("webButtonTouched:")))

let webButton = PassableUIButton(frame: CGRect(x:310, y:40, width:40, height:40))
webButton.setTitle("Visit",for: .normal)
webButton.addTarget(self, action: #selector(YourViewController.webButtonTouched(_:)), for:.touchUpInside)
webButton.params["myvalue"] = "bob"

then finally catch it all here

@IBAction func webButtonTouched(_ sender: PassableUIButton) {
    print(sender.params["myvalue"] ?? "")
}

You do this one time and use it throughout your project (you can even make the child class have a generic "object" and put whatever you like into the button!). Or use the example above to put an inexhaustible number of key/string params into the button.. Really useful for including things like urls, confirm message methodology etc

As an aside, it's important that the SO community realise this there is an entire generation of bad practice being cut'n'paste round the internet by an alarming number of programmers who don't understand/haven't been taught/missed the point of the concept of object extensions

2
  • Can anyone comment whether this is the correct way to do this or not?
    – Eric33187
    Jun 9, 2021 at 2:40
  • 1
    @Eric33187 It's a correct and, moreover, cleaner object oriented approach. But using tags is a lazy in place solution. They both work, but using tags - smells
    – Roman
    Sep 22, 2021 at 9:42
10

For Swift 3.0 you can use following

button.addTarget(self, action: #selector(YourViewController.YourMethodName(_:)), for:.touchUpInside)

func YourMethodName(_ sender : UIButton) {
    print(sender.tag)

}
10

Swift 4.2

Result:

testButton.on(.touchUpInside) { (sender, event) in
    // You can use any reference initialized before the code block here
    // You can access self by adding [weak self] before (sender, event)
    // You can then either make self strong by using a guard statement or use a optional operator (?)
    print("user did press test button")
}

In the file UIButton+Events.swift I've created an extension method for UIButton that binds a UIControl.Event to a completion handler called EventHandler:

import UIKit

fileprivate var bindedEvents: [UIButton:EventBinder] = [:]

fileprivate class EventBinder {

    let event: UIControl.Event
    let button: UIButton
    let handler: UIButton.EventHandler
    let selector: Selector

    required init(
        _ event: UIControl.Event,
        on button: UIButton,
        withHandler handler: @escaping UIButton.EventHandler
    ) {
        self.event = event
        self.button = button
        self.handler = handler
        self.selector = #selector(performEvent(on:ofType:))
        button.addTarget(self, action: self.selector, for: event)
    }

    deinit {
        button.removeTarget(self, action: selector, for: event)
        if let index = bindedEvents.index(forKey: button) {
            bindedEvents.remove(at: index)
        }
    }
}

private extension EventBinder {

    @objc func performEvent(on sender: UIButton, ofType event: UIControl.Event) {
        handler(sender, event)
    }
}

extension UIButton {

    typealias EventHandler = (UIButton, UIControl.Event) -> Void

    func on(_ event: UIControl.Event, handler: @escaping EventHandler) {
        bindedEvents[self] = EventBinder(event, on: self, withHandler: handler)
    }
}

The reason why I used a custom class for binding the event is to be able to dispose the reference later when the button is deintialised. This will prevent a possible memory leak from occurring. This wasn't possible within the UIButton its extension, because I'm not allowed to implement a property nor the deinit method.

0
2

If you have a loop of buttons like me you can try something like this

var buttonTags:[Int:String]? // can be [Int:Any]
let myArray = [0:"a",1:"b"]
for (index,value) in myArray {

     let button = // Create a button

     buttonTags?[index] = myArray[index]
     button.tag = index
     button.addTarget(self, action: #selector(buttonAction(_:)), for: .touchDown)

}
@objc func buttonAction(_ sender:UIButton) {

    let myString = buttonTags[sender.tag]

}
2

Swift 4.0 code (Here we go again)

The called action should marked like this because that is the syntax for swift function for exporting functions into objective c language.

@objc func deleteAction(sender: UIButton) {
}

create some working button:

let deleteButton = UIButton(type: .roundedRect)
deleteButton.setTitle("Delete", for: [])
deleteButton.addTarget(self, action: #selector( 
MyController.deleteAction(sender:)), for: .touchUpInside)
2
  • Thanks for sharing. This works, but marking it with @objc feels unintuitive. Can you explain the reasoning for this?
    – rawbee
    Nov 14, 2017 at 15:39
  • @rawbee Agree that this is counterintuitive. This question has more background: stackoverflow.com/questions/44390378/…
    – Mikrasya
    Jun 9, 2018 at 21:50
2

Swift 5.0 code

I use theButton.tag but if i have plenty type of option, its be very long switch case.

theButton.addTarget(self, action: #selector(theFunc), for: .touchUpInside) 
theButton.frame.name = "myParameter"

.

@objc func theFunc(sender:UIButton){ 
print(sender.frame.name)
}
2

This is more of an important comment. Sharing references of sytanx that is acceptable out of the box. For hack solutions look at other answers.

Per Apple's docs, Action Method Definitions have to be either one of these three. Anything else is unaccepted.

@IBAction func doSomething()
@IBAction func doSomething(sender: UIButton)
@IBAction func doSomething(sender: UIButton, forEvent event: UIEvent)
0

For Swift 2.X and above

button.addTarget(self,action:#selector(YourControllerName.buttonClicked(_:)),
                         forControlEvents:.TouchUpInside)
0

Swift 3.0 code

self.timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector:#selector(fetchAutocompletePlaces(timer:)), userInfo:[textView.text], repeats: true)

func fetchAutocompletePlaces(timer : Timer) {

  let keyword = timer.userInfo
}

You can send value in 'userinfo' and use that as parameter in the function.

0
button.tag = indexPath.row
button.addTarget(self, action: "buttonClicked:", 
    forControlEvents: UIControlEvents.TouchUpInside)
cell.accessoryView = button;

Button tag may help you to retrieve dedicated records from the array/dictionary

0

Although you can not pass a parameter the way you propose (With addTarget), you can use the addAction, where you can call any other function with any parameters you want:

let btnAuto = UIButton(type: .system)
    btnAuto.frame = CGRect(x:0, y:0 , width:200, height:20)
    btnAuto.backgroundColor = .blue
    btnAuto.addAction(
        UIAction { _ in
            print("This gets printed when the button is pressed")
            dothis(param1, param2)
        }, for: .touchUpInside)
    self.view.addSubview(btnAuto)

This is specially useful when you programmatically create a series of buttons and you want each of them to do a different action.

1
  • 1
    iOS 14.0 or greater is required for this solution
    – pw2
    May 15, 2023 at 10:36
-1

May be overkill, but couldn't you use a protocol for this? Then there would really be no need to pass in a parameter because you should have access to the properties within the conforming class.

protocol ButtonActionDelegate {
      func buttonClicked()
}

class SomeClassWithSomeButton: ButtonActionDelegate {
      let button = UIButton
      button.addTarget(self, action: "buttonClicked", forControlEvents: UIControlEvents.TouchUpInside)

      func buttonClicked() {
           //Do specific action
          }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.