Unrecognized Selector when Passing Parameter - UIButton in TableView Cell | Swift iOS - ios

I've had this issue before but it usually due to not having a button hooked up in Storyboard or not passing a parameter when the function is expecting one, like most of the existing questions on here seem to suggest. This time however it is neither of those things.
I am creating a button in my TableView Cell by using this code in the CellForRowAt method:
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
button.addTarget(self, action: Selector(("showPeople:")), for: UIControlEvents.touchUpInside)
button.tag = indexPath.row
cell.addSubview(button)
and I have declared the showPeople method like so:
func showPeople(sender: UIButton) {
print("pressed")
}
When the button is pressed the program crashed with the following message:
showPeople: unrecognized selector sent to instance
But when I declare the method like so (remove the parameter):
func showPeople() {
print("pressed")
}
and change the selector to Selector(("showPeople")) it works fine which I guess means there is an issue with the way I'm passing the parameter. Why would this be happening? The function is expecting a parameter so the : is needed.

Looks like you're missing sender part in your selector.
Try this instead:
button.addTarget(self, action: #selector(ViewController.showPeople(sender:)), for: .touchUpInside)

Related

addTarget not working properly

I'm having trouble getting addTarget to work when I swap the buttons programmatically
let leftButton: UIButton = UIButton(type: UIButtonType.custom)
leftButton.setImage(UIImage(named: "menu"), for: UIControlState.normal)
leftButton.frame = CGRect(x: 0, y: 0, width: 24, height: 24)
...
leftButton.addTarget(nil, action: #selector(MapViewController.backToModules(sender:)), for: UIControlEvents.touchUpInside)
...
let leftBarButton = UIBarButtonItem(customView: leftButton)
let rightBarButton = UIBarButtonItem(customView: rightButton)
//assign button to navigationbar
self.navigationItem.rightBarButtonItems = [rightBarButton]
self.navigationItem.leftBarButtonItem = leftBarButton
My target should call the following function, but it doesn't?
func backToModules(sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
You are missing target parameter (I assume it's self):
leftButton.addTarget(self, action: #selector(MapViewController.backToModules(sender:)), for: UIControlEvents.touchUpInside)
When adding target, you have to specify selector that determines the identifier of the method to call - you managed to do that. However, you have to also give it a target object, an object on which that selector is supposed to be called - and that you specified as nil. That means that the selector was called on nil object.
In your case, the first target parameter has to be object of type MapViewController on which the method defined by the selector (backToModules(sender:)) will be called. If you call that addTarget method in context of MapViewController instance that should be the target, then use self as I suggested.

Swift4: UIButton not working

Within my app i'm trying to back a UIButton, i just want it to print "this is a test" When i press the button it does the classic button animation, however there is nothing printed in the console.
var tbutton: UIButton = {
let button = UIButton(type: .system)
button.frame = CGRect(x: 40.0, y:400.0, width: 300.0, height: 300.0)
let image = UIImage(named: "backb")
button.setBackgroundImage(image, for: .normal)
button.addTarget(self, action: #selector(dothings), for: .touchUpInside)
return button
}()
#objc func dothings(){
print("this is a test")
}
I then add the button into view with:
view.addSubview(tbutton)
Is there a section of code i'm missing, or have i coded something wrong?
You shouldn't initialize your button in that way.
Quoting the Apple documentation from Setting a Default Property Value with a Closure or Function:
If you use a closure to initialize a property, remember that the rest of the instance has not yet been initialized at the point that the closure is executed. This means that you cannot access any other property values from within your closure, even if those properties have default values. moreover:
You also cannot use the implicit self property, or call any of the instance’s methods, hence the problem is here:
button.addTarget(self, action: #selector(dothings), for: .touchUpInside)
so to fix the issue you should move the button initialization (or move the addTarget) after your ViewController is fully initialized (eg: viewDidLoad).
Another way to fix the issue, assuming you are using such button only after viewDidLoad, is to define it as a lazy var:
A lazy stored property is a property whose initial value is not calculated until the first time it is used

Class function called from another class

I have an Objective-C method like this:
-(void)findFiles{
}
It is working fine.But I need to call this method in Swift class.I have done :
var instanceOfCustomObject:DriveListTableViewController = DriveListTableViewController()
and now I can use the method like this:
instanceOfCustomObject.find()
But it works only in if I place it into viewDidLoad.
Is there a way that I can call it on click of button?
I have created button
let button:UIButton = UIButton(frame: CGRect(x: 100, y: 400, width: 100, height: 50))
button.backgroundColor = .black
button.setTitle("Button", for: .normal)
button.addTarget(self, action:#selector(self.buttonClicked), for: .touchUpInside)
self.view.addSubview(button)
func buttonClicked() {
print("Button Clicked")
instanceOfCustomObject.findFiles()
}
But it still works only if I place it into viewDidLoad. Please help me! Is there any other way to make it call in Swift file from Objective-C file??
Declare your var instanceOfCustomObject outside the viewDidLoad method (i.e., inside the class but outside the method).

Passing closure in swift as parameter to be used by selector in function

I am trying to create a generic button creation function into which I pass a closure that represents the action that results as a result of clicking on the button. My code is below. However, I get the following error:
Argument of #selector cannot refer to property. Any suggestions for a workaround ? I don't want to write separate functions for which everything else is the same except for the target action.
func myButton(textColor tColor:UIColor , title:String,
_ buttonFcn: (UIButton) -> Void,
titleSize:CGFloat=30) -> UIButton {
let newButton = UIButton(type: .System)
let bgColor = UIColor(red:204/255, green:204/255, blue:204/255, alpha:1.0)
newButton.backgroundColor = bgColor
newButton.setTitle(title, forState: .Normal)
newButton.setTitleColor(tColor, forState: .Normal)
newButton.titleLabel?.font = newButton.titleLabel?.font.fontWithSize(titleSize)
newButton.addTarget(self, action:#selector(buttonFcn),
forControlEvents:
UIControlEvents.TouchUpInside)
return newButton
}
The problem is that the target-action mechanism is an Objective-C mechanism, and therefore is predicated on the notion that the action selector is a method of an object. You need, therefore, to have some NSObject-based object that has this function as a method, and which can then serve as the target.
Thus, if what differs in every case is the target and the action, what you need to pass is a reference to the target along with the selector string. Swift will squawk at this, but if you know how to form a selector string correctly you can certainly get away with it; you just won't be able to use the #selector syntax, and so you will risk crashing if you form the selector string incorrectly. But it's the kind of thing we used to do all the time in the old Objective-C days, so go right ahead if that's your aim.
Totally artificial but working example:
func buttonMaker(target:NSObject, selectorString:String) -> UIButton {
let b = UIButton(type:.system)
b.setTitle("Testing", for: .normal)
b.addTarget(target, action: Selector(selectorString), for: .touchUpInside)
b.sizeToFit()
return b
}
And here's how to call it from a view controller:
func doButton(_ sender:Any) {
print("ha!")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let b = buttonMaker(target:self, selectorString:"doButton:")
b.frame.origin = CGPoint(x:100, y:100)
self.view.addSubview(b)
}
And when we tap the button, we don't crash (rather, we print "ha"), because I know how to make selector strings correctly. But, as you can see, to accomplish this I had to give up the use of #selector altogether, so safety is out the window. If I had written my selector string incorrectly — for instance, if I had spelled it wrong, or omitted the colon — we'd have crashed on the button tap, just like we used to all the time before Swift #selector and Objective-C #selector were invented.
If your deployment target is iOS 14 or later, you can use the addAction method instead of addTarget. The addAction method lets you use a closure instead of a selector:
func myButton(
textColor: UIColor,
title: String,
titleSize: CGFloat = 30,
_ handler: #escaping (UIButton) -> Void
) -> UIButton {
let button = UIButton(type: .system)
button.backgroundColor = UIColor(red: 204/255, green: 204/255, blue: 204/255, alpha: 1.0)
button.setTitle(title, for: .normal)
button.setTitleColor(textColor, for: .normal)
button.titleLabel?.font = button.titleLabel?.font.withSize(titleSize)
let action = UIAction { action in
guard let button = action.sender as? UIButton else { return }
handler(button)
}
button.addAction(action, for: .touchUpInside)
return button
}
iOS 14 was released on 2020-09-16 and supports iPhone 6S and later devices.

How to add a target action to a UITableViewCell

I've added a button to a specific UITableViewCell. When I select the button, I get a crash:
ButtonTapped
libc++abi.dylib: terminating with uncaught exception of type NSException
At the beginning of cellForRowAt, I'm defining the button:
let myButton = UIButton(type: .custom)
myButton.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
myButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
myButton.tintColor = UIColor.yellow()
For the indexpath.row I attach the button like this:
cell.accessoryView = myButton as UIView
And the action buttonTapped tries to load a different ViewController.
I get confirmation that the button action worked (the routine was called).
The routine is as follows:
func buttonTapped() {
print("ButtonTapped")
let myPickerController = self.storyboard?.instantiateViewController(withIdentifier: "picker") as? MyPickerController
print("1")
self.present(myPickerController!, animated: true)
print("2")
}
As you can see from the log, I do see that the routine was called, but I do not see the print values 1 or 2 before the crash. Anyone see what I'm doing wrong?
Add target like,
myButton.addTarget(self, action: #selector(YourControllerName.buttonTapped(_:)), for: .touchUpInside)
then change your function like,
func buttonTapped(sender : UIButton){
....
}
Hope this helps you.

Resources