I want to create a class to add UIButton action, and then viewcontroller can call this class to create action.
But the error message told me "unrecognized selector sent to instance "
How can I fix them?
class MYController: UIViewController{
#IBOutlet weak var pptMode: UIButton!
#IBOutlet weak var videoMode: UIButton!
#IBOutlet weak var meetingMode: UIButton!
#IBOutlet weak var videoMeeting: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
ModeChange().modeChangeUtil(self,pptMode:pptMode, videoMode: videoMode, meetingMode: meetingMode, videoMeeting: videoMeeting)
}
}
class ModeChange {
func modeChangeUtil(vc:UIViewController,pptMode:UIButton,videoMode:UIButton,meetingMode:UIButton,videoMeeting:UIButton){
pptMode.addTarget(vc, action: "modeTouched:", forControlEvents:UIControlEvents.TouchUpInside)
pptMode.tag = modeButtonTag.ppt.rawValue
videoMode.addTarget(vc, action: "modeTouched:", forControlEvents:UIControlEvents.TouchUpInside)
videoMode.tag = modeButtonTag.video.rawValue
meetingMode.addTarget(vc, action: "modeTouched:", forControlEvents:UIControlEvents.TouchUpInside)
meetingMode.tag = modeButtonTag.meeting.rawValue
videoMeeting.addTarget(vc, action:"modeTouched:", forControlEvents:UIControlEvents.TouchUpInside)
videoMeeting.tag = modeButtonTag.videoMeeting.rawValue
}
enum modeButtonTag: Int {
case ppt
case video
case meeting
case videoMeeting
}
func modeTouched(sender:UIButton){
let modeCmd:ModeCommand = ModeCommand(device: Mode(onoffUrl: OfficeConstants.MeetingRoom3A.MODE))
switch sender.tag {
case modeButtonTag.ppt.rawValue:
modeCmd.modeStatus = .PPT
case modeButtonTag.video.rawValue:
modeCmd.modeStatus = .Video
case modeButtonTag.meeting.rawValue:
modeCmd.modeStatus = .Meeting
case modeButtonTag.videoMeeting.rawValue:
modeCmd.modeStatus = .VideoMeeting
default: break
//do nothing
}
modeCmd.execute()
}
}
You passed an incorrect parameter!
The addTarget method takes 3 parameters:
target is the instance to which the selector will be sent.
action is a selector
forControlEvents is the event that you want to handle
After explaining these, can you figure out which parameter is wrong in your code?
It's the target!
Since the selector will be sent to the target, the target must have a method called modeTouched:.
In your method call, you passed in vc as the target. This means that the selector modeTouched: must be sent to vc. However, you did not define a modeTouched: method in vc! That's why the error occurred.
I guess you want to send the selector to the modeTouched: method in the ModeChange class. Thus, the target should be self:
pptMode.addTarget(self, action: #selector(modeTouched:), forControlEvents:UIControlEvents.TouchUpInside)
That should fix it!
Related
For a given simple audio app with a few buttons:
The button references inside ViewController is:
#IBOutlet weak var recordAudioButton: UIButton!
#IBOutlet weak var playAudioButton: UIButton!
#IBOutlet weak var processAudioButton: UIButton!
But where are those button names and references inside the xcode gui? Notice below that the Allow Recording button is highlighted: but there is no mention of recordAudioButton as a button name:
I want to modify the enabling/disabling logic of a different button that does not have a reference yet.. but can not see how/where to do that . The dialog does not show a way to view/change the button references. So where is the place to do that?
See in Referencing Outlets Section for each button.
You can set disable after buttons setup
#IBOutlet weak var recordAudioButton: UIButton! {
didSet { recordAudioButton.isEnabled = false }
}
#IBOutlet weak var playAudioButton: UIButton! {
didSet { playAudioButton.isEnabled = false }
}
#IBOutlet weak var processAudioButton: UIButton! {
didSet { processAudioButton.isEnabled = false }
}
Then in viewDidLoad check for permission
AVAudioSession.sharedInstance().requestRecordPermission() { [unowned self] allowed in
DispatchQueue.main.async {
self.recordAudioButton.isEnabled = allowed
self.playAudioButton.isEnabled = allowed
self.processAudioButton.isEnabled = allowed
}
}
Then your code
#IBAction func askForPermissions() {
recordingSession = AVAudioSession.sharedInstance()
do {
try recordingSession.setCategory(.playAndRecord, mode: .default)
try recordingSession.setActive(true)
recordingSession.requestRecordPermission() { [unowned self] allowed in
// UI related work has to be executed on main thread(queue)
DispatchQueue.main.async {
self.recordAudioButton.isEnabled = allowed
self.playAudioButton.isEnabled = allowed
self.processAudioButton.isEnabled = allowed
}
}
} catch let error {
presentError(withMessage: error.localizedDescription)
}
}
You just defined the button as an outlet. So it does not appear on the Touch Up Inside, it appears at outlets area. But you connect your other button to an action function, this button appears on the Touch Up Inside.
Given the starting point / hint from #Picode here is what I was missing to get a new UIButton reference. A Medium article helped pave the way https://medium.com/#GanChau/outlet-vs-action-connections-in-xcode-b5331fb233a1 . In particular we need to have both an Editor and the Visual Designer showing:
So on my project now I control-clicked on the new button Run DSP and the context menu shows up:
Now select New Referencing Outlet and connect it to the ViewController code:
I typed in dspJsButton for the Name and we get the following code generated:
#IBOutlet weak var dspJsButton: UIButton!
In the function,I can get a parameter which is UISlider.But its parameter‘s type is CALayer. The Compiler don't throw error ,it's unbeleivable . I don't know how do I get it, But it's effective.
#IBOutlet weak var vectorSlider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
vectorSlider.addTarget(nil, action: #selector(changeVector), for: UIControl.Event.valueChanged) // Less than Swift5,UIControlEvents.valueChanged
}
#IBAction func changeVector(layer: CALayer) {
let transformAnimaZ = CABasicAnimation.init(keyPath: "transform.rotation.z")
transformAnimaZ.fromValue = vectorSlider.value
transformAnimaZ.toValue = vectorSlider.value
let transformGroup = CAAnimationGroup()
transformGroup.animations = [transformAnimaZ]
transformGroup.isRemovedOnCompletion = false
transformGroup.fillMode = CAMediaTimingFillMode.forwards // Less than Swift5,kCAFillModeForwards
layer.add(transformGroup, forKey: "transform")
}
I want to know how to get the parameter. I only find some answer about UIControl, not CALayer. Why can I add layer animat by this way.This is a Demo's picture of SliderRotate
It will help to consider the matter in stages. Let's start with the standard pattern (this is in a view controller):
#IBOutlet var myButton : UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.myButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
#objc func action(sender:UIView) {
print("here")
}
What's happening here is that we step outside of Swift into Cocoa's Objective-C world. In #selector(action) all we're doing is forming a string — in this case, "actionWithSender:", but the details are irrelevant. Cocoa now knows that if the button is tapped, it should send us the message "actionWithSender:". There is one parameter, so Cocoa passes along the sender, which is the button. That is simply how Cocoa behaves with regard to the Control target-action mechanism when the action method has one parameter; there's nothing you can do or change about that.
Now let's change the Swift declaration of the parameter type:
#IBOutlet var myButton : UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.myButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
#objc func action(layer:CALayer) {
print("here")
}
This changes nothing as far as Objective-C is concerned. We changed the name of the function to "actionWithLayer:", but Objective-C knows nothing about how the parameter is typed (the "CALayer" part). It just goes right ahead and calls the method, and passes along the sender, which is still the button. You can prove that by printing the layer parameter:
#IBOutlet var myButton : UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.myButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
#objc func action(layer:CALayer) {
print(layer) // UIButton...
}
So now you have lied to Swift about what sort of object will be arriving as the parameter of our action method. But your lie didn't matter because you didn't do anything with that parameter except print it. Okay, but now let's change the transform of layer:
#IBOutlet var myButton : UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.myButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
#objc func action(layer:CALayer) {
print(layer) // UIButton...
layer.transform = CATransform3DMakeRotation(.pi, 0, 0, 1)
}
Swift permits us to say that, because a CALayer has a transform and it is a CATransform3D. But there's just one problem. This is not in fact a CALayer; it's a UIView (a button in this case). A button has a layer, but it isn't a CATransform3D (it's an affine transform instead). So Swift permits the message but it is ineffective.
Perhaps it's surprising that we don't crash, but that's probably because a button does in fact have a transform in the first place. Having escaped from strong typing, we are able to do something incoherent and get away with it. But you'd crash if you'd say this:
#IBOutlet var myButton : UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.myButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
#objc func action(layer:CALayer) {
print(layer) // UIButton...
layer.zPosition = 0
}
Basically you've lied to Swift (this thing is a button, not a CALayer), so we are allowed to try to set its zPosition, but then it turns out at runtime that it is a button and has no zPosition and we crash.
What's the moral? Don't do that! You are lying to Swift and thus eviscerating its best feature, its strong type checking. Instead, always type your sender as Any and cast down safely. Now the cast will stop you if you get it wrong:
#IBOutlet var myButton : UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.myButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
#objc func action(sender:Any) {
if let layer = sender as? CALayer {
layer.zPosition = 0
} else {
print("it's not a layer you silly person")
}
}
EDIT In your second version of the question, you've sent add(_:forKey:) to the slider and it works as if you had sent it to the layer. But the slider is not a layer. What's happened is you've discovered a secret: behind the scenes, in Objective-C / Cocoa, UIView does in fact respond to addAnimation:forKey:, as we can see by probing Cocoa with a symbolic breakpoint:
And we can discover the same fact by probing with responds:
let slider = UIView()
(slider as AnyObject).responds(to: #selector(CALayer.add(_:forKey:))) // true!
And it happens that a UIView responds to this message by passing it along to its layer. But that doesn't change the reality: just because you say this is a CALayer, it is not! It is a UIView!! It's just coincidence that in fact UIView responds to this message and you don't crash and in fact the layer animates.
My class:
class SelectBox {
internal static func openSelector(list:[String: String], parent:UIView){
print("iosLog HELLO")
parent.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClick(sender:))))
}
#objc func handleClick(sender: UITapGestureRecognizer) {
print("iosLog CLICK")
}
}
Set view :
SelectBox.openSelector(list: AppDelegate.stateList, parent: bgPlaceInto)
After launch print HELLO, but after click on view i get below error :
2018-07-07 18:39:12.298322+0430 Ma[971:260558] [ChatService]: SMT:
2018-07-07 18:39:12.470392+0430
Ma[971:260525] [ChatService]: RCV: 2018-07-07 18:39:12.471851+0430
Ma[971:260591] [ChatService]: RCV:
2018-07-07 18:39:14.674675+0430 Ma[971:260392] *** NSForwarding:
warning: object 0x100a9fc70 of class 'Ma.SelectBox' does not implement
methodSignatureForSelector: -- trouble ahead Unrecognized selector
+[Ma.SelectBox handleClickWithSender:] 2018-07-07 18:39:14.675210+0430 Ma[971:260392] Unrecognized selector +[Ma.SelectBox
handleClickWithSender:]
How i can set click listener to view by class?
Thank you
Your openSelector method is static. The word self in a static context, refers to an instance of the surrounding type's meta type. In this case, SelectorBox.Type.
Obviously, SelectorBox.Type does not have a handleClick method. SelectorBox does.
You need to make the openSelector method non-static:
internal func openSelector(list:[String: String], parent:UIView){
print("iosLog HELLO")
parent.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClick(sender:))))
}
Now self refers to the SelectorBox instance.
You can call it like this:
// declare this at class level:
let box = SelectorBox()
// call the method like this
box.openSelector()
EDIT: Your class should look like this:
class ViewControllerPage: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var bgGenderInto: UIView!
let box = SelectBox()
override func viewDidLoad() {
super.viewDidLoad()
box.openSelector(list: AppDelegate.genderList, parent: bgGenderInto)
}
}
I need to catch the modification of a switch in a dynamic cell.
The app has dynamic cells to list all BLE devices which where found.
The BLE device should be able to turn on/off via switch.
Now is the problem, I can't catch the switch via Outlet or Action.
How is it possible to catch a change from a dynamic cell.
UI for the problem looks like that:
UI with dynamic cell
I found the point where I can catch the tap but there I can't use my methods because in a class type UITableViewCell CoreBluetooth is not supported and I can't fill out the attribute for the CoreBluetooth methods.
import UIKit
class remoteCellTableViewCell: UITableViewCell{
//MARK: Properties
#IBOutlet weak var remoteCellLabel: UILabel!
#IBOutlet weak var remoteCellImageView: UIImageView!
#IBOutlet weak var remoteCellSwitch: UISwitch!
#IBOutlet weak var remoteCellPower: UIProgressView!
override func awakeFromNib() {
super.awakeFromNib()
}
//HERE I CAN CATCH THE SWITCH TAP BUT I CAN'T USE CoreBluetooh IN THIS CLASS
#IBAction func onOff(sender: AnyObject) {
//OBJECT FROM THE CLASS FOR CONNECT AND SEND TO THE PERIPHERAL
var connection: ViewController = ViewController()
connection.viewDidLoad()
//CANT FILLOUT THE ATTRIBUTES BECAUSE OF MISSING COREBLUETOOTH SUPPORT
//-->
connection.centralManagerDidUpdateState(<#T##central: CBCentralManager##CBCentralManager#>)
connection.centralManager(<#T##central: CBCentralManager##CBCentralManager#>, didDiscoverPeripheral: <#T##CBPeripheral#>, advertisementData: <#T##[String : AnyObject]#>, RSSI: <#T##NSNumber#>)
connection.centralManager(<#T##central: CBCentralManager##CBCentralManager#>, didConnectPeripheral: <#T##CBPeripheral#>)
connection.centralManager(<#T##central: CBCentralManager##CBCentralManager#>, didDiscoverPeripheral: <#T##CBPeripheral#>, advertisementData: <#T##[String : AnyObject]#>, RSSI: <#T##NSNumber#>)
connection.centralManager(<#T##central: CBCentralManager##CBCentralManager#>, didConnectPeripheral: <#T##CBPeripheral#>)
connection.peripheral(<#T##peripheral: CBPeripheral##CBPeripheral#>, didDiscoverServices: <#T##NSError?#>)
connection.peripheral(<#T##peripheral: CBPeripheral##CBPeripheral#>, didDiscoverCharacteristicsForService: <#T##CBService#>, error: <#T##NSError?#>)
connection.peripheral(<#T##peripheral: CBPeripheral##CBPeripheral#>, didUpdateValueForCharacteristic: <#T##CBCharacteristic#>, error: <#T##NSError?#>)
connection.peripheral(<#T##peripheral: CBPeripheral##CBPeripheral#>, didWriteValueForDescriptor: <#T##CBDescriptor#>, error: <#T##NSError?#>)
//->
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Use Tags to get the switch state. here is the code that i used to get the button click action. Like this
In your cellForRowAtIndexPath assign tag to the button.
cell.absentButton.tag = indexPath.row
In your button action get the row of the button
if let button = sender as? UIButton {
if button.selected {
// set selected
let row = sender.tag
}
}
From this you can get which switch is changed.
class ViewController: UIViewController,UITextFieldDelegate {
#IBOutlet weak var text: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
name.delegate = self
// Do any additional setup after loading
}
these are some of my code
when name.delegate = self occured an error :
Use of unresolved identifier 'name'
i typed these code from book ,how to fixed it ?is the problem of swift grammar???
It should be:
text.delegate = self
because you add UITextFieldDelegate which need to conform a delegate of any UITextField you add in your code.
With that when you did something with your UITextField it will call its required delegate methods.
For more info refer this Apple Documentation on UITextFieldDelegate.