Check UIButton already exists or not in Swift - ios

I found Objective-C coding for this topic. But the problem is that most of the classes and functions in Objective-C is deprecated in swift programming language.
I am using a UIButton inside a UITableView with flagForAction[Boolean Value]. So what i want to achieve is that if the UIButton is created once , there is no need to recreate it. So for that i need to check whether the UIButton already exists or not. Somebody suggested me the concept of tag for this, applying a particular tag to this UIButton and checking that is exist on the view or not. But i don't know how to do that.

Setting a tag:
myButton.tag = 1234 // some unique value, probably better to define as an enum or constant
Retrieving a view by tag (likely in cellForRowAtIndexPath):
if let myButton = tableviewCell.viewWithTag(1234) { // change tableviewCell for whatever your tableview cell variable name is
// myButton already existed
} else {
// TableviewCell doesn't contain a myButton, so create one here and set the tag as above
}

in swift 2.0
for view in myview.subviews {
if let btn : UIButton = view as? UIButton {
// access button here, either tag or title etc..
}
}

I am using view as example. You can also identify a button with accessibilityIdentifier property of the button, if you dont want to use tag proprerty of button.
For example :
var button = UIButton(frame: CGRectMake(0, 0, 100, 44))
button.accessibilityIdentifier = "button 1"
self.view.addSubview(button)
Here, i created a button with accessibilityIdentifier "button 1"
While creating the next button, i will check in the subview if it contains the button with accessibilityIdentifier "button 1", as below
var subviews : NSArray = self.view.subviews
for button : AnyObject in subviews{
if(button .isKindOfClass(UIButton)){
if(button.accessibilityIdentifier == "button 1"){
println("Button Already Exists")
}else{
println("Create new button")
}
}
}

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!

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

What is the convention to handle secondary action of UIButton in 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")
}
}

how to find a string property that corresponds to a gesture.view in Swift?

I have a json file containing an array of dictionaries:
"question":"Question text",
"answers":["Yes", "No"],
"answerTracker":["1a", "1b"]
I have created a Question Class & an Answer Button View Class to display the questions & answers in the view.
In the view controller I have so far displayed the questions, created the answer buttons, and finally, added a tapGestureRecogniser to each button.
func answerTapped(gesture:UITapGestureRecognizer) {
// Get access to the button that was tapped
let selectionButtonThatWasTapped:SelectionButtonView? = gesture.view as? SelectionButtonView
if let actualButton = selectionButtonThatWasTapped {
// Find out the index for the button that has been tapped
let selectionTappedIndex:Int? = find(self.selectionButtonArray, actualButton)
**// Find the answer tracker for the button that has been tapped?**
// Append the tracker for the selected button to the User Generated Code
if let actualAnswerTracker:String = self.answerTracker as String! {
self.userGeneratedCode.append(actualAnswerTracker)
}
In the tapGestureRecognizer method above I now need to find the corresponding answerTracker (String) for the answer button that is tapped (UIView) but can't find a way? The answerTracker string corresponding to the tapped answer is to be appended into a String array property called userGeneratedCode.
Any suggestions?
you do not need to add a tapGestureRecogniser to a button, you can add a target and selector to button anyway. If you want to know which button is pressed, you can use tag property on UIButton to check against.

UIView does not have a member named, "inertia"

I am attempting to create a button programmatically that will fade out when tapped, change the text, then fade back in. However, when i try to write the code for the fade animation, I get the error, "'UIView' does not have a member named 'inertia' Inertia is the name of the button, of course.
Here is my code for creating the button, it is within a function that is called within the viewDidLoad() function:
var inertia = UIButton.buttonWithType(UIButtonType.System) as UIButton
inertia.frame = CGRectMake(firstView.frame.width/2-(inertia.frame.width/2), firstView.frame.height/10, firstView.frame.width, firstView.frame.height/10)
inertia.frame = CGRectMake(firstView.frame.width/2-(inertia.frame.width/2), firstView.frame.height/10, firstView.frame.width, firstView.frame.height/10)
inertia.setTitle("Newton's First Law of Motion", forState: UIControlState.Normal)
inertia.addTarget(self, action: "tapped:", forControlEvents: .TouchUpInside)
self.firstView.addSubview(inertia)
This is my line of code for when the button is tapped so far, where the error occurs:
UIView.animateWithDuration(0.4, animations: {self.firstView.inertia.alpha = 0})
I believe I am missing something in the button creation, because when I create an outlet for a button from the storyboard the fade animation will not produce an error.
Please help me fix this issue as I will be using this often. Also if you could find a way for me to create the button's frame properly without having to declare it twice that would be helpful as well.
What I have tried (to no avail):
I have put : UIButton! after var inertia
I have tried `UIView.animateWithduration(0.4, animations: {self.inertia.alpha = 0})
Just to break down the problem line, specifically self.firstView.inertia.alpha. Self is obviously whatever class instance you are in. I assume from the error you are reporting and your code that firstView is a UIView. Now you have added inertia as a subview of firstView, but that does not create a property named inertia on the view. In other words, there is no firstView.inertia. What firstView has is a subviews property which is an array of anyobject.
In other words the button that you created and called inertia is now somewhere in the array firstView.subviews, though it's hard to say where depending on how many other views it has as subviews.
You could iterate through the subviews array to find the button which you'd previously called inertia, but it might be simpler to just keep a reference to inertia. You could make it a property in your class (if there is only one such button) and the call your code with
UIView.animateWithDuration(0.4, animations: {self.inertia.alpha = 0})
As I understand from your description you code looks something like this:
class ViewController:UIViewController {
var firstView:UIView!
func tapped(button: UIButton) {
UIView.animateWithDuration(0.4) { self.firstView.inertia.alpha = 0 }
}
override func viewDidLoad() {
super.viewDidLoad()
var inertia = UIButton.buttonWithType(UIButtonType.System) as UIButton
inertia.frame = CGRectMake(firstView.frame.width/2-(inertia.frame.width/2), firstView.frame.height/10, firstView.frame.width, firstView.frame.height/10)
inertia.frame = CGRectMake(firstView.frame.width/2-(inertia.frame.width/2), firstView.frame.height/10, firstView.frame.width, firstView.frame.height/10)
inertia.setTitle("Newton's First Law of Motion", forState: UIControlState.Normal)
inertia.addTarget(self, action: "tapped:", forControlEvents: .TouchUpInside)
self.firstView.addSubview(inertia)
}
}
Now, you get that error because inerta is not declared as a property in your class, you only create it in a function. If you want to use that button when pressed in tapped() then simply change:
UIView.animateWithDuration(0.4) { self.firstView.inertia.alpha = 0 }
// To
UIView.animateWithDuration(0.4) { button.alpha = 0 } // When TouchUpInside the button will pass itself
Or you can make it a propery by adding:
var inertia:UIButton! // Under class ...
And remove var in viewDidLoad when setting it.

Resources