Create UIButtons dynamically with for loop in Swift - ios

I am trying to add buttons dynamically to a scroll view after pressing another button.
I created a custom UIView which I want to add to the scroll view.
Below you can find the code how I am trying to do it:
var buttonY: CGFloat = 0 // our Starting Offset, could be 0
for _ in audioCommentsArray {
UIView.animateWithDuration(0.15, animations: {
let commentView = AudioCommentView(frame: CGRectZero)
commentView.frame = CGRect(x: 0, y: buttonY, width: 75, height: 75)
buttonY = buttonY + 75 // we are going to space these UIButtons 75px apart
commentView.alpha = 1.0
audioCommentsListScrollView.addSubview(commentView)
})
}
I want to add these commentView's using a simple animation. However only the last commentView is added correctly to the scrollView whereas the above views are added like this:
Only the background of the commentView is shown whereas the other elements are not visible.
Does anyone have an idea what I might be missing? Adding views using a for loop shouldn't be complicated as I have done this many times before but this time I seem to miss something?
Thank you in advance!

The UIView animations are overlapping and interfering with each other as you process them through the loop. Instead, you should chain them so that the next animation does not interfere with the other. Delete the loop. Then call the animations one after the other in the completion handler. You can call it recursively to ensure each button is animated.
var count = 0
func startButtonsAnimation() {
UIView.animateWithDuration(0.15, animations: {
let commentView = AudioCommentView(frame: CGRectZero)
commentView.frame = CGRect(x: 0, y: buttonY, width: 75, height: 75)
buttonY = buttonY + 75 // we are going to space these UIButtons 75px apart
commentView.alpha = 1.0
audioCommentsListScrollView.addSubview(commentView)
}, completion: { (value: Bool) in
if self.count < self.audioCommentsArray.count {
self.count += 1
self.startButtonsAnimation()
}
})

Related

Stretching UIView in Swift

So I have two view and a button one of the view is hidden "View1" and the other is not "View2" , and once I click on the button "View1" will appear , but I want "View2" to go down , I will add image show what I want to do
Brute way, if your views has fix sizes:
1, create 3 variables for the position of the views
var view1Center: CGPoint = CGPoint(x: 100, y: 100)
var view2CenterBefore: CGPoint = CGPoint(x: 100, y: 100)
var view2CenterAfter: CGPoint = CGPoint(x: 100, y: 400)
2, In ViewDidLoad or ViewDidAppear set view's alpha to 0 and the positions
view1.alpha = 0
view2.alpha = 0
view1.center = view1Center
view2.center = view2CenterBefore
3, When you press the button, you should animate the moves and show view1.
#IBAction func showView1(_ sender: Any) {
UIView.animate(withDuration: 0.5, animations: {
view2.center = view2CenterAfter
})
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
UIView.animate(withDuration: 0.5, animatios: {
view1.alpha = 1
}
}
}
We can use UIStackView class for this with ease. We can add both the views in a UIStackView instance with vertical axis. view1 height should have Highest constraint priority (999) instead of required (1000). This way when view1 is hidden, it's height constraint won't have conflict.
You can take reference of following code snippet:
let stack = UIStackView()
// aligns subviews in top to bottom manner
stack.axis = .vertical
stack.addArrangedSubview(view1)
stack.addArrangedSubview(view2)
When view1 is kept hidden, view2 will automatically move up. StackView should not be given height constraint so that it will have inferred height of summation of its subviews.

Swift imageView animation doesn't work

