addTarget not working properly - ios

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.

Related

Swift | UIButton causes crash on tap

I added a button on the main screen of the app and on the tap of a button, a new viewcontroller is presented.
This works completely fine in the simulator but as soon as I try in an actual iPhone, it causes the app to crash.
Also, the crash is only caused on the login button while the sign up button made the same way does work perfect
I will leave the code below
var loginButton = UIButton()
var signUpButton = UIButton()
loginButton.setTitle("Login", for: .normal)
loginButton.titleLabel?.textAlignment = .center
loginButton.backgroundColor = appGreenTheme
loginButton.titleLabel?.textColor = .white
loginButton.layer.cornerRadius = 20
loginButton.titleLabel?.font = UIFont.systemFont(ofSize: 20)
loginButton.setBackgroundImage(UIImage(named: "pinkOrangeGradientPDF"), for: .normal)
loginButton.clipsToBounds = true
signUpButton.setTitle("Sign Up", for: .normal)
signUpButton.setTitleColor(.black, for: .normal)
signUpButton.titleLabel?.textAlignment = .center
signUpButton.backgroundColor = .white
signUpButton.titleLabel?.textColor = .black
signUpButton.layer.cornerRadius = 20
signUpButton.titleLabel?.font = UIFont.systemFont(ofSize: 20)
loginButton.addTarget(self, action: #selector(loginButtonTapped1(_:)), for: .allTouchEvents)
signUpButton.addTarget(self, action: #selector(signUpButtonTapped1(_:)), for: .allTouchEvents)
///////////////////////////////////////////////////
#objc func loginButtonTapped1(_ sender: UIButton) {
let nav = UINavigationController(rootViewController: LoginViewController())
self.present(nav, animated: true, completion: nil)
}
#objc func signUpButtonTapped1(_ sender: UIButton) {
let nav = UINavigationController(rootViewController: SignUpViewController())
self.present(nav, animated: true, completion: nil)
}
I also tried with "touchUpInside" events. again it works perfectly in the simulator but not in a physical device.
Any help is welcome.
Below is the error shown in the logs
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SparkGPS.LoginView addTarget:action:forControlEvents:]: unrecognized selector sent to instance 0x13dd4c740'
The answer is in the error message. Somewhere, my guess is in LoginViewController, there is a view of type LoginView. That view is calling addTarget(_:action:for:). LoginView is not subclassed from UIControl and does not have addTarget(_:action:for:). It's causing the crash.
Let me break down the parts of -[SparkGPS.LoginView addTarget:action:forControlEvents:].
The - at the beginning means it's an instance method and not a static or class method.
SparkGPS.LoginView is the module and class. A module is another word for a framework or app. In this case, it looks like you have an app named SparkGPS and a class named LoginView.
addTarget:action:forControlEvents: is Objective-C's name for addTarget(_:action:for:).
Finally, "selector sent to instance" means the variable call a method. Selector is a way to identify a method, and an instance is stored in a variable. For example, in your code you have loginButton.setTitle("Login", for: .normal). This could be worded as setTitle(_:for:) was sent to the instance loginButton.
You can add a tap gesture recogniser to the button itself. It's best practice to use outlets, but this works fine and is useful for other UI components like views or labels too
let loginTapGesture = UITapGestureRecognizer(target: self,
action: #selector(loginButtonTapped1))
loginButton.addGestureRecognizer(loginTapGesture)

UIButton selector is not working

I have a view that is placed as a subview of navigationController in order to fill the whole display. On this view I have a subview that has two buttons. "Remove and Done". and then it also has a datepicker. The datePicker works, however, the Remove and Done buttons are not firing the action functions.
The buttons:
var setButton: UIButton = {
var button = UIButton(type: .system)
button.setTitle("Done", for: .normal)
button.tintColor = .white
button.addTarget(self, action: #selector(handleReminderSetBtn), for: .touchUpInside)
return button
}()
var cancelButton: UIButton = {
var button = UIButton(type: .system)
button.setTitle("Remove", for: .normal)
button.tintColor = .white
button.addTarget(self, action: #selector(handleReminderCancelBtn), for: .touchUpInside)
return button
}()
The main blackView that is in the navigationController:
viewOverLay.addSubview(cardreminder1)
viewOverLay.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height)
viewOverLay.backgroundColor = UIColor.black.withAlphaComponent(0.3)
self.navigationController?.view.addSubview(viewOverLay)
CardReminder1 is the UIView on which I have two buttons.
I reckon there is some issue with the target in the addTarget method of the two buttons. What could be the issue?
You shouldn't initialize setButton and cancelButton 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:
addTarget(self...)
so to fix the issue you should move the buttons initialization (or move the addTarget) after your CardReminder1 is fully initialized.
I just had a similar issue. Along with the other answer posted, making the property lazy worked in my case as well. It depends on when you first try to access that property, but in my case I only accessed it after initialization was complete, so making the property lazy worked perfectly.
For example in your case, the following might work:
lazy var setButton: UIButton = {
var button = UIButton(type: .system)
button.setTitle("Done", for: .normal)
button.tintColor = .white
button.addTarget(self, action: #selector(handleReminderSetBtn), for: .touchUpInside)
return button
}()
lazy var cancelButton: UIButton = {
var button = UIButton(type: .system)
button.setTitle("Remove", for: .normal)
button.tintColor = .white
button.addTarget(self, action: #selector(handleReminderCancelBtn), for: .touchUpInside)
return button
}()

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.

Unrecognized Selector when Passing Parameter - UIButton in TableView Cell | Swift 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)

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