Swift 4 - Creating a common function for multiple buttons - swift

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
}

Related

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.

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

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

UICollectionView Cell Button Long Press Gesture return self keep changing?

I'm having something like a shopping cart page, it shows the products the user selected in a collection view, with each cell having 2 button linked to the product that will increase/decrease the amount of product to a singleton cartManager.
So far everything is working, i used protocol to ensure i know which product in the collection view that i'm adding/subtracting the product from. With this code:
protocol CartProductButtonDelegate : class {
func cartProductPlus(_ sender: CartProductCell)
func cartProductMinus(_ sender: CartProductCell)
}
class CartProductCell: UICollectionViewCell{
//labels and image product details etc.
#IBOutlet weak var productMinusBtn: UIButton!
#IBOutlet weak var productPlusBtn: UIButton!
weak var delegate : CartProductButtonDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let tapPlusGesture = UITapGestureRecognizer(target: self, action: #selector(productPlusBtnTapped(_:)))
tapPlusGesture.numberOfTapsRequired = 1
self.productPlusBtn.addGestureRecognizer(tapPlusGesture)
let tapMinusGesture = UITapGestureRecognizer(target: self, action: #selector(productMinusBtnTapped(_:)))
tapMinusGesture.numberOfTapsRequired = 1
self.productMinusBtn.addGestureRecognizer(tapMinusGesture)
}
#objc func productMinusBtnTapped(_ sender: UITapGestureRecognizer) {
delegate?.cartProductMinus(self)
}
#objc func productPlusBtnTapped(_ sender: UITapGestureRecognizer) {
delegate?.cartProductPlus(self)
}
}
And in my UIViewController, i add the collectionview delegate,datasource, and the custom protocol and make all the cell's delegate to the viewcontroller in cellForItem. Everytime i add or subtract the product, i reload the collectionview in order to show the correct amount on the cell label.
func cartProductPlus(_ sender: CartProductCell) {
guard let tappedIndexPath = self.cartCollectionView.indexPath(for: sender) else {
debugPrint("GUARD BROKE GETTING INDEX PATH FOR PRODUCT PLUS TAPPED")
return
}
let product = self.productList[tappedIndexPath.item]
debugPrint("cart Product Plus on product name: \(product.name), index : \(tappedIndexPath.item)")
if let maxBought = Int(product.maxBought ?? ""){
if cartManager.numberOfProductsInCart(product: product) < maxBought{
cartManager.addProduct(product: product)
}
}
self.rearrangeArray()//this is to reload the collection view as well as update UI on cart and someother stuff
}
The problem arise when i tried to add long press gesture using existing logic, for those people who want to buy in bulk.
I've tried to implement this:
let longPressPlusGesture = UILongPressGestureRecognizer(target: self, action: #selector(productPlusLongPressed(_:)))
self.productPlusBtn.addGestureRecognizer(longPressPlusGesture)
#objc func productPlusLongPressed(_ sender: UILongPressGestureRecognizer){
if sender.state == .began || sender.state == .changed{
delegate?.cartProductPlus(self)
}
}
However when i long pressed the button, the item its adding are mixed up, the debug message are showing the collection view cell's index i'm receiving is going up in ascending order 0,1,2,3 then repeat 0,1,2,3 (depending on how many products are there in the collection view cell)
So question, is there a way to fix this? should i not reload collection view when i'm long pressing, if so, how do i update the UI to inform the user. Is there other method to work around the problem or should i just give up the idea of long press and just allow the user to tap the amount and edit it?
Alright, found a work around. After i've implemented the long press delegate, i split the protocol to two extra functions, one for when long press began/still press and the other for when long press ends.
func cartProductLongPlusStarted(_ sender: CartProductCell)
func cartProductLongMinusStarted(_ sender: CartProductCell)
func cartProductLongPlusEnded(_ sender: CartProductCell)
func cartProductLongMinusEnded(_ sender: CartProductCell)
However i do not update the UI from the viewcontroller when long press is active, i just update them from the cell itself. The cell will just hardcode to update the UI and only when the long press is finished, the view controller will just update the UI again.
#objc func productPlusLongPressed(_ sender: UILongPressGestureRecognizer){
if sender.state == .began || sender.state == .changed{
delegate?.cartProductLongPlusStarted(self)
if var amount = Int(self.productCountLabel.text ?? "0"){
if self.maxAmount != nil{
if amount < self.maxAmount!{
amount += 1
}
}else{
amount += 1
}
self.productCountLabel.text = String(amount)
}
}else{
delegate?.cartProductLongPlusEnded(self)
}
}
The only minor issue is that the long press seems to update too fast, the value might be updating abit too fast for the user to react when to stop properly, any idea to slow the updating the long press function call a bit?

