Swift Accessing UIButton Tags - ios

I created multiple buttons with unique tags and added the buttons to the UIView of my project. I want to re-access them so I can add the buttons into a views dictionary for autolayout.
func createButtons() {
for index in 0...10 {
var button = UIButton.buttonWithType(UIButtonType.System) as UIButton
button.tag = index
self.view.addSubview(button)
}
Code to create a views dictionary for autolayout purposes.
func createDictionary() {
var mbd : [String:UIButton]! = [:]
for index in 0...10 {
let tmpButton = self.view.viewWithTag(index) as? UIButton
mbd["button" + String(index)] = tmpButton
}
return mbd
}
However, I don't think the above code is creating any dictionary and I am not sure why. If I shift the code which I append to the dictionary in the first for loop then everything works. But I am trying to learn to use the viewWithTag method as I will need it frequently.
Any guidance is appreciated.

If you dont need the tags for anything else, except for creating the dictionary, you could create the dictionary while you create the buttons.
I would suggest something like this:
func createButtons (){
var mbd : [String:UIButton]! = [:]
for index in 0...10 {
var button = UIButton.buttonWithType(UIButtonType.System) as UIButton
self.view.addSubview(button)
mbd["button" + String(index)] = button
}
}
This solution is also more performant, in the sense that you don't call the loop twice and, since you already have the buttons, you don't need to call viewWithTag anymore.
Hope this works for you. Let me know how it goes!

Related

UIelement programmatically created. Can I take the name of new created UIelement from variable?

I edited exactly what I want to achieve, this time different example without taken parameter in function.
What I want to achieve is:
var array = [String]()
func create() {
var arrayCount = array.count // will be 0
var nameForUI = "view/(arrayCount)" // will be view0
let nameForUI: UIVIew = {
let view = UIVIew()
return view
}()
array.append(nameForUI)
view.addSubview(nameForUI)
//
}
next time if I call create() func , the next view will be called "view1" So my question is, how to achieve this result? every time function will called it will create new element with new name.
Edit
To directly answer your question: No, you cannot do that.
Code written in Swift is compiled -- it is not an interpreted / scripted language.
So you cannot write code that creates a button named "littleButton" and then have a line of code littleButton.backgroundColor = .red
You can sort of do this by creating a Dictionary that maintains the "name" of the element as the key, and a reference to the element as the value.
Start with initializing an empty dictionary:
var buttonsDict: [String : UIButton] = [String : UIButton]()
Your "create" func can start like this:
func createButton(named str: String) -> Void {
// create a button
let b = UIButton()
// give it a default title
b.setTitle("Button", for: .normal)
// add it to our Dictionary
buttonsDict.updateValue(b, forKey: str)
}
When you want to create a button:
createButton(named: "littleButton")
When you want to access that button by name:
// use guard to make sure you get a valid button reference
guard let btn = buttonsDict["littleButton"] else { return }
view.addSubview(btn)
Edit 2
Another option, which is perhaps more similar to your edited question:
// initialize empty array of views
var viewsArray: [UIView] = [UIView]()
override func viewDidLoad() {
super.viewDidLoad()
// create 5 views
for _ in 0..<5 {
create()
}
// ...
// then, somewhere else in your code
viewsArray[0].backgroundColor = .red
viewsArray[1].backgroundColor = .green
viewsArray[2].backgroundColor = .blue
viewsArray[3].backgroundColor = .yellow
viewsArray[4].backgroundColor = .orange
}
func create() -> Void {
// create a view
let v = UIView()
// add it to our array
viewsArray.append(v)
// add it as a subview
view.addSubview(v)
}
As you see, instead of trying to reference the created views by name (which you cannot do), you can reference them by array index.
Just remember that arrays are zero-based... so the first element added to the array will be at [0] not [1].

How to find index of selected item within a UIStackView Swift 4

I have 5 buttons within a UIStackView, and I want to find out which index is being selected, and later compare those indexes. My code right now gives me an Array.Index. I've tried both subviews and arrangedSubviews. Is there anyway I can turn this into an Integer? I can't figure it out. Thanks!!
if let selectedIndex = stackview.subviews.index(of: sender) {
}
// UPDATE
I kinda got what I wanted with:
let int = stackview.subviews.distance(from: stackview.subviews.startIndex, to: selectedIndex)
I'm still not sure if this is the most efficient way, but it does the job for now.
index(of:) return Int.
Also you should find your button in the arrangedSubviews, not in the subviews
Assuming your stack view contains only buttons, and each button is connected to this #IBAction, this should work:
#IBAction func didTap(_ sender: Any) {
// make sure the sender is a button
guard let btn = sender as? UIButton else { return }
// make sure the button's superview is a stack view
guard let stack = btn.superview as? UIStackView else { return }
// get the array of arranged subviews
let theArray = stack.arrangedSubviews
get the "index" of the tapped button
if let idx = theArray.index(of: btn) {
print(idx)
} else {
print("Should never fail...")
}
}
I would add a tag to each of your buttons (button.tag = index) then check the tag of your sender.
So then you can wire up each of your buttons to the same function with a sender parameter, then check if sender.tag == index.

