107

I'm programmatically adding a UITapGestureRecognizer to one of my views:

let gesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(modelObj:myModelObj)))

self.imageView.addGestureRecognizer(gesture)

func handleTap(modelObj: Model) {
  // Doing stuff with model object here
}

The first problem I encountered was "Argument of '#selector' does not refer to an '@Objc' method, property, or initializer.

Cool, so I added @objc to the handleTap signature:

@objc func handleTap(modelObj: Model) {
  // Doing stuff with model object here
}

Now I'm getting the error "Method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C.

It's just an image of the map of a building, with some pin images indicating the location of points of interest. When the user taps one of these pins I'd like to know which point of interest they tapped, and I have a model object which describes these points of interest. I use this model object to give the pin image it's coordinates on the map so I thought it would have been easy for me to just send the object to the gesture handler.

Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
mike
  • 2,073
  • 4
  • 20
  • 31
  • 1
    You can't pass values to a selector like that. Why can't you keep that value in an instance variable and access it from the selector method ? – Midhun MP Apr 06 '17 at 09:56
  • 4
    Possible duplicate of http://stackoverflow.com/questions/35635595/sending-a-parameter-argument-to-function-through-uitapgesturerecognizer-selector. – Martin R Apr 06 '17 at 10:19

6 Answers6

121

It looks like you're misunderstanding a couple of things.

When using target/action, the function signature has to have a certain form…

func doSomething()

or

func doSomething(sender: Any)

or

func doSomething(sender: Any, forEvent event: UIEvent)

where…

The sender parameter is the control object sending the action message.

In your case, the sender is the UITapGestureRecognizer

Also, #selector() should contain the func signature, and does NOT include passed parameters. So for…

func handleTap(sender: UIGestureRecognizer) {
    
}

you should have…

let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))

Assuming the func and the gesture are within a view controller, of which modelObj is a property / ivar, there's no need to pass it with the gesture recogniser, you can just refer to it in handleTap

Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
  • 2
    [The Target-Action Mechanism](https://developer.apple.com/documentation/uikit/uicontrol#1658488) section of the [UIControl documentation for Swift](https://developer.apple.com/documentation/uikit/uicontrol) is also a good read showing the three forms action methods can take. – toraritte May 21 '18 at 03:15
  • 3
    To add to this great answer, certain object types such as Timer have `userInfo: Any?` parameters that can be used in conjunction with this solution to pass in model objects with the sender. – Justin Vallely Oct 27 '20 at 13:42
  • @JustinVallely this really makes it a great answer! thank you! :) – Aviram Netanel Sep 02 '22 at 13:07
  • Not a good answer. You didn't explain how sender can be used – 6rchid Aug 27 '23 at 07:16
41

Step 1: create the custom object of the sender.

step 2: add properties you want to change in that a custom object of the sender

step 3: typecast the sender in receiving function to a custom object and access those properties

For eg: on click of the button if you want to send the string or any custom object then

step 1: create

class CustomButton : UIButton {

    var name : String = ""
    var customObject : Any? = nil
    var customObject2 : Any? = nil

    convenience init(name: String, object: Any) {
        self.init()
        self.name = name
        self.customObject = object
    }
}

step 2-a: set the custom class in the storyboard as well

enter image description here

step 2-b: Create IBOutlet of that button with a custom class as follows

@IBOutlet weak var btnFullRemote: CustomButton!

step 3: add properties you want to change in that a custom object of the sender

btnFullRemote.name = "Nik"
btnFullRemote.customObject = customObject
btnFullRemote.customObject2 = customObject2
btnFullRemote.addTarget(self, action: #selector(self.btnFullRemote(_:)), for: .touchUpInside)

step 4: typecast the sender in receiving function to a custom object and access those properties

@objc public func btnFullRemote(_ sender: Any) {

var name : String = (sender as! CustomButton).name as? String

var customObject : customObject = (sender as! CustomButton).customObject as? customObject

var customObject2 : customObject2 = (sender as! CustomButton).customObject2 as? customObject2

}
Ninad Kambli
  • 745
  • 7
  • 12
  • 4
    Clever answer that gives a good solution (rather than saying it's impossible, as most answers I have found about this say)... Tried this approach and can confirm it works – Joshua Wolff Jun 26 '19 at 23:38
  • 2
    Custom object wrapper to pass everything you need! So clever. Thank you. – Kyle Beard Aug 20 '20 at 00:55
16

Swift 5.0 iOS 13

I concur a great answer by Ninad. Here is my 2 cents, the same and yet different technique; a minimal version.

Create a custom class, throw a enum to keep/make the code as maintainable as possible.

enum Vs: String {
  case pulse = "pulse"
  case precision = "precision"
} 

class customTap: UITapGestureRecognizer {
  var cutomTag: String?
}

Use it, making sure you set the custom variable into the bargin. Using a simple label here, note the last line, important labels are not normally interactive.

let precisionTap = customTap(target: self, action: #selector(VC.actionB(sender:)))
precisionTap.customTag = Vs.precision.rawValue
precisionLabel.addGestureRecognizer(precisionTap)
precisionLabel.isUserInteractionEnabled = true

And setup the action using it, note I wanted to use the pure enum, but it isn't supported by Objective C, so we go with a basic type, String in this case.

@objc func actionB(sender: Any) {
// important to cast your sender to your cuatom class so you can extract your special setting.
  let tag = customTag as? customTap
  switch tag?.sender {
    case Vs.pulse.rawValue:
      // code
    case Vs.precision.rawValue:
      // code
    default:
      break
    }
}

And there you have it.

user3069232
  • 8,587
  • 7
  • 46
  • 87
  • 1
    Awesome. this works. But I think it should be corrected that @objc func actionB(sender: Any) { let tap = sender as? customTap switch tap?.customTag { ... – madu Aug 09 '20 at 15:32
10
cell.btn.tag = indexPath.row //setting tag
cell.btn.addTarget(self, action: #selector(showAlert(_ :)), for: .touchUpInside)

@objc func showAlert(_ sender: UIButton){
  print("sender.tag is : \(sender.tag)")// getting tag's value
}
Clean Coder
  • 496
  • 5
  • 12
4

Just create a custom class of UITapGestureRecognizer =>

import UIKit

class OtherUserProfileTapGestureRecognizer: UITapGestureRecognizer {

    let userModel: OtherUserModel    
    init(target: AnyObject, action: Selector, userModel: OtherUserModel) {
        self.userModel = userModel
        super.init(target: target, action: action)
    }
}

And then create UIImageView extension =>

import UIKit

    extension UIImageView {
    
        func gotoOtherUserProfile(otherUserModel: OtherUserModel) {
            isUserInteractionEnabled = true
            let gestureRecognizer = OtherUserProfileTapGestureRecognizer(target: self, action: #selector(self.didTapOtherUserImage(_:)), otherUserModel: otherUserModel)
            addGestureRecognizer(gestureRecognizer)
        }
        
        @objc internal func didTapOtherUserImage(_ recognizer: OtherUserProfileTapGestureRecognizer) {
            Router.shared.gotoOtherUserProfile(otherUserModel: recognizer.otherUserModel)
        }
    }

Now use it like =>

self.userImageView.gotoOtherUserProfile(otherUserModel: OtherUserModel)
Rajan Singh
  • 202
  • 2
  • 7
2

You can use an UIAction instead:

self.imageView.addAction(UIAction(identifier: UIAction.Identifier("imageClick")) { [weak self] action in
    self?.handleTap(modelObj)
}, for: .touchUpInside)
ubuntudroid
  • 3,680
  • 6
  • 36
  • 60