This is a very useful technique and it’s pretty straight forward.
Let’s say we have an array of items or heroes and we want to dynamically create buttons that
will do something linked to the hero/item selected.
One example is if you have an array of saved games and you want to generate buttons to load each saved game.
In reality you’ll most likely be generating your array from dynamic content – plist or CoreData.
In our case, just for the sake of this example we’ll start off by creating a static array with some Strings:
1 |
var arrayOfVillains = ["santa", "bugs", "superman", "batman"] |
Now we have our array to work with. Let’s generate some UIButtons:
1 2 3 4 5 6 7 8 9 10 11 12 |
for villain in arrayOfVillains { let villainButton = UIButton(frame: CGRect(x: 50, y: 50, width: 450, height: 30)) villainButton.layer.cornerRadius = 10 villainButton.backgroundColor = UIColor.darkGrayColor() villainButton.setTitle("Button for Villain: \(villain)", forState: UIControlState.Normal) villainButton.addTarget(self, action: "villainButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside) villainButton.tag = Int(element.id) self.myView.addSubview(loadHeroButton) // myView in this case is the view you want these buttons added } |
First problem you are going to encounter here is that your buttons will overlap over each other, so you can only see one button on the screen ( the last one to add ):
so we need to add an offset. So we’ll end up with this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var arrayOfVillains = ["santa", "bugs", "superman", "batman"] var buttonY: CGFloat = 20 // our Starting Offset, could be 0 for villain in arrayOfVillains { let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30)) buttonY = buttonY + 50 // we are going to space these UIButtons 50px apart villainButton.layer.cornerRadius = 10 // get some fancy pantsy rounding villainButton.backgroundColor = UIColor.darkGrayColor() villainButton.setTitle("Button for Villain: \(villain)", forState: UIControlState.Normal) // We are going to use the item name as the Button Title here. villainButton.titleLabel?.text = "\(villain)" villainButton.addTarget(self, action: "villainButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside) self.view.addSubview(villainButton) // myView in this case is the view you want these buttons added } |
So at this point we should have all of our buttons generated.
You should see something like this:
All good except nothing happens when you press them..wait !
Did I say nothing ? I meant this:
Here is the fix for that..we are going to create a function for the button taps:
1 2 3 4 5 6 7 8 9 10 11 |
func villainButtonPressed(sender:UIButton!) { if sender.titleLabel?.text != nil { println("You have chosen Villain: \(sender.titleLabel?.text)") } else { println("Nowhere to go :/") } } |
You can also pass let’s say the UIButton’s tag instead.
If you only need Int’s you can use sender.tag, instead of the titleLabel.text .
Keep in mind that for this example you would put the generator for the Buttons in the viewDidLoad()
method ( or enclose it in a function and then call that function in viewDidLoad() ), but the “villainButtonPressed function you will place outside of viewDidLoad.
And Voila, you should now have your dynamically generated UIButtons!
I’ve added a few more things, just so we can get some system messages, to make sure we have not gone completely crazy:
Here is what your full code should look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() var arrayOfVillains = ["santa", "bugs", "superman", "batman"] var buttonY: CGFloat = 20 // our Starting Offset, could be 0 for villain in arrayOfVillains { let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30)) buttonY = buttonY + 50 // we are going to space these UIButtons 50px apart villainButton.layer.cornerRadius = 10 // get some fancy pantsy rounding villainButton.backgroundColor = UIColor.darkGrayColor() villainButton.setTitle("Button for Villain: \(villain)", forState: UIControlState.Normal) // We are going to use the item name as the Button Title here. villainButton.titleLabel?.text = "\(villain)" villainButton.addTarget(self, action: "villainButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside) self.view.addSubview(villainButton) // myView in this case is the view you want these buttons added } } func villainButtonPressed(sender:UIButton!) { if sender.titleLabel?.text != nil { println("You have chosen Villain: \(sender.titleLabel?.text)") } else { println("Nowhere to go :/") } } } |
Here is an addition on request. This will generate one button at the start and a new button on button click:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
import UIKit class ViewController: UIViewController { var number: Int = 1 var arrayOfVillains = ["Main Button"] var buttonY: CGFloat = 20 // our Starting Offset, could be 0 override func viewDidLoad() { super.viewDidLoad() addStartingButtons() } func addStartingButtons() { for villain in arrayOfVillains { let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30)) buttonY = buttonY + 50 // we are going to space these UIButtons 50px apart villainButton.layer.cornerRadius = 10 // get some fancy pantsy rounding villainButton.backgroundColor = UIColor.darkGrayColor() villainButton.setTitle("Button for Villain: \(villain)", forState: UIControlState.Normal) // We are going to use the item name as the Button Title here. villainButton.titleLabel?.text = "\(villain)" villainButton.addTarget(self, action: "villainButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside) self.view.addSubview(villainButton) // myView in this case is the view you want these buttons added } } func addNewButton() { let villainButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30)) buttonY = buttonY + 50 // we are going to space these UIButtons 50px apart villainButton.layer.cornerRadius = 10 // get some fancy pantsy rounding villainButton.backgroundColor = UIColor.darkGrayColor() villainButton.setTitle("New Button : \(number)", forState: UIControlState.Normal) // We are going to use the item name as the Button Title here. villainButton.titleLabel?.text = "\(number)" villainButton.addTarget(self, action: "villainButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside) self.view.addSubview(villainButton) // myView in this case is the view you want these buttons added } func villainButtonPressed(sender:UIButton!) { if sender.titleLabel?.text != nil { println("You have chosen Villain: \(sender.titleLabel?.text)") addNewButton() number++ } else { println("Nowhere to go :/") } } } |
Here is a link to the second part: UIButtons From A loop – part 2
Any questions or comments – let me know. Thanks!
Hey, awesome help there! Would just like to know on how do we generate the next button in the array every time the buttonPressed func is clicked, instead of generating all four buttons in the array at one go? Thanks!!!
Hey, @bleeary_d:disqus, thanks! I made some changes and posted it at the bottom of the article. Let me know if that works. Cheers!
Heyy, wow that was amazing hahah that was what i wanted! Thanks so much for your help and cheers mate! (Y)
Btw, if you don’t want the pressing of new buttons to generate more buttons too, just take the ‘.addTarget’ line out of the addNewFunction.
Hi! I have noticed that you use subview to add the buttons to the view controller. I have tried the above codes for my project and it seems that every time i go into another view , the buttons i have created disappears. Is there any way to keep the buttons in the subview alive in that view even when i go into another view? Thanks!
What do you mean go into another view ? Can you post your code somewhere ?
Hi thanks for replying! I am sorry that currently my project is under NDA. So apparently the view controller which i had use to generate buttons programatically (2nd VC) , was link to a navigation controller to another view controller(1st VC). After adding a plenty amount of buttons in the 2nd view controller, if i were to pressed press the back button (bar button item) to transit back to the 1st VC and then by clicking a button to show the 2nd VC, all the button previously created in the 2nd VC was gone. Cheers!
That’s because you are adding the buttons to a UIView that is getting recreated every time you load the VC. You need to keep a reference of it and only create it once, not every time you load the VC.
Thanks! I know this is a silly question but how do you keep a reference of it so that it only create once? Sorry i am really new to swift and programming.
Did you figure it out? I see your question is gone 🙂
If you still need this I think the easiest solution for you, since you are using the nav. controller is this:
add this line at the bottom of the addNewButton() func:
arrayOfVillains.append(“New Button : (number)”)
And then move the declaration of the array before the class, so it’s global, like this:
import UIKit
var arrayOfVillains = [“Main Button”]
class ViewController: UIViewController {
var number: Int = 1
var buttonY: CGFloat = 80 // our Starting Offset, could be 0
override func viewDidLoad() {
super.viewDidLoad()
Hey mate, i am a currently facing a small problem which is frustrating. What i did was i preset 6 buttons in an array and have an add button where every time the add button is pressed, the first button in the array will be set with an image and label and if i click add again, the second button’s image and label will appear and so on.. And so, i have a navigation controller and when i clicked back on the nav controller which leads me to another VC and when i come back to the initial VC, the buttons are gone! It will be great if you could help me and thanks and sorry for the trouble 🙂
Hi, look at my answer below. SwiftNoob had a similar problem. I think the easiest solution would be to append them to the array, whenever you add a new button. So when you add 2 more , your array will then contain 8 buttons.
Hey, thanks for replying! I have tried the solution you gave SwiftNoob below but instead of getting the image button set, i got the addition of starting button instead (After going back to the previous VC and to this VC again)
Here are the codes for my view controller
func addStartingButtons()
{
for lockers in arrayOfLockers
{
let addLockerbtn = UIButton(frame: CGRect(x: 250, y: buttonY + 70, width: 120, height: 30))
buttonY = buttonY + 80 // we are going to space these UIButtons 50px apart
addLockerbtn.layer.cornerRadius = 10 // get some fancy pantsy rounding
addLockerbtn.backgroundColor = UIColor.grayColor()
addLockerbtn.setTitle(“Add New+”, forState: UIControlState.Normal) // We are going to use the item name as the Button Title here.
addLockerbtn.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
addLockerbtn.addTarget(self, action: “ButtonPressed:”, forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(addLockerbtn) // myView in this case is the view you want these buttons added
}//end of startingButton
}//end of addStartingButtons Func
func addNewButton(sender: UIButton)
{
let image = UIImage(named: “lock.png”) as UIImage!
sender.backgroundColor = UIColor.grayColor()
sender.setImage(image, forState: UIControlState.Normal)
sender.layer.cornerRadius = 10
arrayOfLockers.append(“Add New+”)
}
func ButtonPressed(sender:UIButton!)
{
if number<7
{
if number == 1
{
addNewButton(btn1)
}
else if number == 2
{
addNewButton(btn2)
}
else if number == 3
{
addNewButton(btn3)
}
else if number == 4
{
addNewButton(btn4)
}
else if number == 5
{
addNewButton(btn5)
}
else if number == 6
{
addNewButton(btn6)
}
number++
} //end of number less than 6
else
{
let maxbtnAlert = UIAlertView()
maxbtnAlert.title = "Exceed max buttons added"
maxbtnAlert.message = "ERROR! Cannot add anymore buttons!"
maxbtnAlert.addButtonWithTitle("Ok")
maxbtnAlert.show()
} //end of else
}//end of buttonPressed func
I see, so it really depends what exactly you are aiming at. I see a quick fix for your case.
Just create a second array for the newly created buttons. Then append to it every time you create one.
In the ViewDidLoad method add the Start buttons and then add the Newbuttons ( create a second func for the addNewButtons )
Hey, i have tried your method but it still didn’t really work out… It would be great if u could provide me a example perhaps? I am pretty sure there are errors in the way I implemented 😀 thanks in advance!
I’ll probably just put it into a second lesson, give me a day or two. There are 2 ways of doing it, so I’ll try and show you both.
That will be a great help! Thanks a lot 🙂
http://www.helpmecodeswift.com/advanced-functions/generating-uibuttons-from-a-loop-part-2
Here it is. Btw, I tried doing a second method, but it turned out it was not a good solutions, so just stick with this, it should do everything you need. Let me know 🙂