I have code to create animation:
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView = UIImageView(frame:CGRect(x: 10, y: 10, width: 10, height: 10))
imageView.image = UIImage(named:"2.png")
UIView.animate(withDuration: 5) {
self.imageView = UIImageView(frame:CGRect(x: 10, y: 10, width: 500, height: 500))
self.view.addSubview(self.imageView)
self.view.layoutIfNeeded()
}
}
But animation doesn't work. And imageView not showing.
You should set the constraint's constant before the animation block and inside just call layoutIfNeeded
self.imageViewWidth.constant = 500
self.imageViewHeight.constant = 500
UIView.animate(withDuration: 10) {
self.layoutIfNeeded()
}
Try this..
UIView.animate(withDuration: 10) {
self.imageViewWidth.constant = 500
self.imageViewHeight.constant = 500
self.view.updateConstraintsIfNeeded()
self.view.layoutIfNeeded()
}
To decrease and increase size you have to use the on completion handler
UIView.animate(withDuration: 2, animations: {
self.imageViewWidth.constant = 10
self.imageViewHeight.constant = 10
self.view.layoutIfNeeded()
}) { (true) in
UIView.animate(withDuration: 10, animations: {
self.imageViewWidth.constant = 500
self.imageViewHeight.constant = 500
self.view.layoutIfNeeded()
})
}
if you want the decrease to start immediately change the first withDuration to 0
There are things that makes sense within an animation block (here: UIView.animate). Creating a newer image view is not one of them. Neither is addSubView.
The problem is that you are completely reassigning to your imageview variable - creating a new one within animation block.
You should create your image view and add it to your main view completely before you execute UIView.animate.
Animation is changing the visible properties of your objects, not recreating them. If you want frame change, simply do the following within animation block:
self.imageView.frame = CGRect(x: 10, y: 10, width: 500, height: 500)
You can also call layoutSubviews() or layoutIfNeeded() depending upon your constraint requirements, within UIView.animate.
Where is your problem
Problem 1
imageView = UIImageView(frame:CGRect(x: 10, y: 10, width: 10, height: 10))
imageView.image = UIImage(named:"2.png")
Here, you created new instance of an image view which never has been added into viewcontroller's view stack. You should add this view into stack. These line of code doesn't have any effect on animation performance.
Problem 2
UIView.animate(withDuration: 5) {
self.imageView = UIImageView(frame:CGRect(x: 10, y: 10, width: 500, height: 500))
self.view.addSubview(self.imageView)
self.view.layoutIfNeeded()
}
}
You initialized another imageview and added as a subview. This image view doesn't have any image or background color and as a result you do not see anything happending in the device screen. You should not do these stuff here. View.animate block does not need these stuff. Few things you should learn about animation. How animation works
Solution:
Try these lines of code instead.
// initial frame
var frame:CGRect = CGRect(x: 10, y: 10, width: 10, height: 10)
// create imageview and add this into view stack
self.imageView = UIImageView(frame:frame)
self.imageView.image = UIImage(named:"2.png")
self.view.addSubview(self.imageView)
// set new height & width
frame.size.width = 500;
frame.size.height = 500;
// animate view
UIView.animate(withDuration: 5) {
self.imageView.frame = frame;
self.view.layoutIfNeeded()
}
Hope, it will work!
if you want to animate constraints than you can use this
self.imageViewWidth.constant = 500
self.imageViewHeight.constant = 500;
UIView.animate(withDuration: 10) {
// if you are doing this in View controller then
self.view.layoutSubviews()
// if you are doing this in View then
self.layoutSubviews()
}

How to create multiple labels without them all disappearing at once?

I used the code below to create a label every time a button is pressed, then have the label move to a specific location, and then have it delete.
My problem is it can't create multiple labels because it's deleting the label way too soon, because it removes everything named label.
How can I fix this so it creates multiple labels that only get deleted when a label individually completes its animation?
A solution I've thought of, but can't figure out is where you have it create a label with a different name such as label1, label2, and so on, so that way it can delete a specific label when it completes it's animation, instead of deleting all of them.
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
func createLabel() {
// Find the button's width and height
let labelWidth = label.frame.width
// Find the width and height of the enclosing view
let viewWidth = self.view.frame.width
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - labelWidth
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
// Offset the button's center by the random offsets.
label.center.x = xoffset + labelWidth / 2
label.center.y = 300
label.font = UIFont(name:"Riffic Free", size: 18.0)
label.textColor = UIColor.white
label.textAlignment = .center
label.text = "+1"
self.view.addSubview(label)
}
func clearLabel() {
UIView.animate(withDuration: 0.9, delay: 0.4, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .curveLinear, animations: {
self.label.center = CGPoint(x: 265, y: 75 )
}, completion: { (finished: Bool) in
self.label.removeFromSuperview()
})
}
#IBAction func clicked(_ sender: Any) {
createLabel()
clearLabel()
}
The problem is with this code:
#IBAction func clicked(_ sender: Any) {
createLabel()
clearLabel()
}
There are two issues. The first is that you are doing the animation and removal before you even give the label a chance to become part of the interface. You need to introduce a delay (you can use my delay utility, https://stackoverflow.com/a/24318861/341994):
#IBAction func clicked(_ sender: Any) {
createLabel()
delay(0.1) {
clearLabel()
}
}
The second problem, as you have rightly said, is that you have one label instance variable, which you are using to share the label between createLabel and clearLabel. Thus another label cannot come along during the animation.
But you don't actually need any instance variable. So get rid of your label declaration completely! Instead, modify createLabel so that it actually creates the label (i.e. calls UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))) as a local variable, and then returns a reference to label it creates, like this:
func createLabel() -> UILabel {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
// ...
return label
}
... And then just have clearLabel take that same label as a parameter so that it moves that label and removes it at the end of the animation, like this:
func clearLabel(_ label : UILabel) {
// ...
}
Your clicked implementation will thus look like this, passing the label out of createLabel and into clearLabel:
#IBAction func clicked(_ sender: Any) {
let label = self.createLabel()
delay(0.1) {
self.clearLabel(label)
}
}
(The remaining details of modifying createLabel and clearLabel to make that work are left as an exercise for the reader.)
Now each tap on the button creates and animates and removes a new label, independently of whatever else may have happened before.

Creating & adding buttons to scrollview dynamically

