Objective-C
iOS Development
addTarget:action:forControlEvents
UIControl Events
Swift Programming

Passing parameters to addTargetactionforControlEvents

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

In iOS development, user interface controls like buttons, sliders, and switches respond to user interaction through the target-action pattern. The addTarget(_:action:for:) method (called addTarget:action:forControlEvents: in Objective-C) connects a control event to a method on a target object. A common question is how to pass custom parameters to the action method, since the method signature is constrained by UIKit. This article explains how the mechanism works and covers practical techniques for passing data.

How the Target-Action Pattern Works

When a user taps a button or interacts with a control, UIKit sends a predefined action message to the target object. You do not call the action method yourself — the system calls it for you. The action method can have one of three signatures:

swift
1// No parameters
2@objc func buttonTapped() { }
3
4// Sender only
5@objc func buttonTapped(_ sender: UIButton) { }
6
7// Sender and event
8@objc func buttonTapped(_ sender: UIButton, forEvent event: UIEvent) { }

You wire the action to a button like this:

swift
let button = UIButton(type: .system)
button.setTitle("Tap Me", for: .normal)
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)

The key constraint is that UIKit determines which arguments are passed to the action method. You cannot add arbitrary custom parameters to the selector. This means you need alternative approaches to pass extra data.

Technique 1: Use the Tag Property

Every UIView has an integer tag property. You can set it to identify which control triggered the action:

swift
1let button1 = UIButton(type: .system)
2button1.tag = 1
3button1.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
4
5let button2 = UIButton(type: .system)
6button2.tag = 2
7button2.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
8
9@objc func buttonTapped(_ sender: UIButton) {
10    switch sender.tag {
11    case 1:
12        print("First button tapped")
13    case 2:
14        print("Second button tapped")
15    default:
16        break
17    }
18}

This approach is simple but limited to integer values and becomes hard to maintain as the number of controls grows.

Technique 2: Subclass the Control

For richer data, you can create a custom subclass of UIButton (or any UIControl) and add properties to it:

swift
1class DataButton: UIButton {
2    var itemId: String?
3    var metadata: [String: Any] = [:]
4}
5
6// Usage
7let button = DataButton(type: .system)
8button.itemId = "product-42"
9button.metadata = ["category": "electronics"]
10button.addTarget(self, action: #selector(dataButtonTapped(_:)), for: .touchUpInside)
11
12@objc func dataButtonTapped(_ sender: DataButton) {
13    guard let itemId = sender.itemId else { return }
14    print("Tapped button for item: \(itemId)")
15}

This is clean and type-safe. The action method receives the custom subclass as the sender, giving you direct access to the extra properties.

Technique 3: Use Associated Objects (Objective-C Runtime)

If subclassing is not practical, you can attach arbitrary data to any object using Objective-C associated objects:

swift
1import ObjectiveC
2
3private var associatedKey = "customData"
4
5extension UIButton {
6    var customData: String? {
7        get { objc_getAssociatedObject(self, &associatedKey) as? String }
8        set { objc_setAssociatedObject(self, &associatedKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
9    }
10}
11
12// Usage
13button.customData = "special-value"
14button.addTarget(self, action: #selector(handleTap(_:)), for: .touchUpInside)
15
16@objc func handleTap(_ sender: UIButton) {
17    print(sender.customData ?? "no data")
18}

Technique 4: Use Closures (Modern Swift Approach)

Starting with iOS 14, UIAction lets you use closures directly, avoiding selectors entirely:

swift
1let button = UIButton(type: .system)
2let itemId = "product-42"
3
4let action = UIAction { _ in
5    print("Button tapped for item: \(itemId)")
6}
7button.addAction(action, for: .touchUpInside)

The closure captures variables from its surrounding scope, which is the most natural way to pass parameters in modern Swift. For projects targeting iOS 14 and later, this is the recommended approach.

Objective-C Equivalent

In Objective-C, the wiring looks similar, but you use selectors and method declarations:

objectivec
1[button addTarget:self
2           action:@selector(buttonTapped:)
3 forControlEvents:UIControlEventTouchUpInside];
4
5- (void)buttonTapped:(UIButton *)sender {
6    NSLog(@"Button with tag %ld tapped", (long)sender.tag);
7}

Common Pitfalls

  • Trying to add extra parameters to the selector: The selector passed to addTarget must match one of the three supported signatures (no params, sender, or sender + event). Adding custom parameters causes an unrecognized selector crash at runtime.
  • Forgetting the @objc attribute in Swift: Selectors require Objective-C runtime dispatch. Omitting @objc on the action method causes a compile-time error or a runtime crash.
  • Using tag for complex data: The tag property only holds an Int. Encoding complex information into an integer leads to fragile, unreadable code. Use subclassing or closures instead.
  • Retain cycles with closures: When using UIAction closures that capture self, failing to use [weak self] can create retain cycles that leak memory.
  • Not matching the sender type in the action method: If you subclass UIButton but declare the action parameter as UIButton instead of your subclass, you need to cast the sender, which defeats the purpose of subclassing.

Summary

  • The target-action pattern restricts action methods to zero, one (sender), or two (sender + event) parameters — you cannot pass arbitrary arguments through the selector.
  • Use the tag property for simple integer-based identification of controls.
  • Subclass UIButton or use associated objects to attach rich custom data to controls.
  • On iOS 14 and later, prefer UIAction closures, which capture surrounding variables naturally and eliminate the need for selectors.
  • Always mark action methods with @objc in Swift to enable Objective-C runtime dispatch required by the target-action mechanism.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.