I am using Xcode 8.1 and swift 3.0. and I am creating a UISwitch programmatically, and the code is:
let preconnectedSwitch = UISwitch()
preconnectedSwitch.isOn = true
preconnectedSwitch.addTarget(self, action: #selector(switchValueDidChange), for: .valueChanged)
func switchValueDidChange() {
print("file:- \(#file) , fucntion:- \(#function), line:- \(#line) ")
// print("\(sender.isOn)")
// UserDefaults.standard.set(true, forKey: Constant.USER_DEFAULT_IS_CONNECTED)
}
But when I change UISwitch value then application crash with this error
I think the problem is that this is a Swift function, not an Objective-C function. Add the #IBAction tag to your function. That will tell the compiler to create an Objective-C function.
Related
I just updated to Xcode 13.3 and I'm seeing several instances of a new warning that I've not seen with previous versions of Xcode. As an example, I have a simple table view cell named LabelAndSwitchTableViewCell that looks like this:
import UIKit
class LabelAndSwitchTableViewCell: UITableViewCell {
private let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let _switch: UISwitch = {
let _switch = UISwitch()
_switch.translatesAutoresizingMaskIntoConstraints = false
_switch.addTarget(self, action: #selector(didToggleSwitch), for: .valueChanged)
return _switch
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(label)
contentView.addSubview(_switch)
// layout constraints removed for brevity
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc private func didToggleSwitch() {
print("Switch was toggled...")
}
}
As you can see, I'm adding a target to the switch that I want to be called when the value of the switches changes:
_switch.addTarget(self, action: #selector(didToggleSwitch), for: .valueChanged)
After updating to Xcode 13.3, I'm now seeing a new warning on this line:
'self' refers to the method 'LabelAndSwitchTableViewCell.self', which may be unexpected
Xcode's suggestion to silence this warning is to replace:
_switch.addTarget(self, action: #selector(didToggleSwitch), for: .valueChanged)
...with...
_switch.addTarget(LabelAndSwitchTableViewCell.self, action: #selector(didToggleSwitch), for: .valueChanged)
Making this change does silence the warning but it also causes the app to crash (unrecognized selector) when I toggle the switch. Here's the dump from that crash:
[app_mockup.LabelAndSwitchTableViewCell didToggleSwitch]: unrecognized selector sent to class 0x1043d86e8
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[app_mockup.LabelAndSwitchTableViewCell didToggleSwitch]: unrecognized selector sent to class 0x1043d86e8'
Making the didToggleSwitch() method static will prevent the crash but I'm not sure why I'd want to do that. I can obviously revert the change (from LabelAndSwitchTableViewCell.self back to just self) but I'm wondering if there's something else that I should be doing to address this?
You can fix by changing the lets to lazy var's
private lazy var _switch2: UISwitch = {
let _switch = UISwitch()
_switch.translatesAutoresizingMaskIntoConstraints = false
_switch.addTarget(self, action: #selector(didToggleSwitch), for: .valueChanged)
return _switch
}()
The Xcode fix-it suggestion is just wrong.
The reason is self is not ready yet in phase 1 of object initialisation. Phase 1 is to set all stored properties, and only in phase 2, you can access to self.
To fix your code, you can use lazy property, where the initialisation phase 1 is completed.
Here is the reference:
https://docs.swift.org/swift-book/LanguageGuide/Initialization.html
For this warning:
If there is no super class, or the super class is not NSObject, self is an error when to use in a stored property directly.
If the super class is NSObject, self gets compiled to an auto closure (ClassXXX) -> () -> ClassXXX. So in runtime, self will be used as current instance. But, since Swift 5.6 is warning us on that, I think the reference of self in stored property of a NSObject subclass may not be allowed in future swift versions.
Example 1: error when there is no super class
Example 2: the compiled self in AST
For this code:
import Foundation
class MyTest: NSObject {
var myself = self
}
Here is part of the compiled AST:
LabelAndSwitchTableViewCell.self as suggested, will not work in most cases. Use nil and search the responder chain.
addTarget(_:action:for:)
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 { }
}
This is a follow on from a previous question I have asked but I feel I am missing something very simple and its driving me up the wall!
I have a custom tableview cell which contains a switch and I need to trigger a function each time it's value is changed. I've tried using .addTarget but it never seems to trigger the function so maybe my selector syntax is incorrect.
I create the switch programatically within the tableview cell like this:
let thisSwitch: UISwitch = {
let thisSwitch = UISwitch()
thisSwitch.isOn = false
thisSwitch.translatesAutoresizingMaskIntoConstraints = false
thisSwitch.addTarget(self, action: Selector("switchTriggered:"), for: .valueChanged)
return thisSwitch
}()
Then directly below that I have my function:
func switchTriggered(sender: AnyObject) {
print("SWITCH TRIGGERED")
let sentSwitch = sender as! UISwitch
privateExercise.switchState = sentSwitch.isOn
}
It shows an error message stating " No method declared with Objective-C selector 'switchTriggered:' ". What am I missing here? Any help would be much appreciated!
The selector syntax should be
thisSwitch.addTarget(self, action: #selector(switchTriggered), for: .valueChanged)
Also keep the parameter as UISwitch type itself in order to avoid casting in function
func switchTriggered(sentSwitch: UISwitch) {
print("SWITCH TRIGGERED")
privateExercise.switchState = sentSwitch.isOn
}
Inside an extension for UIButton, I create a button and add its target.
extension UIButton {
convenience init(target: AnyObject) {
self.init(type: .system)
self.addTarget(target, action: Selector("setLang:"), for: .touchUpInside)
}
}
In ViewController, when using the custom init function to create the button, as target, I pass self.
This worked fine before upgrading my code to Swift 3. Now, however, I receive an error when tapping the button saying:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyApp.ViewController setLang:]: unrecognized selector sent to instance xxx
Inside ViewController there is the following function declared that is supposed to be called:
func setLang(button: UIButton) {
//...
}
Any ideas?
Thanks in advance!
i found that solution from apple developer Forums. This may help you.
selectionButton.addTarget(self, action: #selector(SingleQuestionViewController.selected(_:)), for: .touchUpInside)
// try like this , i hope it will work for you.
self.addTarget(target, action: NSSelectorFromString("setLang:"), for: .touchUpInside)
Found the problem. Instead of declaring the function like I did before, I need to do it the following way:
func setLang(_ button: UIButton) {
//...
}
Hope this helps other peeps.
I am trying to call a function whenever my UISwitch is tapped without using an #ib action but am having trouble finding the proper way to do this nothing is seeming to work no matter how i try to call it. I am using everything through code and not storyboard so using an ib property isn't really an option i am trying to use at going about this
I am using this but keep getting the error
terminating with uncaught exception of type NSException
func gameSwitchTapped(){
print("touched")
if gameMuteSwitch.isOn == true {
gameview.saveData.set(false, forKey: gameview.gameMuteKey)
} else {
gameview.saveData.set(true, forKey: gameview.gameMuteKey)}
}
And, elsewhere:
gameMuteSwitch.addTarget(self, action: Selector(("gameSwitchTapped")), for: UIControlEvents.valueChanged)
I think this should do it:
mySwitch.addTarget(self, action: #selector(self.switchChanged(sender:)), forControlEvents: UIControlEvents.ValueChanged)
func switchChanged(sender: UISwitch) {
let value = mySwitch.on
// Do something
}
In Objective C:
[mySwitch addTarget:self action:#selector(action:) forControlEvents:UIControlEventValueChanged];