Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"classname has no member functionname" when adding UIButton target

Tags:

swift

swift3

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.

like image 704
Pekka Avatar asked Jul 30 '16 11:07

Pekka


3 Answers

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).

like image 175
OOPer Avatar answered Sep 28 '22 09:09

OOPer


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.

like image 44
vacawama Avatar answered Sep 28 '22 11:09

vacawama


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

like image 28
Goodtime Avatar answered Sep 28 '22 11:09

Goodtime