UIStackView - hide and collapse subview with animation - ios

I'm trying to hide UIStackView's subview like this:
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 2.0,
delay: 0, options: [.curveEaseOut], animations: {
self.label.isHidden = true
self.label.alpha = 0.0
self.stackView.layoutIfNeeded()
})
However, the label disappears instantly with using this code. I suspect this is because of setting isHidden to true, which is required for collapsing.
Is there a way how to hide and collapse UIStackView's subvew with animation? Or it might be better to not to use UIStackView at all?

According to Apple's documentation:
You can animate both changes to the arranged subview’s isHidden property and changes to the stack view’s properties by placing these changes inside an animation block.
I've tested the below code using iOS 12.1 Simulator and it works as expected.
UIView.animate(
withDuration: 2.0,
delay: 0.0,
options: [.curveEaseOut],
animations: {
self.label.isHidden = true
self.label.alpha = 0.0
})

You can animate view properties like alpha, color, etc. However, some things happen instantly - isHidden in this case.
Here's an example using UIView.animate:
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseOut, animations: {
self.label.alpha = 0 // Changes the label's layer alpha value
}, completion: { finished in
self.label.isHidden = true // Hides the label
self.label.layer.alpha = 1 // Resets the label's alpha without un-hiding it
})
Using UIViewPropertyAnimator:
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 2, delay: 0, options: .curveEaseOut, animations: {
self.label.alpha = 0 // Sets the label's alpha
}) { _ in
self.label.isHidden = true // Hides the label
self.label.alpha = 1 // Resets the label's alpha without un-hiding it
}

I have tried your code. Its animating
if self.stackView.subviews.count > 0 {
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0, delay: 0, options: [.curveEaseOut], animations: {
self.stackView.subviews[0].isHidden = true
self.stackView.subviews[0].alpha = 0.0
self.stackView.layoutIfNeeded()
}) { (position) in
self.stackView.subviews[0].removeFromSuperview()
}
}

Just you can use simple solution with animateKeyframes to fade alpha , then hide , i think this will give you what you need So hide after 1 Sec and 0.8 Sec fading
// showLabel is Bool to handle status declare it at you File
#IBAction func toggleStackLabelTapped(_ sender: UIButton) {
showLabel = !showLabel
UIView.animateKeyframes(withDuration: 1, delay: 0, options: .calculationModeLinear, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.8) {
self.label.alpha = (self.showLabel) ? 1 : 0
}
UIView.addKeyframe(withRelativeStartTime: 0.8, relativeDuration: 1) {
self.label.isHidden = !self.showLabel
}
})
}

make sure you have not given height constraint to the stackview.
and try this.
UIView.animate(withDuration: 0.5) {
self.stackView.subviews[INDEX_OF_LABEL_IN_STACK]?.alpha = 0
self.stackView.subviews[INDEX_OF_LABEL_IN_STACK]?.isHidden = true
self.view.layoutSubviews()
}

Related

Sequence 2 Animations That Loop With Delay In-Between

Goal: Create a looping animation with 2 overlapping uilabels to show one and hide the other by setting alphas respectively to 1 and 0.
Any tips would be appreciated.
What I've Tried:
Autoreverse/repeat animation - While this works, there is no delay in-between when the animation reverses and repeats itself.
DispatchQueue.main.async {
UIView.animate(withDuration: 1.0, delay: 3.0, options: [.autoreverse, .repeat]) {
self.labelOne.alpha = 0
self.labelTwo.alpha = 1
}
}
Recursive animation - This works, but when I re-enter the foreground from the background, the animation spazzes out showing/hiding each uilabel. My issue here is being unable to stop/reset the recursive animation.
DispatchQueue.main.async {
UIView.animate(withDuration: 1.0, delay: 3.0, options: .curveLinear) {
self.labelOne.alpha = 0
self.labelTwo.alpha = 1
} completion: { isComplete in
UIView.animate(withDuration: 1.0, delay: 3.0, options: [.curveLinear]) {
self.labelOne.alpha = 1
self.labelTwo.alpha = 0
} completion: { isComplete in
self.runThisAnimation()
}
}
}

Hiding UIView animation in swift

I am trying to hide/present UITextField with an animation. The animation works when presenting the text field but not when hiding, is there any particular reason for this?
UIView.transition(with: self.confirmPasswordTextField, duration: 0.5, options: [.transitionCrossDissolve], animations: {
self.confirmPasswordTextField.isHidden = self.login
self.layoutIfNeeded()
})
You better use alpha
self.confirmPasswordTextField.alpha = show ? 0.0 : 1.0
UIView.transition(with: self.confirmPasswordTextField, duration: 0.5, options: [.transitionCrossDissolve], animations: {
self.confirmPasswordTextField.alpha = show ? 1.0 : 0.0
})

iOS/Swift: Use UIView.animate to animate text getting added/removed from UITextView

