What is the convention to handle secondary action of UIButton in iOS - ios

I have a UIButton which is used to do something when user touch the button. I did it in Touch Up Inside. I have to implement another function which is treated as like a secondary action for that UIButton.
What is the convention in iOS to handle more than one action for same UIButton? Should I use
double-tap action
or
long press gesture
or anything else?

For maintaining the App I would not advise implementing 2 IBactions on a button.
I would suggest creating an IBAction routine which initiates both actions.

Set tag of the button inside method, set default tag is 0,
- (IBAction)action:(id)sender {
UIButton *mybtn = (UIButton*)sender;
if (mybtn.tag == 0) {
//perform fist action
//set btn tag to 1
mybtn.tag = 1;
}
else if (mybtn.tag == 1)
{
//perform second action
//set btn tag to 0
mybtn.tag = 0;
}

As Imad suggested, use a bool called isClickedOnce (default is false), when user click first time then set value to true.
var isClickedOnce = false
func switchAction(){
if isClickedOnce{
print("Second")
}else{
isClickedOnce = true
print("First")
}
}

Related

Why use tags with UIButtons?

I'm studying Swift and I have a question.
When we make some button's Action function, sometimes tags are used.
After all to set button's tags with code, we must connect button's Outlet from storyboard.
Why use tags instead of outlet's variable name?
Any number of buttons can have a single action and we then need tag to distinguish action based on button tag. you don't actually need outlet for each button if you are setting tag from storyboard, Here is a detailed articles about tags:
Working With Multiple UIButtons and Utilizing Their Tag Property
Many cases many button have the same ibaction. In this situation , tag can help
As others have said the function could be completely independent of the button, it could be in another class altogether.
Buttons may not be in StoryBoards and could be created programatically, potentially dynamically, so there maybe no case of checking if the button passed to the function is the same as a local variable (as a button may not be an ivar on the class the function is based on)
Tags give an easy way to provide some identifier to button or view that can be cross referenced when the function is called in order to achieve the desired out come
Here's an example to illustrate, bear in mind this is somewhat contrived in order to provide an detail
In you ViewController you need to dynamically create a button but don't want to store it as a variable on the VC. You also have a separate class which is handling taps
We could create an enum to handle button types, this handles the tag and title for the button type
enum ButtonType: String {
case nonIvar
case other
var tag: Int {
switch self {
case .nonIvar:
return 0
case .other:
return 1
}
}
var title: String {
switch self {
case .nonIvar:
return "Non iVar Button"
case .other:
return "Some other Button"
}
}
}
we then have our class that handles the tap function
class ButtonHandler {
#IBAction func handleTap(_ sender: UIButton) {
if sender.tag == ButtonType.nonIvar.tag {
// do something with the none ivar buton
} else {
// handle other type
}
}
}
then in our view controller we create our button hooking the two instances above up
let handler = ButtonHandler()
func addMyButton() {
let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
let buttonType: ButtonType = .nonIvar
myButton.setTitle(buttonType.title, for: .normal)
myButton.tag = buttonType.tag
myButton.addTarget(self, action: #selector(handler.handleTap(_:)), for: .touchUpInside)
view.addSubview(myButton)
}
This means that the handler can be used on any class and the button types could be equally used throughout the app and the behaviour would be the same without duplicating code
Hope this helps!

Check for condition befor executing button selector

I have multiple buttons with different functions, now these buttons should check for a condition befor executing the actual functionality, I don't won't to change all buttons functions like the following:
#objc func btnAction(sender: UIButton) {
if condition {
runSomthing()
return
} else {
run the actual btn action
}
}
I need to extract this to a superclass that accomplishes what I looking for without changing the current button actions
Is there a correct way to do it?
One of the solutions is to link all the buttons to the same IBAction and perform corresponding button functionalities using the button tag value. This prevents the condition checking in each IBAction.
guard let button = sender as? UIButton else {
return
}
if condition {
runSomthing()
return
} else {
switch button.tag {
case 1:
Perform operation A
case 2:
Perform operation B
case 3:
Perform operation C
default:
print("None")
return
}
}
Enums can also be used instead of tag value for better identification. I referred How to use one IBAction for multiple buttons in Swift?

iOS Refresh Accessibility Label

