46

I sublcassed in Swift 3 a UIButton subclass that is written in Objective-C.

When I try to add a target, it fails with an error code:

class ActionButton: JTImageButton {

    func action() {

    }

    func configure()) {
        // ...
        self.addTarget(self, action: #selector(self.action()), for: .touchUpInside)
        // error: 
        // Argument of '#selector' does not refer to an '@objc' method, property, or initializer

    }
}
MJQZ1347
  • 2,607
  • 7
  • 27
  • 49
  • 2
    Your `action` method conflicts with the `action(for:, forKey:)` method of `UIView`, compare http://stackoverflow.com/questions/35658334/how-do-i-resolve-ambiguous-use-of-compile-error-with-swift-selector-syntax. – Martin R Jan 06 '17 at 19:00

6 Answers6

75

The problem is that in #selector(self.action()), self.action() is a method call. You don't want to call the method; you want to name the method. Say #selector(action) instead: lose the parentheses, plus there's no need for the self.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    In addition, it conflicts with the `action(for:, forKey:)` method of `UIView`. `#selector(action)` will give an "ambiguous use" and your Q&A http://stackoverflow.com/questions/35658334/how-do-i-resolve-ambiguous-use-of-compile-error-with-swift-selector-syntax applies. – Martin R Jan 06 '17 at 18:57
  • so `func action() { }` has to be repalced with some other name as @matt said otherwise you will end up with `Ambiguous use of action() ` error. – Ashok R Jan 06 '17 at 19:05
  • Agreed. I'll point that out in my answer. I was answering the intent of the question, not the technicals. I shouldn't do that! –  Jan 06 '17 at 19:52
  • 2
    What if function with more than one Argument? – Krunal Nagvadia Sep 16 '19 at 06:42
  • What if it does? – matt Sep 17 '19 at 00:05
  • What if the named func has more than one Argument... how and where to fill those Arguments?? – Let.Simoo Aug 30 '20 at 12:43
  • 1
    @Let.Simoo There are no arguments in a `#selector` expression. You are _describing_ the method, not calling it. – matt Aug 30 '20 at 13:36
44

All you have to do is mark the func as @objc and there's no need for the self reference or the parenthesis

class ActionButton: JTImageButton {

    @objc func btnAction() {

    }

    func configure() {
        // ...
        self.addTarget(self, action: #selector(btnAction), for: .touchUpInside)
        // error: 
        // Argument of '#selector' does not refer to an '@objc' method, property, or initializer

    }
}

You can even make it private if you want

cjrieck
  • 974
  • 6
  • 11
  • 1
    Thanks, but it says `Ambiguous use of 'action'` when I implemented your approach? Edit: Renaming `action()` to `action2()` helped. – MJQZ1347 Jan 06 '17 at 18:55
  • Weird. I renamed the method to be `btnAction` and it doesn't give me that error. Maybe something to do with the function name, `action`? I've updated my answer to show what I mean – cjrieck Jan 06 '17 at 18:58
  • 1
    You don't need the `@objc` because the class already inherits from NSObject. – Martin R Jan 06 '17 at 19:05
  • True. Since it's a public method on an `NSObject` there's no need for the `@objc`. If the func were to be made `private` it would be needed – cjrieck Jan 06 '17 at 19:08
13

Adding @objc keyword in the front the method that is perfect, but I still got this error. Finally, I found out the solution as follows enter image description here

There is a pair of superfluous parentheses behind the method as picture shown above. What I should do is that remove it and it worked well.

Johnny
  • 1,112
  • 1
  • 13
  • 21
  • 1
    This doesn't answer the question's needs. Also, please don't repost answers. – Papershine Nov 05 '17 at 11:52
  • 3
    This is the answer I came across, and someone may encounter too. And I solved it by removing the parenthesis. Why you downvoted my answer? It's REAL answer! @paper1111 – Johnny Nov 06 '17 at 00:43
2

Added from comments on another answer: func action() is not just a poor choice for a function name and action, it fails to build. (You can use it as an input function parameter though, which I do for clarity when passing in target/action to an init() that sets these things.) I'm replacing this with MyAction() for clarity.


Try this:

self.addTarget(self, action: #selector(MyAction), for: .touchUpInside)

The said, a much better design is to move the MyAction() function to the button superview, as that makes things more aligned with basic MVC design:

Superview:

let myButton = ActionButton()
// include other button setup here
myButton.addTarget(self, action: #selector(MyAction), for: .touchUpInside

func action(_ sender: ActionButton) {
    // code against button tap here
}

Alternative coding for this, keeping the "action()" method in the view controller but moving only the "addTarget" into the button:

self.addTarget(superview?, action(superview?.MyAction), for: .touchUpInside)

Why am I asking you to consider moving the "MyAction()" method to the superview? Twofold:

  • It controls not only the button, but all other subviews in it's view and they commonly interact with each other via the view controller.
  • It makes the button much more reusable in other scenarios.
1

Instead of saying self.action(), use ActionButton.action().

Bryan
  • 1,335
  • 1
  • 16
  • 32
-1

If you don't mind adding an extra function, you could nest the function.

self.addTarget(self, action: myAction, for: UIControlledEvent)

myAction(){
    @obj.methodYouWantToCall(//parameters//)
}
Eugene Temlock
  • 69
  • 1
  • 1
  • 7