I have a nested function like this and I want to call childFunc when user tap to my button, but it does not work
class MyClass {
func parentFunc() {
button.addTarget(self, action: #selector(parentFunc().childFunc), for: .touchUpInside)
func childFunc() {
print("User tapped")
}
}
}
It raise error like this:
Value of tuple type '()' has no member 'childFunc'
Is there any way to perform childFunc with #selector ?
Edit 1:
I have use closure like this, But I think it's not a nice way because I have to make another function
class MyClass {
myClosure: () -> () = {}
func parentFunc() {
button.addTarget(self, action: #selector(runChildFunc), for: .touchUpInside)
func childFunc() {
print("User tapped")
}
myClosesure = childFunc
}
func runChildFunc() {
myClosure()
}
}
instead, you can try below code to achieve your goal
class MyClass {
let button = UIButton()
#objc public func parentFunc(_ sender : UIButton?)
{
func childFunc() {
print("User tapped")
}
if sender != nil && sender.tag == 100{
childFunc()
return
}
button.addTarget(self, action: #selector(parentFunc(_:)), for: .touchUpInside)
button.tag = 100
}
}
In above code, Sender is optional so you can pass nil when you don't want to call child function like parentFunc(nil)
Is there any way to perform childFunc with #selector
No. The entire idea makes no sense. Keep in mind that the nested (inner) func does not really exist. It only comes into existence, momentarily, when the outer func runs. The running of the outer func brings the inner func into existence at the moment the definition is encountered, effectively storing it in a local variable, so that it is callable from subsequent code in scope inside the func. And like any local variable, when the outer func code ends, the inner func goes back out of existence. Also, because it is a local variable inside the func, it is scoped in such a way that it couldn't be called into from outside, even if such a notion made sense — just as a local variable inside a func cannot be seen from outside the func, even if that notion made sense.
The selector must point to a method of a class instance (where the class derives from NSObject). This is a Cocoa Objective-C architecture; you have to obey Objective-C rules.
Related
When setting the action with the "addTarget" method on a button in Swift, is there a way for me to pass a parameter to the function I want to trigger?
Say I had a simple scenario like this:
let button = UIButton()
button.addTarget(self, action: #selector(didPressButton), for: .touchUpInside)
#objc func didPressButton() {
// do something
}
Obviously the above code works fine, but say I wanted the 'didPressButton' function to take in a parameter:
#objc func didPressButton(myParam: String) {
// do something with myParam
}
Is there a way I can pass a parameter into the function in the 'addTarget' method?
Something like this:
let button = UIButton()
button.addTarget(self, action: #selector(didPressButton(myParam: "Test")), for: .touchUpInside)
#objc func didPressButton(myParam: String) {
// do something with myParam
}
I'm coming from a JavaScript background and in JavaScript it's pretty simple to achieve this behavior by simply passing an anonymous function that would then call the 'didPressButton' function. However, I can't quite figure how to achieve this with swift. Can a similar technique be used by using a closure? Or does the '#selector' keyword prevent me from doing something like that?
Thank you to anyone who can help!
The short answer is no.
The target selector mechanism only sends the target in the parameters. If the string was a part of a subclass of UIbutton then you could grab it from there.
class SomeButton: UIButton {
var someString: String
}
#objc func didPressButton(_ button: SomeButton) {
// use button.someString
}
It is not possible to do that in iOS. You can get the View in the selector in your case it is a button.
button.addTarget(self, action: #selector(buttonClick(_:)), for: .touchUpInside)
#objc func buttonClick(_ view: UIButton) {
switch view.titleLabel?.text {
case "Button":
break
default:
break
}
}
I have a UIDatePicker. After a date is selected I call this method
func goToNextScreen(selectedDate: Date) {
//...
}
Now I have added a UIButton. In that button action I want to call the same method goToNextScreen without any date value. Date value is optional in next screen. I tried the following code
btn.addTarget(self, action: #selector(goToNextScreen), for: .touchUpInside)//goToNextScreen(_:)
#objc func goToNextScreen(selectedDate: Date? = nil) {
//...
}
When the button is tapped the app crashes.
How to solve this without adding another method? If it is not possible why my approach doesn't work
What is happening here is, the button's internal logic is trying to pass the sender, which is a UIButton into your method's Date parameter. However, the sender parameter won't get passed if your method don't have any arguments.
Optional parameters don't really work in this situation. What you can do however, is to create another parameterless overload for goToNextScreen:
#objc func goToNextScreen() {
goToNextScreen(selectedDate: nil)
}
And change
btn.addTarget(self, action: #selector(goToNextScreen), for: .touchUpInside)
to
btn.addTarget(self, action: #selector(goToNextScreen as () -> Void), for: .touchUpInside)
so that it different between the two overloads.
Note that the reason why just writing #selector(goToNextScreen) is ambiguous is because you have two methods named goToNextScreen, and Swift needs to resolve to one of them. But it can't with just the name. Here is a similar situation:
class Foo {
#objc func f() {}
func f(x: Int) {}
let selector: Selector = #selector(f) // ambiguous use of f
}
Edit: You can't really do this without creating another method. Selectors are inflexible things.
It's not possible in this case re-use the same method. You should create a new one without parameters or whose parameters are UIButton (or a more generic type, often is Any) and the UIEvent.
Here's the explanation of the Target-Action mechanism: UIControl.
I thought you've to try this
var selectedDate = Date()
btn.addTarget(self, action: #selector(goToNextScreen), for: .touchUpInside)//goToNextScreen(_:)
#objc func goToNextScreen(_ sender: UIButton)
{
selectedDate ?? self.datePickerDate : Date()
}
Using Any as sender type and casting to Date works
//add button Target
btn.addTarget(self, action: #selector(goToNextScreen(_:)), for: .touchUpInside)
//Call with date value
goToNextScreen(Date())
#objc func goToNextScreen(_ selectedDate: Any) {
nextVC.date = selectedDate as? Date
//...
}
sender paramter of UIButton is casted to Date which is reason of crash , it should be a UIButton
btn.addTarget(self, action: #selector(btnClicked), for: .touchUpInside)//goToNextScreen(_:)
#objc func btnClicked(_ sender:UIButton) {
// call next here
goToNextScreen()
}
func goToNextScreen(_ selectedDate: Date? = nil) {
if let date = selectedDate { }
}
I'm adding a target to a button but instead of having the target action reference a predefined function I want it to reference a closure.
typealias AlertAction = (title: String, handler: () -> ())
class ErrorView: UIView {
func addAction(_ action: AlertAction) {
let button = UIButton()
...
let selector = #selector(action.handler) //error happens here
button.addTarget(self, action: selector, for: .touchUpInside)
}
}
I'm getting an error on this line:
let selector = #selector(action.handler)
which is "Argument of '#selector' does not refer to an '#objc' method, property, or initializer"
This makes sense because usually you have to add #objc to your func declaration, but I'm wondering if there's a way to make my closure refer to an #objc method after the fact perhaps by wrapping it in another function.
Is this possible? I don't know how to define an #objc marked closure so I'm not sure.
#selector() is based on Objective-C bridging, since swift closures are non-objective-c, you can't use them.
One alternative solution is wrap your code inside an Objective-C function.
class AlertAction:NSObject {
var title:String?
#objc
func getHandler(sender:Any){
print("hi")
}
}
and use it like:
// Instance of a class
let alertAction = AlertAction()
// Usage
let button = UIButton()
let selector1 = #selector(AlertAction.getHandler)
button.addTarget(alertAction, action: selector1, for: .touchUpInside)
I try figure out why self point to the GameViewController instead of Answer
GameViewController.swift
class GameViewController: UIViewController {
var gameplay = QuestionsController(colors: colors)
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(gameplay.answersController.answers[0].button)
}
func didPressAnswerButton(sender: UIButton!) {
sender.setTitle("Im from GameViewController class", forState: .Normal)
}
}
QuestionsController.swift
class QuestionsController {
var question: Question
var answersController: AnswersController
}
AnswersController.swift
class AnswersController {
var answers = [Answer]()
func prepareAnswers() {
let answer = Answer()
answers.append(answer)
}
}
Answer.swift
class Answer{
let button: UIButton
func prepareButton() {
let answerButton = AnswerButton(type: .System)
answerButton.addTarget(self, action: "didPressAnswerButton:", forControlEvents: .TouchUpInside)
button = answerButton
}
func didPressAnswerButton(sender: UIButton!) {
sender.setTitle("Im from Answer class", forState: .Normal)
}
}
addTarget:action:forControlEvents: tells the control (answerButton in this case) what method to call, and what object to call it on, when the user taps the button. Looking at your code in more detail:
answerButton.addTarget(self, action: "didPressAnswerButton:", forControlEvents: .TouchUpInside)
When the user taps a button, the TouchUpInside event fires on the answerButton, and when that happens we want to invoke a method didPressAnswerButton: on an Answer object
So, we need to tell answerButton what do do when this TouchUpEvent fires. You do this calling the addTarget:action:forControlEvents method on the answerButton
The self argument tells the answerButton what object to notify about the event: it is the target. In this context, self is an Answer object.
The "didPressAnswerButton:" argument indicates what method the answerButton should call in response to the tap event: this is the action
This is the target-action mechanism of Objective-C/Cocoa. It's a very common pattern, it's worth it to read the linked documentation to learn a bit more about how it works. The key is that this is based on Objective-C* message passing: in the code above, "didPressAnswerButton:" indicates a selector, which when paired with a target (self), tells the answerButton how to send a "message" to the target when the user taps the button.
Also, note that when you are editing a storyboard and ctrl-drag from a button to your view controller and select a method, you are also setting up a target/action using this same mechanism. You select the target object by dragging to the view controller icon (or some other icon), and then you pick the action/selector when clicking on a method name in the popup.
* Target-Action was originally designed for Objective-C, but for the common case of implementing a view controller, you can assume Swift works the same way. Just note when reading documentation that Swift uses simple strings for actions, whereas Objective-C uses #selector(...).
I need call a function with selector when change switch, but only when I set switch to On state:
cell.switchMy.addTarget(self, action: "openView", forControlEvents: UIControlEvents.ValueChanged)
func openView(){
var cell:CustomCell = self.tableView.dequeueReusableCellWithIdentifier("cell5") as CustomCell
if(cell.switchMy.on == true){
println("ON")
self.performSegueWithIdentifier("segueUnl", sender: self)
}
}
If I use this code, always call the function, but I 'm controlling if the switch state is ON... How can this correctly?
This is (nearly) the right way to do this. You can't have a method be called only when the switch state is on.
But you don't need to retrieve the cell from the openView method. The sender will be passed by the caller:
cell.switchMy.addTarget(self, action: "openView:", forControlEvents: UIControlEvents.ValueChanged)
func openView(sender: UISwitch){
if(sender.on) {
println("ON")
self.performSegueWithIdentifier("segueUnl", sender: self)
}
}
Be careful: the selector used in addTarget needs to be changed from openView to openView: