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 { }
}
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
}
}
This question already has an answer here:
Why does Xcode line-out autocomplete methods for selector?
(1 answer)
Closed 2 years ago.
I have a UIButton within a UITableViewCell. When I call addTarget on the button and attempt to put a function in, the autocomplete has the function crossed out with a white line (see first image). Xcode still allows me to put the function in and run the app; however, when the button is tapped the function isn't called.
Button initialization:
private let infoButton: UIButton = {
let button = UIButton()
button.backgroundColor = .clear
button.adjustsImageWhenHighlighted = false
button.setImage(Images.sizeGuide, for: .normal)
button.addTarget(self, action: #selector(infoButtonPressed(_:)), for: .touchUpInside)
return button
}()
Function (infoButtonPressed):
#objc private func infoButtonPressed(_ sender: UIButton) {
print("Button Pressed")
}
Because I reuse this cell multiple times and only one of these cells needs to have this button, I have a variable that dictates whether or not to show the button:
var hasInfoButton: Bool = false {
didSet {
if hasInfoButton {
setupInfoButton()
}
}
}
The function that is called above simply sets up the button using autoLayout. Something to mention: when I tried calling addTarget in this function, the app crashed with Unrecognized selector sent to instance...
The tableView in which this is embedded in is only static and displays data. Therefore, allowSelection and allowsMultipleSelection are both set to false.
Does anyone have a solution to this?
You shouldn't need (_ sender: UIButton) the method should just be:
#objc func infoButtonPressed() {
print("button pressed")
}
EDIT:
The button initializer is also a little strange. The way I generally go about this kind of thing is like this:
let button = UIButton()
private func configureButton() {
button.backgroundColor = .clear
button.adjustsImageWhenHighlighted = false
button.setImage(Images.sizeGuide, for: .normal)
button.addTarget(self, action: #selector(infoButtonPressed(_:)), for: .touchUpInside)
}
Then call configureButton() in viewDidLoad()
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 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.
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: