How do you call the 'sender' of a UIButton in another function? - swift

I want to call the 'sender' of the pressed button in a new function.
I have created a generic function to perform standard operations within my calculator app. I then want to call this function for each type of operator that I have an IBAction linked to.
func standardOperation(sender: UIButton) {
if isOperator.contains(where: numberLabel.text!.contains) {
numberLabel.text = sender.currentTitle
firstValue = true
} else {
value = formatTextIntoDouble()
numberLabel.text = sender.currentTitle
firstValue = true
}
}
#IBAction func divideButtonPressed(_ sender: UIButton) {
operatorIndex = sender.currentTitle!
standardOperation(sender: xx)
operatorLabel.text = "\(prevValue) \(operatorIndex)"
}
However, I cannot seem to be able to find the right sender properties as I keep getting an error in the standardOperation(sender: xx) within each IBAction. What is the right way to call the sender of the UIButton that is pressed? I.e., what replaces the xx?
I've tried UIButton or AnyObject but none work.

Just pass in the sender that triggered divideButtonPressed
standardOperation(sender: sender)
I would also take a look at force unwrapping optionals and code defensively.
examples: numberLabel.text! and

Related

How to Pass the data given in UITextField to another variable on another class

So i have a UITextField named apiTextField and a function of the saveButton :
func saveButton(_ sender: Any) {
}
I want when the user writes to the UITextField and press the saveButton that text the user wrote be passed to a variable which is called var baseURL:String? in another class.
I didn't find anything related to UITextField so i decided to make this question, another similar one is 10 years old!.
var anotherClass = AnotherClass()
func saveButton(_ sender: Any) {
guard let text = apiTextField.text, !text.isEmpty else { return }
anotherClass.baseURL = text
}
Is that what you are looking for?

Swift - How to get the sender tag for an array of buttons using UILongPressGestureRecognizer?

I have buttons in the storyboard that I put into a Referencing Outlet Collection. I'm using UITapGestureRecognizer and UILongPressGestureRecognizer for all of these buttons, but how can I print exactly which button gets tapped? Bellow is what I tried but doesn't work. I get an error that says "Value of type 'UILongPressGestureRecognizer' has no member 'tag'." I'm trying to build the button grid for the Minesweeper game. Thank you for your help.
class ViewController: UIViewController {
#IBOutlet var testButtons: [UIButton]! // There are 100 buttons in this array
override func viewDidLoad() {
super.viewDidLoad()
let testButtonPressed = UILongPressGestureRecognizer(target: self, action: #selector(testPressed))
testButtonPressed.minimumPressDuration = 0.5
// These indexes are just to test how to recognize which button gets pressed
testButtons[0].addGestureRecognizer(testButtonPressed)
testButtons[1].addGestureRecognizer(testButtonPressed)
}
#objc func testPressed(_ sender: UILongPressGestureRecognizer) {
print("Test button was pressed")
print(sender.tag) // THIS DOESN'T WORK, BUT CONCEPTUALLY THIS IS WHAT I WANT TO DO
}
This error occurs because UILongPressGestureRecognizer object has no tag property
You can access sender's button in a way like that:
#objc func testPressed(_ sender: UILongPressGestureRecognizer) {
guard let button = sender.view as? UIButton else { return }
print(button.tag)
}
I think that the best solution to handle button's actions is to add #IBAction
(you can add it like #IBOutlet with a minor change - set Action connection type)
And then in #IBAction block you cann access all button properties (like tag and others)
instead of using gesture I think it would be better to use #IBAction and connect the buttons With it here is a small example
UILongPressGestureRecognizer which is a subclass of UIGestureRecognizer, can be used only once per button or view. Because UILongPressGestureRecognizer has only a single view property. In your code, it will always be testButtons[1] calling the testPressed action. So you have to first modify the viewDidLoad code like this :-
for button in testButtons {
let testButtonPressed = UILongPressGestureRecognizer(target: self, action: #selector(testPressed))
testButtonPressed.minimumPressDuration = 0.5
button.addGestureRecognizer(testButtonPressed)
button.addGestureRecognizer(testButtonPressed)
}
Then you can access the button from testPressed like this (I hope you've already set the tag in the storyboard) :-
#objc func testPressed(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
if let button = sender.view as? UIButton {
print(button.tag)
}
}
}
You need to set tags before pressing!
On the viewDidLoad() method you must add something like:
testButtons.enumerated().forEach {
let testButtonPressed = UILongPressGestureRecognizer(target: self, action: #selector(testPressed))
testButtonPressed.minimumPressDuration = 0.5
$0.element.addGestureRecognizer(testButtonPressed)
$0.element.tag = $0.offset
}
And when the long press is receiving you need to get a tag from view not from the sender!
print(sender.view?.tag)
Since a gesture recognizer should only be associated with a single view, and doesn't directly support using an identity tag to match it with buttons. When creating an array of buttons for a keyboard, with a single gesture response function, I found it easier to use the gesture recognizer "name" property to identify the associated button.
var allNames: [String] = []
// MARK: Long Press Gesture
func addButtonGestureRecognizer(button: UIButton, name: String) {
let longPrssRcngr = UILongPressGestureRecognizer.init(target: self, action: #selector(longPressOfButton(gestureRecognizer:)))
longPrssRcngr.minimumPressDuration = 0.5
longPrssRcngr.numberOfTouchesRequired = 1
longPrssRcngr.allowableMovement = 10.0
longPrssRcngr.name = name
allNames.append(name)
button.addGestureRecognizer(longPrssRcngr)
}
// MARK: Long Key Press
#objc func longPressOfButton(gestureRecognizer: UILongPressGestureRecognizer) {
print("\nLong Press Button => \(String(describing: gestureRecognizer.name)) : State = \(gestureRecognizer.state)\n")
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
if let keyName = gestureRecognizer.name {
if allNames.contains(keyName) {
insertKeyText(key: keyName)
} else {
print("No action available for key")
}
}
}
}
To implement, call the addButtonGestureRecognizer function after creating the button, and provide a name for the button (I used the button text) e.g.
addButtonGestureRecognizer(button: keyButton, name: buttonText)
The button name is stored in the "allNames" string array so that it can be matched later in "longPressOfButton".
When the button name is matched in the "longPressOfButton" response function, it passes it to "addKeyFunction" where it is processed.

Swift 4 - Creating a common function for multiple buttons

I'm wondering if there is a more efficient way to code an action that is the same with the exception of which button has been pressed and which item in a struct it relates to. Basically, I have a struct of 10 variables all of which are a boolean type and I have 10 buttons. When the user presses the button, I want to check whether it has already been pressed (using the struct) and then change the background of the button depending on the state and reverse the state. I've copied my current code for one of the buttons but thought I should be able to avoid doing this 10 times!
#IBAction func architectureButtonPressed(_ sender: Any) {
if myInterests.architecture {
myInterests.architecture = false
architectureButton.setBackgroundImage(imageUncheckedNarrow, for: .normal)
} else {
myInterests.architecture = true
architectureButton.setBackgroundImage(imageCheckedNarrow, for: .normal)
}
}
Well one simple way is to have each UIButton point to the same architectureButtonPressed IBAction method. Since the button that's pressed is passed into the method (sender) you can consult it's tag property to know the index of which field in your struct should be updated. (And then you might want to change your struct to just store an array of 10 bools, but up to you).
Then for each UIButton, whether programmatically in storyboard or nib, you'd assign the appropriate index value to the button's tag field.
Create yours IBOutlet for each button.
Create a array and store all buttons like : var arrayButtons : [UIButton] = []
arrayButtons.append[button1]
arrayButtons.append[button2]
...
Create a array of booleans to store true/false: var arrayBools : [Bool] = [] and initialize if some value.
Note that the indexes of the arrayButtons and arrayBools must be same related.
Create selector function to listen touch buttons.
button.addTarget(self, action: #selector(my_func), for: .touchUpInside)
#objc func my_func(_ sender : UIButton) {
for i in 0...arrayButtons.size-1 {
if arrayButtons[i] == sender {
if arrayBooleans[i] {
arrayBooleans[i] = false
arrayButtons[i].setImage()
} else {
arrayBooleans[i] = true
arrayButtons[i].setImage()
}
}
}
}
My suggestion is to manage the images in Interface Builder via State Config (Default/Selected)
Then assign an unique tag starting from 100 to each button and set the isSelected value in the IBAction to the corresponding struct member in a switch statement:
#IBAction func buttonPressed(_ sender: UIButton) {
switch sender.tag {
case 100: myInterests.architecture = sender.isSelected
case 101: myInterests.art = sender.isSelected
...
default: break
}
}
Alternatively use Swift's native KVC with WriteableKeypath
let keypaths : [WritableKeyPath<Interests,Bool>] = [\.architecture, \.art, \.fashion, \.history, \.localCulture, \.music, \.nature, \.shopping, \.sport, \.anything]
#IBAction func buttonPressed(_ sender: UIButton) {
let index = sender.tag - 100
let keypath = keypaths[index]
myInterests[keyPath: keypath] = sender.isSelected
}

How do I return an Int variable from an #IBAction function in swift 3?

I am trying to return a value that I have made in my Xcode project. The Int value is made in the #IBAction function of a stepper.
#IBAction func stepper(_ sender: UIStepper) -> Int {
let Number: Int = Int(sender.value)
return Number
print(Number)
The system is giving me this error: "Methods declared #IBAction must return 'Void' (not'Int')".
#IBAction is an inbuilt attribute used to fire methods that would perform certain tasks based on user's interaction and cannot return values/objects. What you can do is trigger other actions, or initialize other global/local variables within the action method.
The error - Methods declared #IBAction must return 'Void' (not'Int') simply means that an IBAction method cannot return anything and must return void aka nothing.
Based on your comment on using the stepper's value for a UIButton this is what you can do-
At the class level for the View Controller declare a variable
var stepperValue: Int = 0 {
didSet{
// use stepperValue with your UIButton as you want
}
}
And then the #IBAction-
#IBAction func stepper(_ sender: UIStepper){
stepperValue = Int(sender.value)
}
Everytime, stepperValue is set inside the #IBAction method the code block inside the didSet observer will fire and the stepperValue's current value can be accessed inside the didSet observer code block to be used in any logic you want there.
OR, you could simply put the entire didSet observer block code inside of the IBAction method stepper.
OR, you could write another method func modifyMyButton(_ stepperval: Int) put your logic there and call this method from inside the IBAction method.
#IBAction func are system based functions that are triggered on events based on user interaction. They don't return you anything. They call functions for you, if you want. You need to describe what scenario are you trying to implement.

Triggering function with parameters upon UIButton press

I'm trying to call a function with parameters on a button press, from what I understand #selector just checks that a function is there and then runs it. I've seen other answers where the button can be sent to the function but sadly I don't think that will solve my problem.
If I run this code (func is any function and a: 14 is just an example of a parameter being given):
myButton.addTarget(self, action: #selector(func(a: 14)), for: .touchUpInside)
I get an error saying 'Argument of #selector does not refer to an #objc method, property, or initializer
A workaround that I've been using:
myButton.addTarget(target: self, action: #selector(myFunc), for: .touchUpInside)
func myFunc() {
someOtherFunc(args)
}
The problem with this is that unless the argument that was going to be passed is global or class wide and known, you wont be able to use it.
Main Question:
Is there a way to have it run a function with parameters when a button is clicked without setting a class wide variable and assessing that instead of using a parameter?
My Solution:
Simplified, and buttons aren't setup and such...
var personName:String!
func scrollViewClicked(name:String) {
personName = name
myButton.addTarget(target: self, action: #selector(myFunc), for: .touchUpInside)
}
func myFunc() {
do something with personName
}
So pretty much I have a way of 'solving' the problem but it feels like a bit of a hack/improper way. Just trying to figure out if there is a 'real' way to do this or if it isn't meant to happen.
No, there's no way to have a UIButton run a function with arbitrary parameters.
But there is a way to have it run with some parameters, which may be useful to you.
The documentation for addTarget says that it takes a selector, which is essentially just a reference to a method. If you pass it a method with the right set of arguments, it will call it and pass whatever it's designed to pass. If you send a method with other arguments, you'll get an "unrecognized selector" error.
UIControl's addTarget understands three kinds of selectors:
func myFunc()
func myFunc(sender: UIButton)
func myFunc(sender: UIButton, forEvent event: UIEvent)
So you can set it to run a function with parameters, but the only parameters it knows how to send are the button that was pressed and the event it generated.
This is still potentially useful though, if you can use information about the button and/or the event to determine your action. For example you can set up your handler:
func myFunc(sender: UIButton, forEvent event: UIEvent) {
switch(sender) {
case myButton:
print("myButton was pressed")
default:
print("Something else was pressed.")
}
}
Depending on your use case, you could make use of the button's storyboard restoration ID, its title or other identifier, or you could even subclass UIButton and give it an instance variable to hold your parameter, like this:
class MyButtonClass: UIButton {
var argument:String = ""
}
Then when you're setting up your button you specify the argument:
myButton.argument = "Some argument"
And you can access it from your handler like this:
func myFunc(sender: UIButton, forEvent event: UIEvent) {
if let button = sender as? MyButtonClass {
print(button.argument)
}
}
It's still not as neat as just specifying your parameter in the selector, but as far as I know that's not possible.