I have a button with accessibility label #"a". When the button is pressed, I have a callback that sets the accessibility label button.accessibilityLabel = #"b". I know this line of code runs. However, if I tap the button again, VoiceOver still reads a. Unfortunately, the code I'm working with is proprietary, so I can't share it directly.
However, in general, I would like to know what issues might cause VoiceOver to not recognize an update to a label.
THE BEST way to handle dynamic accessibility labels is to override the property functions on the views that are being focused (EX: on a UIButton). This allows TWO things. A: it's a lot easier to maintain than setting the property everywhere it can change. B: you can log information and see when the system is requesting that information, so you can better understand WHY things are happening. So even if it doesn't directly fix your issue, seeing WHEN the system requests your value, and logging that data, is inherently valuable.
Doing this in Objective C
#implementation YourUIButton
-(NSString*)accessibilityLabel {
if(someCondition) {
return #"a";
} else {
return #"b";
}
}
#end
In Swift
public class YourUIButton : UIButton
override public var accessibilityLabel: String? {
get {
if (someCondition) {
return "a"
} else {
return "b"
}
}
set {
NSException.raise(NSException("AccessibilityLabelException", "You should not set this accessibility label.", blah))
}
}
}
You could also use this logic JUST to debug, and allow setting and such.
There are a lot of potential issues here. Race conditions, which view is actually getting focus, is there some parent child relationship going on, etc. Overriding the property and adding logging statements to the above code will help you understand what view is actually getting the accessibility label requested and when. Super valuable information!
Use this while changing button text
UIAccessibility.post(notification: .layoutChanged, argument: yourButton)
Try to add UIAccessibilityTraitUpdatesFrequently to your buttons property accessibilityTraits
- (void)viewDidLoad {
myButton.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently
}
Also, when changing accessibilityLabel be sure that you're on main thread.
dispatch_async(dispatch_get_main_queue(), ^{
myButton.accessibilityLabel = #"b";
});
You don't really need a way to refresh the voice over labels. Its done automatically. I have tried this and it works as expected.
class ViewController: UIViewController {
var tapCount = 0
var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
button = UIButton(type: .system)
button.setTitle("Hello", for: .normal)
button.frame = CGRect(x: 10, y: 10, width: 100, height: 50)
view.addSubview(button)
button.accessibilityLabel = "Hello Button"
button.accessibilityHint = "Tap here to start the action"
button.accessibilityIdentifier = "hello_button"
button.addTarget(self, action: #selector(buttonTap(sender:)), for: .touchUpInside)
}
#IBAction func buttonTap(sender:UIButton) {
tapCount = tapCount + 1
sender.accessibilityLabel = "Hello button, tapped \(tapCount) times"
}
}
What Voice oversays:
Hello Button-pause-Button-pause-Tap here to start the action
On button tap
Hello button, tapped one times
Another tap
Hello button, tapped two times

Touch Up Inside not working properly

I have an app with some buttons, when those buttons are pressed the image on them should change. I assume that the TouchUpInside runs when you tap and remove the finger while still holding inside the area of the element, however it only works rarely and I'm not sure why.
The reason I use TouchUpInside instead of TouchDown is because I want the user to be able to cancel the action.
I'm sorry if I've misunderstood anything about those events and if this has already been asked. I couldn't find an answer to my problem searching the web.
//The IBAction is set to trigger on TouchUpInside
#IBAction func action11(sender: UIButton) {
setTile(sender)
}
func setTile(sender: UIButton) {
if turn {
print("O's turn")
sender.setImage(xTile, forState: .Normal)
turn = false
}
}
EDIT: Added the necessary code
There are some properties of UIButtons which you can use to achieve what you want.
You can use Default and selected state of uibutton to set two different images.
In XIB select state "Default" and assign default image to that state again select state to "Selected" and assign image which you want after button section.
and add following line in button selection method.
-(IBAction)buttonTapped:(UIButton *)sender{
sender.selected = !sender.selected;
}
Your understanding is correct, you need to use touchUpInside.
I assume you are trying to create a button that has a toggle function. On one touch you want the button to have the value Say "X" and when touched again the button has a value "O".
Take a look at this code below, this should do the job.
class ViewController: UIViewController {
var isButtonPressed = false{
// Adding a Property Observer, that reacts to changes in button state
didSet{
if isButtonPressed{
// Set the Value to X.
}else{
// Set the Value to O.
}
}
}
#IBAction func changeButtonValue(sender: UIButton) {
// Toggle the button value.
isButtonPressed = !isButtonPressed
}
}
If you don't set turn=true after the first time, this code is executed it will be executed only one.
if turn {
print("O's turn")
sender.setImage(xTile, forState: .Normal)
turn = false
}
Check if the button frame is large enough to get finger touch.
Apple says at least 35x35 pixel.