I have the following code that creates buttons based on the fetched data. After buttons are created they get added to a scrollview.
And they should display horizontally.
I can confirm that there is data but it shows weird things in simulator (see attached screenshot). What is wrong in my implementation?
override func viewDidLoad() {
super.viewDidLoad()
fetchRecipeCategory()
for var i = 0; i < self.category.count; i++ {
let frame1 = CGRect(x: 20, y: 20 + (index * 45), width: 45, height: 45 )
let button = UIButton(frame: frame1)
button.setTitle("\(category[i].name)", forState: .Normal)
button.backgroundColor = UIColor.blackColor()
self.categoryScrollView.addSubview(button)
}
print(category[0].name)
}
You need to use different y frame value for each button. Your button creation is using index for its y frame and you did not increment it.
Since your are using for loop, you could use i value for it.
let frame1 = CGRect(x: 20, y: 20 + (i * 45), width: 45, height: 45 )
this code will create vertical list of button. If you want to list your button horizontally, change x frame value for each button
let frame1 = CGRect(x: 20 + (i * 45), y: 20, width: 45, height: 45 )
As for your screenshot, it looks like that the button title is set to Optional("data"). if you really sure that your data source contains data, you could simply unwrap it for your button title. Better if you use optional binding for it.

How do I change UIView Size?

I have trouble with modifying my UIView height at launch.
I have to UIView and I want one to be screen size * 70 and the other to fill the gap.
here is what I have
#IBOutlet weak var questionFrame: UIView!
#IBOutlet weak var answerFrame: UIView!
let screenSize:CGRect = UIScreen.mainScreen().bounds
and
questionFrame.frame.size.height = screenSize.height * 0.70
answerFrame.frame.size.height = screenSize.height * 0.30
It has no effect on the app during run time.
I use autolayout but I only have margins constraints...
Am I doing it all wrong?
This can be achieved in various methods in Swift 3.0 Worked on Latest version MAY- 2019
Directly assign the Height & Width values for a view:
userView.frame.size.height = 0
userView.frame.size.width = 10
Assign the CGRect for the Frame
userView.frame = CGRect(x:0, y: 0, width:0, height:0)
Method Details:
CGRect(x: point of X, y: point of Y, width: Width of View, height:
Height of View)
Using an Extension method for CGRECT
Add following extension code in any swift file,
extension CGRect {
init(_ x:CGFloat, _ y:CGFloat, _ w:CGFloat, _ h:CGFloat) {
self.init(x:x, y:y, width:w, height:h)
}
}
Use the following code anywhere in your application for the view to set the size parameters
userView.frame = CGRect(1, 1, 20, 45)
Here you go. this should work.
questionFrame.frame = CGRectMake(0 , 0, self.view.frame.width, self.view.frame.height * 0.7)
answerFrame.frame = CGRectMake(0 , self.view.frame.height * 0.7, self.view.frame.width, self.view.frame.height * 0.3)
Swift 3 and Swift 4:
myView.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
Hi create this extends if you want. Update 2021 Swift 5
Create File Extends.Swift and add this code (add import foundation where you want change height)
extension UIView {
var x: CGFloat {
get {
self.frame.origin.x
}
set {
self.frame.origin.x = newValue
}
}
var y: CGFloat {
get {
self.frame.origin.y
}
set {
self.frame.origin.y = newValue
}
}
var height: CGFloat {
get {
self.frame.size.height
}
set {
self.frame.size.height = newValue
}
}
var width: CGFloat {
get {
self.frame.size.width
}
set {
self.frame.size.width = newValue
}
}
}
For Use (inherits Of UIView)
inheritsOfUIView.height = 100
button.height = 100
print(view.height)
For a progress bar kind of thing, in Swift 4
I follow these steps:
I create a UIView outlet : #IBOutlet var progressBar: UIView!
Then a property to increase its width value after a user action var progressBarWidth: Int = your value
Then for the increase/decrease of the progress progressBar.frame.size.width = CGFloat(progressBarWidth)
And finally in the IBACtion method I add progressBarWidth += your value for auto increase the width every time user touches a button.
You can do this in Interface Builder:
1) Control-drag from a frame view (e.g. questionFrame) to main View, in the pop-up select "Equal heights".
2)Then go to size inspector of the frame, click edit "Equal height to Superview" constraint, set the multiplier to 0.7 and hit return.
You'll see that constraint has changed from "Equal height to..." to "Proportional height to...".
Assigning questionFrame.frame.size.height= screenSize.height * 0.30 will not reflect anything in the view. because it is a get-only property. If you want to change the frame of questionFrame you can use the below code.
questionFrame.frame = CGRect(x: 0, y: 0, width: 0, height: screenSize.height * 0.70)
I know that there is already a solution to this question, but I found an alternative to this issue and maybe it could help someone.
I was having trouble with setting the frame of my sub view because certain values were referring to its position within the main view. So if you don't want to update your frame by changing the whole frame via CGRect, you can simply change a value of the frame and then update it.
// keep reference to you frames
var questionView = questionFrame.frame
var answerView = answerFrame.frame
// update the values of the copy
questionView.size.height = CGFloat(screenSize.height * 0.70)
answerView.size.height = CGFloat(screenSize.height * 0.30)
// set the frames to the new frames
questionFrame.frame = questionView
answerFrame.frame = answerView

Resources