I am adding and removing attributed text from a UITextView. I wish to use UIView.animate to add an animation to when text is appended to the text view and when that appended text is removed from the text view. So far, I have this, but it does not cause any noticeable animation on the text view:
UIView.animate(withDuration: 1, delay: 0, options: .CurveLinear, animations: {
self.view.layoutIfNeeded()
self.textView.attributedText = newAttributedText
}, completion: { finished in
print("Animation completed")
}
// prints "Animation completed", but no animation occurs
You cannot animate changing of text in that manner. There is a list of animatable properties of CALayer class and UIView class.
Set text cannot be animated by UIView.animate. Only changes like transparency, colors, shape, location, etc can be animated by the UIView.animate.
Here is an example of animating origin and transparency change with code looks like
self.textView.text = "test Animation"
self.textView.alpha = 0
UIView.animate(withDuration: 3, delay: 0, options: .curveLinear, animations: {
var frame = self.textView.frame
frame.origin.x = frame.origin.x + 50
frame.origin.y = frame.origin.y + 50
self.textView.frame = frame
self.textView.alpha = 1
}, completion: { finished in
print("Animation completed")
})
And animation looks like
Here is my solution (swift 3 ) :
let animator = UIViewPropertyAnimator(duration: 0.2, curve: .easeOut, animations: {
self.textView2.frame = self.textView2.frame.offsetBy(dx: 20 , dy: 25)
} )
animator.startAnimation()

How to define an animation?

I'm looking for a way to let a variable equal an animation. So that later I can refer to it and change the speed of the animation.
// Image Animation
UIView.animate(withDuration: 2.5, delay: 0.8, options: [.repeat, .curveLinear], animations: {
self.image.frame = CGRect(x: 250, y: 1200, width: 45, height: 45)
}, completion: nil)
// Function Speed Check
func speedcheck() {
if score > 25 {
Image.stopAnimating()
// Image Animation
UIView.animate(withDuration: 1.0, delay: 0.8, options: [.repeat, .curveLinear], animations: {
self.Image.frame = CGRect(x: 250, y: 1200, width: 45, height: 45)
}, completion: nil)
UIView.commitAnimations()
}
}
"Image.stopAnimating()"
As shown in the example, this line does not stop the animation that was previously executed above, because it does not know what to cease. Therefore, I'm wondering how to define the initial animation, so from there I can increase the speed.
What's the best way to speed up an animation when the score is greater than 25?
Any help would be appreciated.
You actually don't need to assign the animation to a variable. I don't think that is even possible.
By speeding up the animation, you mean reducing the duration of the animation.
To do that just hold the animationDuration value in a global variable say animationSpeed.
Just use this animationSpeed variable value in the UIView.animate() method for the duration param.
Declare a global speed param:
var animationSpeed = TimeInterval.init(5.0)
Use this in your UIView.animate() method:
UIView.animate(withDuration: animationSpeed) {
}
Now you can change this animationSpeed param value when a condition is satisfied:
if condition {
self.animationSpeed = TimeInterval.init(2.5)
}
When you give the animation options as .repeat, the values captured initially while entering the animation block will be used throughout the animation. This is not something that we want. Use something like this
func animateView() {
UIView.animate(withDuration: self.speed, delay: 0.0, options: [.curveLinear], animations: {
// Your animation
}, completion: { flag in
if flag {
if speed > 25 {
self.speed = TimeInterval.init(2.0)
self.view.layer.removeAllAnimations()
}
self.animateView()
}
})
}

Perfect Swift3 Boing

To animate a bar opening...
#IBOutlet var barHeight: NSLayoutConstraint!
barHeight.constant = barShut?30:100
self.view.layoutIfNeeded()
t = !barShut?30:100
UIView.animate(withDuration: 0.15,
delay: 0,
options: UIViewAnimationOptions.curveEaseOut,
animations: { () -> Void in
self.barHeight.constant = t
self.view.layoutIfNeeded()
},
completion: {_ in
Screen.barShut = !Screen.barShut
}
)
That's great ...
But how would you make it boing like this?
(The only way I'd know to do this is, use CADisplayLink, with a few lines of code for a spring decaying.) Is this available in UIKit?
You can use the spring animation method that is built in to UIView:
func toggleBar() -> Void {
self.view.layoutIfNeeded()
let newHeight:CGFloat = !barShut ? 30:100
barShut = !barShut
barHeightConstraint.constant = newHeight
UIView.animate(withDuration: 1.5, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 3, options: [], animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
You will want a longer animation duration than 0.15 of a second in order for the bounce to seem realistic; I think the values I have look pretty good, but you can play with them to get the exact effect you are after.
Since the animation duration is longer, I found that I could tap the button the triggered the open/shut while the previous animation was still running. Setting barShut in the completion block meant that the bar didn't react to all taps. I moved the toggle outside of the animation to address this.

Resources