How do I uninitialize a variable reference in Swift

I have a #IBAction function that accepts a sender: UIButton! as a parameter.
#IBAction func buttonPress(sender: UIButton!)
At some point in the function, I am copying the sender to another variable which was previously declared as a UIButton()
anotherVar = sender
I understand this is a reference to the original sender given UIButton is a class
However at some point of the code, I want to break the reference to sender and "reset" anotherVar to a plain vanilla UIButton(). How would I do this?
EDIT:
I feel I should expand on what I'm doing, perhaps I'm going about this the wrong way...
I have eight buttons all calling the same #IBAction function called buttonPress(). The idea is for the user to tap a button, see an image and then tap another button (out of the remaining seven) to find the matching image. When buttonPress() gets called, the code:
1. Checks to see if this is a first button being tapped
- if it is, it shows the button image and then assigns sender to anotherVar;
- if it is the second button being pressed (i.e. another button was previously clicked), the code runs a match to compare the sender's image to anotherVar's image which was set above
2. If there is a match, I "lock" the buttons so the matching logic doesn't get executed if the user taps the buttons again
3. If there is no match, I want to "clear out" anotherVar ready for another matching task. I don't want to "lock" the buttons as the same button may still need to be clicked.
Here's the full code:
#IBAction func buttonPress(sender: UIButton!) {
var buttonImage = UIImage()
buttonImage = UIImage(named: listOfImages[sender.tag])!
if (!imageIsDone[sender.tag] && (sender.tag != buttonToCompare.tag)) {
// Only execute button logic if match for image not already found and the user isn't tapping the same image
if (imageAwaitingCheck) {
// User has made their first image selection, do matching logic on image clicked
sender.setImage(buttonImage, forState: .Normal)
if (sender.currentImage == buttonToCompare.currentImage) {
// Tapped image macthes previously clicked image
println("Match")
// "Lock" the buttons as they've been matched
imageIsDone[sender.tag] = true
imageIsDone[buttonToCompare.tag] = true
imageAwaitingCheck = false
}
else {
// Tapped image does not match previously clicked image
println("No match")
imageAwaitingCheck = false
buttonToCompare.tag = 100
// ********ERROR IS HERE*********I forced this so that
// (sender.tag != buttonToCompare.tag) is true above when
// the user taps on the first button again after no match is found.
// However, this is a REFERENCE to the original sender and sets the
// button tag to 100 which causes the condition to fail and hence
// tapping button 1, then button 2, no match, then clicking button 1
// again doesn't execute any of this logic
}
}
else {
// User has selected this as the first image, simply show it
sender.setImage(buttonImage, forState: .Normal)
imageAwaitingCheck = true
buttonToCompare = sender // I am copying sender to buttonToCompare. Ideally this would create a copy but because UIButton is a class, this is creating a buttonToCompare as a reference
}
}
}
As long as anotherVar is assigned as var, not let, you can simply do the following when you're done with the sender:
anotherVar = UIButton()
This will "overwrite" the previous value of anotherVar and "reset" it to a new instance of UIButton.
However, you probably don't need to do this at all if the only place you're accessing anotherVar is this function - as long as you call anotherVar = sender, that will also replace the reference to the previous button with the new sender button.
To achieve what you describe after your edit, you don't really need to make a lot of changes. In this if-statement: if (sender.currentImage == buttonToCompare.currentImage) add buttonToCompare = UIButton() at the very end. In the else-statement, do the same instead of changing it's tag.
Alternatively, if you want the sender to be copied, you can do that like this:
let archivedData = NSKeyedArchiver.archivedDataWithRootObject(button)
let buttonCopy = NSKeyedUnarchiver.unarchiveObjectWithData(archivedData)

Resources