Assign 'action' dynamically in uibutton.addTarget - ios

please bear with me, as I'm new to swift -4 weeks old-.
I've created the following 2 functions in fileA.swift
func custombttn(theSelector:Selector)-> UIButton{
let bttn = UIButton(frame: CGRect(x:20, y:400, width:200, height:30))
bttn.setTitle("tap this button", for: UIControlState.normal)
bttn.backgroundColor = UIColor.black
bttn.setTitleColor(UIColor.magenta, for: UIControlState.normal)
bttn.addTarget(bttn, action: theSelector, for: UIControlEvents.touchUpInside)
return bttn
}
func customtxtfld() -> UITextField{
let txtField = UITextField(frame: CGRect(x:20, y:360, width:200, height:30))
txtField.borderStyle = UITextBorderStyle.roundedRect
txtField.backgroundColor = UIColor.magenta
txtField.placeholder = "Do you like me now..?"
return txtField
}
The reason behind the custombttn(theSelector:Selector), is that i want to pass the function dynamically to the button in my viewcontroller file.
Now, moving the fileB.swift, I have the following code...
class TabOneViewController: UIViewController{
let txt = customtxtfld()
let bttn = custombttn(theSelector: #selector(updatetxt))
override func loadView() {
super.loadView()
view.addSubview(txt)
view.addSubview(bttn)
}
func updatetxt(){
txt.text = "hello, you!"
}
}
Here is where things get tricky, when I attempt to build, I don't get any error (not even a warning). However, when I run the app, and tap the bttn in fileB.swift, I get the following error during runtime:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[UIButton updatetxt]:
unrecognized selector sent to instance 0x7f8453415670'
If I have 2 or more functions in my fileB.swift that I wish to assign dynamically to the action part of the addTarget, is there any way I can pass the selector dynamically to a button..?
Appreciate your time and assistance. Please let me know if I need to explain something further.

It's crashing because your button target is wrong.
func custombttn(target:Any, theSelector:Selector)-> UIButton{
let bttn = UIButton(frame: CGRect(x:20, y:400, width:200, height:30))
bttn.setTitle("tap this button", for: UIControlState.normal)
bttn.backgroundColor = UIColor.black
bttn.setTitleColor(UIColor.magenta, for: UIControlState.normal)
bttn.addTarget(target, action: theSelector, for: UIControlEvents.touchUpInside)
return bttn
}
And use it like this
class TabOneViewController: UIViewController{
let txt = customtxtfld()
override func loadView() {
super.loadView()
view.addSubview(txt)
let bttn = custombttn(target:self, theSelector: #selector(updatetxt))
view.addSubview(bttn)
}
func updatetxt(){
txt.text = "hello, you!"
}
}

Yes, you can. The issue here is that you passed the button itself as the target for the action. Just pass the correct target when adding the action, which in this case is the instance of your view controller.

Related

Swift - open URL from button click

I am learning Swift and iOS development, and I am just trying to figure out how to open an URL from a button click.
I found this answer: SwiftUI: How do I make a button open a URL in safari?
So I am trying to incorporate "Link" into my code below:
class ViewController: UIViewController {
private let visitwebsitebutton: UIButton = {
let visitwebsitebutton = UIButton()
visitwebsitebutton.backgroundColor = .gray
visitwebsitebutton.setTitle("Visit Website", for: .normal)
visitwebsitebutton.setTitleColor(.white, for: .normal)
visitwebsitebutton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
visitwebsitebutton.layer.cornerRadius = 20
visitwebsitebutton.Link("Some label", destination: URL(string: "https://www.mylink.com")!) // <-- link used here
return visitwebsitebutton
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(visitwebsitebutton)
}
}
Using Link above gives me an error that reads "Value of type 'UIButton' has no member 'Link'".
What am I doing wrong and how can I fix it?
Edit 1
I just tried this inside private let visitwebsitebutton:
visitwebsitebutton(action: {"www.redacted.com"})
But now I'm getting the below error:
Cannot call value of non-function type 'UIButton'
Edit 2
Within private let visitwebsitebutton, I attempted the following:
visitwebsitebutton.addTarget(self, action: "buttonClicked", for: UIControl.Event.touchUpInside)
Using the above, I am getting a few warning:
'self' refers to the method 'ViewController.self', which may be unexpected
Use 'ViewController.self' to silence this warning
No method declared with Objective-C selector 'buttonClicked'
Replace '"buttonClicked"' with 'Selector("buttonClicked")'
I tried to call the buttonClicked like this:
#objc func buttonClicked(sender:UIButton)
{
if(sender.tag == 5){
var abc = "argOne" //Do something for tag 5
}
print("hello")
}
And above, I am getting the below warning:
Initialization of variable 'abc' was never used; consider replacing with assignment to '_' or removing it
Replace 'var abc' with '_'
I just want to get the button to work.
This is how I solved the problem:
class ViewController: UIViewController {
private lazy var visitwebsitebutton: UIButton = {
let visitwebsitebutton = UIButton()
let mygreen = UIColor(rgb: 0x12823b)
visitwebsitebutton.backgroundColor = mygreen
visitwebsitebutton.setTitle("Visit Website", for: .normal)
visitwebsitebutton.setTitleColor(.white, for: .normal)
visitwebsitebutton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
visitwebsitebutton.layer.cornerRadius = 20
visitwebsitebutton.addTarget(self, action: #selector(visitwebsitebuttonTapped), for: .touchUpInside)
return visitwebsitebutton
}()
#objc func visitwebsitebuttonTapped() {
if let yourURL = URL(string: "https://www.somesite.com") {
UIApplication.shared.open(yourURL, options: [:], completionHandler: nil)
}
}
}
If anyone needs help with iOS mobile development with Swift, and you just want to be able to click on a button and have it take you to a site, look no further.

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)

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.

unrecognized selector sent to instance in swift building a UISlider

ok i'm trying to build a simple UISlider in swift and i always get the same error when i run my code, it's a sigbrt error and it gives me this error:
2015-06-03 22:36:52.659 myslider[2780:224039] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[myslider.ViewController sliderValueChanged:]: unrecognized selector sent to instance 0x7f92f1e25340'
* First throw call stack:
here's the code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var ValueLabel: UILabel!
var slider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
slider = UISlider(frame: CGRectMake(100, 100, 200, 23))
slider.minimumValue = 0
slider.maximumValue = 100
view.addSubview(slider)
slider.center = view.center
slider.value = slider.maximumValue / 3.0
slider.addTarget(self, action: "sliderValueChanged:", forControlEvents: UIControlEvents.ValueChanged)
slider.continuous = false
salueDidChange(slider)
ChangeColor()
thumbImage()
}
func salueDidChange(sender: UISlider){
ValueLabel.text = "\(sender.value)"
}
func ChangeColor(){
slider.maximumTrackTintColor = UIColor.redColor()
slider.minimumTrackTintColor = UIColor.greenColor()
}
func thumbImage(){
slider.setThumbImage(UIImage(named: "thumbNormal"), forState: UIControlState.Normal)
slider.setThumbImage(UIImage(named: "thumbHighlighted"), forState: UIControlState.Highlighted)
}
}
The error is telling you exactly what's wrong. You're creating a UISlider in code. You set up it's target like this:
slider.addTarget(
self,
action: "sliderValueChanged:",
forControlEvents: UIControlEvents.ValueChanged)
So when you change the value of your slider, the method is going to try to call a method "sliderValueChanged:" in your view controller. That method needs to take 1 parameter, a sender:
#IBAction func sliderValueChanged(sender: AnyObject)
{
//Do something with the new slider value.
}
The type of sender can also be type UISlider.
If you don't have a method with that signature in your view controller, you will crash when you change the slider value, just like you say you are.
EDIT:
As others have pointed out in their comments/answers, it looks like your target method is the misnamed method salueDidChange. You should rename it.
It's also a good idea to put the #IBAction tag on methods that will be called from controls. You must do that if you're going to define the control and connect it's action in Interface Builder (which is a good idea, rather than doing it in code.)
With slider.addTarget(self, action: "sliderValueChanged:", forControlEvents: UIControlEvents.ValueChanged) you're telling Swift to call the method sliderValueChanged: on your ViewController, but there is no such method in your ViewController. That's what the error is telling you. Most likely you wrote salueDidChange for the same purpose so you should rename salueDidChange to sliderValueChanged and add #IBAction.
If that doesn't work, try connecting the event handler from your storyboard:
Remove the slider.addTarget line of code.
Right click your slider in your storyboard. See if there's an event handler hooked up for the value changed event. If so remove it using the little x.
Ctrl-drag from your slider to your sliderValueChanged method to hook it up again.
Jonathan, I had a similar frustrating experience with unrecognized selector and UISlider. Others have already said salueDidChange is a typo but it isn't the only issue. The syntax for selector has been a source of confusion for newcomers (like me) as Swift evolved.
The correct syntax for selector is explained in a nut shell in this answer to an unrelated problem.
“Using #selector will check your code at compile time to make sure the
method you want to call actually exists. Even better, if the method
doesn’t exist, you’ll get a compile error: Xcode will refuse to build
your app, thus banishing to oblivion another possible source of bugs.”
Your question along with answers here helped me reach a working solution in Swift 3 below.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var ValueLabel: UILabel!
var slider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
slider = UISlider(frame: CGRect(x: 100, y: 100, width: 200, height: 23))
slider.minimumValue = 0
slider.maximumValue = 100
view.addSubview(slider)
slider.center = view.center
slider.value = slider.maximumValue / 3.0
slider.addTarget(self, action: #selector(sliderValueChanged), for: UIControlEvents.valueChanged)
slider.isContinuous = false
changeColor()
thumbImage()
}
func sliderValueChanged(sender: UISlider){
print(sender.value)
// ValueLabel.text = "\(sender.value)" // Note: not included in this test!!!
}
func changeColor(){
slider.maximumTrackTintColor = UIColor.red
slider.minimumTrackTintColor = UIColor.green
}
func thumbImage(){
slider.setThumbImage(UIImage(named: "thumbNormal"), for: UIControlState.normal)
slider.setThumbImage(UIImage(named: "thumbHighlighted"), for: UIControlState.highlighted)
}
}

Resources