Userdefaults Boolean for button

I am fairly new to Swift programming. Using Userdefaults I was trying to customize user behaviour. Below image is of my initial controller. I require to save userdefaults so that App remembers the user selection of button, (i.e. A or B). Can you assist to provide me a function that I use in viewDidLoad and it remembers the button selection and segues to its respective ViewController.
My code to perfrom segue if Button A or B is selected is
let parent = self.storyboard?.instantiateViewController(withIdentifier: "DashboardVC") as! DashboardVC
self.navigationController?.pushViewController(parent!, animated: true)
Yet it doesnt segue. It keeps loading my initial viewcontroller.
do like
set the tag for each button and create the common method for handle the function , for e.g
#IBAction func handle_Action(_ sender: UIButton) {
defaultName.set(sender.tag, forKey: "yourKeyName")
}
and in your class
class ViewController: UIViewController {
let defaultName = UserDefaults.standard
// finally access the integer in your Viewload
override func viewDidLoad() {
super.viewDidLoad()
let getVal = defaultName.integer(forKey: "yourKeyName") as Int
if getVal == 1{ //called by A
}else if getVal == 2{
//called by B
}else{ // not interactwithButton action }
}

Make CheckBox in swift for IOS

Is it possible to achieve this using swift?
I would like to make many checkboxes inside an UIView in swift
Simple just check for the image loaded on button and take appropriate action see below code:
// declare bool
var unchecked = true
#IBAction func tick(sender: UIButton) {
if unchecked {
sender.setImage(UIImage(named:"checked.png"), forControlState: .Normal)
unchecked = false
}
else {
sender.setImage( UIImage(named:"unchecked.png"), forControlState: .Normal)
unchecked = true
}
}
Note:
You need to use two different images named as checked and unchecked.
Then above code is used for separate button (checkmarks) you need to create.
Simply add a UIButton on the board. Remove the text and add an image for a unselected(default) state and a selected state.
On the ViewController add an #IBAction for the function as follows:
#IBAction func check(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
}
make a UI button
set an uncheckedImage for your button for UIControlStateNormal and a checkedImage for your UIControlStateSelected.
Now on taps the button will change its image and alternate between checked and unchecked image.
Use for loop to create multiple checkboxes and set the x position.Set tag for each to identify when tap.
You can easily create simple checkbox control in swift like these...
#IBAction func btn_box(sender: UIButton) {
if (btn_box.selected == true)
{
btn_box.setBackgroundImage(UIImage(named: "box"), forState: UIControlState.Normal)
btn_box.selected = false;
}
else
{
btn_box.setBackgroundImage(UIImage(named: "checkBox"), forState: UIControlState.Normal)
btn_box.selected = true;
}
}
Swift 4
let checkedImage = UIImage(named: "checked")! as UIImage
let uncheckedImage = UIImage(named: "unchecked")! as UIImage
#IBAction func tickavtion(_ sender: Any) {
if unchecked {
(sender as AnyObject).setImage(checkedImage, for: UIControlState.normal)
unchecked = false
}
else {
(sender as AnyObject).setImage(uncheckedImage, for: UIControlState.normal)
unchecked = true
}
}
which kind of checkbox you intend to make? Multi-selection or radio btn? Have you tried doing it in OC? I advice you 1) to add the btns into an array after you initialized them. 2) In the touch event you traverse the btns to implement your logic.