Send multiple tags with UIButton in UITableView

Im suffering from sending two tags to a function to show UIActivityViewController and share the cell data, well before i used to send one value at a time and within a single UIButton:
cell.sharefb.tag = indexPath.row
cell.sharefb.addTarget(self, action: "showAlert:", forControlEvents:UIControlEvents.TouchUpInside)
But now I've implemented sections in my UITableView so my array is like :
Array[indexPath.section][indexPath.row]
One of them (section or row) is not enough at a time, i need to send both to my function ? how can i do that ?
func showAlert(sender:AnyObject){
// i want to use it like :
Array[sender.sectionvalue][sender.rowvalue]
}
You could subClass UIButton and add properties for the data you need eg
class MyButton : UIButton {
var row : Int?
var section : Int?
}
you can then set those properties, and in your showAlert function you can get them back and use:
func showAlert(sender:AnyObject){
let theButton = sender as! MyButton
let section = theButton.section
let row = theButton.row
}
Edit: Added where to set the button as per comment requested:
In your StoryBoard, make sure that your button is of type MyButton (and not UIButton anymore).
and then where you used to set the tag, don't use the tag but set the properties. So replace this code :
cell.sharefb.tag = indexPath.row
cell.sharefb.addTarget(self, action: "showAlert:", forControlEvents:UIControlEvents.TouchUpInside)
with:
cell.sharefb.row = indexPath.row
cell.sharefb.section = indexPath.section
cell.sharefb.addTarget(self, action: "showAlert:", forControlEvents:UIControlEvents.TouchUpInside)

Add a string property to a UIButton in Swift

