16

Super newb in Swift and iOS development here.

I am following this tutorial about implementing a custom control in a single view iOS app. It's a Swift 2 tutorial, but so far I'm doing OK transposing everything to 3 as I go (I use XCode 8 Beta).

I have a custom class, RatingControl, connected to a View in the storyboard.

In the class's constructor, I create a button:

let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.red()

Then, I try to assign an action to the button. The tutorial says I should do it like so:

button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), 
for: .touchDown)       

and then create, in the same RatingControl class, the method:

func ratingButtonTapped(button: UIButton) {
  print("Button pressed ")
}

But when I compile, it complains:

type "RatingControl" has no member "ratingButtonTapped"

I've made 100% sure the function is there, in the class, and properly named. Full source

Is there something obvious I'm missing?

What I tried:

  • Added @objc to the class definition as per this answer (but that seems weird for a Swift-only thing, no?)

  • Made ratingButtonTapped() explicitly public (but that doesn't look like it should be necessary)

  • Fiddled around with strings instead of selectors, button.addTarget(self, action: "RatingControl.ratingButtonTapped", for: .touchDown) and many more, but that just crashes it later.

Community
  • 1
  • 1
Pekka
  • 442,112
  • 142
  • 972
  • 1,088
  • 3
    A-ha! XCode's auto-hinting feature kind of answered the question: I need to use `RatingControl.ratingButtonTapped` now, without brackets. That works fine and compiles now. I could delete this question - but if someone would like to provide an answer explaining this in more detail and *why* it happens, we might be able to make it a useful resource for future readers having the same problem? – Pekka Jul 30 '16 at 11:13
  • 2
    I think the question is fine as long as it's labeled `swift3`, since this will probably cause some confusion once Swift 3 becomes the standard. One tip regarding selectors though: if the method is within the same scope as the caller, you can call `self.methodName` instead of `ClassName.methodName`, just a cosmetic detail though. – xoudini Jul 30 '16 at 12:41

4 Answers4

16

In Swift 3, method reference for: func ratingButtonTapped(button: UIButton) becomes ratingButtonTapped(button:).

So, using #selector(RatingControl.ratingButtonTapped(button:)) also work.

And if you want to keep #selector(RatingControl.ratingButtonTapped(_:)), then you need to declare the ratingButtonTapped method as:

func ratingButtonTapped(_ button: UIButton) { //<- `_`
    print("Button pressed ")
}

And if you have only one ratingButtonTapped method in the class, you can address the selector as #selector(RatingControl.ratingButtonTapped) or simply (from inside RatingControl class) #selector(ratingButtonTapped).

OOPer
  • 47,149
  • 6
  • 107
  • 142
  • what if function does not have any arguments and this happen? – kashish Jan 19 '18 at 14:25
  • @kashish, it's not clear what your issue is. Better start your own thread and you would get or be guided to more appropriate answers. – OOPer Jan 19 '18 at 15:03
11

This happened because Swift 3 has changed the way it handles the first parameter name. In Swift 3, all parameter names must be used when calling a function unless an explicit _ was declared as the parameter name.

What you used as your selector was fine if you had declared your function as:

func ratingButtonTapped(_ button: AnyObject) {
    print("Button pressed ")
}

You could also have used this as your selector:

#selector(RatingControl.ratingButtonTapped(button:))

Added @objc to the class definition as per this answer (but that seems weird for a Swift-only thing, no?)

Your code may be in Swift, but you are interacting with the Objective-C runtime when you are coding for Cocoa Touch (iOS framework). The selector is a function that needs to be visible to the Objective-C runtime. You get this for free most of the time because you are implementing this in a class that ultimately inherits from NSObject (like UIViewController). If you have a Swift only class that doesn't inherit from NSObject, then you can add @objc to make the class and methods visible to the Objective-C runtime.

vacawama
  • 150,663
  • 30
  • 266
  • 294
3

If you want to call an action that is in your View Controller from a Different Class you can try this.

Use ViewController() for your target. Use ViewController.functionName for your selector. Do not use a helper method for the view controller variable like "vc", otherwise you will not be able to access objects within the ViewController.

Here is an example target:

self.addTarget(ViewController(), action:#selector(ViewController.Test(_:)), for: UIControlEvents.touchDragInside)

In your View Controller, here is an example Action

@IBAction func Test(_ sender: Any?) {
    print("Goodtime was here")

}

In the target you must add () but not in the action's selector. You do not have to call @IBAction, it can just be func. Some people use @objc or public any of those prefixes on the action should work.

Review, if the action is in a different Class or ViewController, you must put the the Class reference in both the target and the action's selector. Otherwise, it will try to always call the action within the same file regardless if it is correct in the Selector. Likewise, if the action is in the same file use, self for the target and inside the action's selector.

Cheers

Goodtime
  • 111
  • 3
2

When adding button targets in swift 3 first you need to be in class.

class SomeClass {

  // ...

  let button = UIButton()
  // configure button to taste...

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

  // ...

  @objc public func doSomething() {
    print("hello, world!")
  }

  // ...

}
Aquila Sagitta
  • 438
  • 3
  • 9