How can I associate a string property with a UIButton in Swift? I don't want the string to appear as the button text, simply to be assigned to the button as an identifier or a key. Here is what I have so far:
func createAnswerButtons() {
var index:Int
for index = 0; index < self.currentQuestion?.answers.count; index++ {
// Create an answer button view
var answer:AnswerButtonView = AnswerButtonView()
selection.setTranslatesAutoresizingMaskIntoConstraints(false)
// Place into content view
self.scrollViewContentView.addSubview(answer)
// Add a tapped gesture recognizer to the button
let tapGesture:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("answerTapped:"))
answer.addGestureRecognizer(tapGesture)
// Add constraints etc
// Set the answer button text
let answerText = self.currentQuestion!.answers[index]
answer.setAnswerText(answerText)
// Set the identifier for each answer button
self.identifier = self.currentQuestion!.answerIdentifier[index]
// Add to the selection button array
self.answerButtonArray.append(answer)
}
So I think I need something after
// Set the identifier for each answer
self.identifier = self.currentQuestion!.answerIdentifier[index]
To assign the identifier to the button.
The reason for this is I'm trying to implement a decision tree logic so that I can keep track of each answer button that is tapped to generate a code string that will correspond to a final result.
Using the Objective-C runtime, we can add properties to classes at runtime:
extension UIButton {
private struct AssociatedKeys {
static var DescriptiveName = "nsh_DescriptiveName"
}
#IBInspectable var descriptiveName: String? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.DescriptiveName,
newValue as NSString?,
UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)
)
}
}
}
}
Adding #IBInspectable also lets us set the descriptiveName property through Interface Builder.
For more about the Objective-C runtime, I recommend you check out this NSHipster article.
You can use the accessibility identifier (button.accessibilityIdentifier), if the button you want to identify should have a unique identifier (this matters if you're ever writing UI tests).
You can also subclass UIButton and add a variable buttonIdentifier.
class IdentifiedButton: UIButton {
var buttonIdentifier: String?
}
You can use accessibilityIdentifier property of UIButton.
#IBOutlet weak var button: UIButton!
button.accessibilityIdentifier = "Some useful text"
Use
button.accessibilityIdentifier = "some text"
istead of tag.
You can create an array with the strings you want associated with the button. Then set the buttons tag to the index of the string you want associated with the button. Hence:
var myStrings = ["First","Second","Third"]
button.tag = //insert a number corresponding to the string index in myStrings that you want for the button
func buttonPressed(sender: UIButton){
var selectedString = myString[sender.tag]
}

How to loop through all UIButtons in my Swift view?

How would I loop through all UIButtons in my view in Swift? I would want to set all the titles to "", but my for-loop in Swift is giving an error.
for btns in self.view as [UIButton] {
// set the title to ""
}
This code should work:
for view in self.view.subviews as [UIView] {
if let btn = view as? UIButton {
btn.setTitleForAllStates("")
}
}
You need to iterate through the subViews array.
Shortened and updated for Swift 3 & 4
for case let button as UIButton in self.view.subviews {
button.setTitleForAllStates("")
}
Looping over subview works, but it's sometimes a little ugly, and has other issues.
If you need to loop over some specific buttons, for example to add corner radius or change tint or background color, you can use an array of IBOutlets and then loop over that.
var buttons = [SkipBtn, AllowBtn]
for button in buttons as! [UIButton] {
button.layer.cornerRadius = 5
}
Swift 4:
let subviewButtons = self.view.subviews.filter({$0.isKind(of: UIButton.self)})
for button in subviewButtons {
//do something
}
To add some context for a common use case, suppose the buttons were in a scroll view and you wanted to highlight the tapped button and de-highlight the other buttons. In this situation, you would direct all buttons to one action method:
#objc private func buttonAction(_ button: UIButton) {
for case let b as UIButton in view.scrollView.subviews {
if b == button {
b.setTitleColor(UIColor.green, for: []) // highlight
} else {
b.setTitleColor(UIColor.black, for: []) // de-highlight
}
}
}
This code seems to be quite useful for iterating over any object within a view, just change UIButton for any other subview type such as UIView or UIImageView, etc.
let filteredSubviews = self.view.subviews.filter({
$0.isKindOfClass(UIButton)})
for view in filteredSubviews {
//Do something
}
Used some of the offered questions out there and created my own. I believe is the most efficient when you want to programmatically set up the title of various UIButtons(in my case I am building a quiz)
By randomising my array list and with just a for loop I printing the item at index to the button title
for view in self.viewForButtons.subviews{
if view.isKindOfClass(UIButton)
{
let button : UIButton = view as! UIButton
button.setTitle("item[i]", forState: .Normal)
}
}
If you have UIView's within self.view then you need to loop through the subviews while searching for UIButton. Using the accepted answer, I made this little function to do so:
Swift 4 + :
func findButton(`in` view: UIView){
for view in view.subviews as [UIView] {
if let button = view as? UIButton {
// Do something with 'button'
}else{
// Loop through subview looking for buttons
findButton(in: view)
}
}
}
Usage:
override func viewDidLoad() {
findButton(in: self.view)
}
Hope this helps!
Here's a short way in Swift if you know the subview only has buttons:
myView.subviews.map {
($0 as? UIButton)!.enabled = false
}

Resources