Sequence 2 Animations That Loop With Delay In-Between - ios

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()
}
}
}

Related

UIStackView - hide and collapse subview with animation

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()
}

Best way to execute multiple animations synchronously

This will be a 'best way to do' kind of a question.
I know there are multiple ways to do sequence animations, but which one is the better way to do it?
let's say I have 3 animations that I'm trying to execute, one after the other, so the next one only starts when the previous has finished.
I can do it with the UIView.animate(withCompletionBlock:) so in the completion section I call the second one and on the completionBlock of the second one I call the third one.
This one in particular makes me think: Is that a 'beautiful' way to do it? Is there any known issues using completionBlocks within another?
Just a thought. Calling animations within the previous animation completion block, may bring the code somehow like the pyramid of doom, if there's something like 10 synchronous animations, don't you think?
Another way to do is with the dispatch_after, the I call the next animation based on the NSTimeInterval of the previous one. In this case it may cause a app crash suspending the Main thread for the next one to execute, then suspend it again to execute the third one.
The last known way that I know is that I can use a NSTimeInterval to call the next animation, but this one is somewhat like the dispatch_after option.
In your opinion which would be the best way to do it?
Executing 3 or more visual animations, all synchronously (waiting for the previous one to finish to start the next one).
I might use this a lot in my app and I want to start it with the best way.
EXAMPLES
Animations within completionBlocks
DispatchQueue.main.async {
UIView.animate(withDuration: 2.0, animations: {
view1.alpha = 1
}, completion: { (_) in
UIView.animate(withDuration: 5.0, animations: {
view2.alpha = 1
}, completion: { (_) in
UIView.animate(withDuration: 2.0, animations: {
view3.alpha = 0
}, completion: { (_) in
UIView.animate(withDuration: 2.0, animations: {
view4.alpha = 0
}, completion: { (_) in
print("all completed")
})
})
})
})
}
With dispatch_after:
let anim1_duration = 2.0
let anim2_duration = 3.0
let anim3_duraiton = 5.0
UIView.animate(withDuration: anim1_duration, animations: {
view1.alpha = 1
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.init(uptimeNanoseconds: UInt64(anim1_duration * Double(NSEC_PER_SEC)))) {
UIView.animate(withDuration: anim2_duration, animations: {
view2.alpha = 1
}
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.init(uptimeNanoseconds: UInt64((anim1_duration + anim2_duration) * Double(NSEC_PER_SEC)))) {
UIView.animate(withDuration: anim3_duration, animations: {
view3.alpha = 1
}
}
Check out UIView's animate​Keyframes(with​Duration:​delay:​options:​animations:​completion:​):
class func animateKeyframes(withDuration duration: TimeInterval,
delay: TimeInterval,
options: UIViewKeyframeAnimationOptions = [],
animations: #escaping () -> Void,
completion: ((Bool) -> Void)? = nil)
For your specific case, it would look like this:
UIView.animateKeyframes(withDuration: 20,
delay: 0,
options: [],
animations: {
UIView.addKeyframe(withRelativeStartTime: 0,
relativeDuration: 0.2,
animations: {
view1.alpha = 1
})
UIView.addKeyframe(withRelativeStartTime: 0.2,
relativeDuration: 0.3,
animations: {
view2.alpha = 1
})
UIView.addKeyframe(withRelativeStartTime: 0.5,
relativeDuration: 0.1,
animations: {
view3.alpha = 0
})
UIView.addKeyframe(withRelativeStartTime: 0.6,
relativeDuration: 0.4,
animations: {
view4.alpha = 0
})
},
completion: nil)
In terms of 'better reading code' consider these 2 ways of performing animations:
building stacks of animations: more
create keyframe animations using CAKeyframeAnimation: more
These options are avoiding completion blocks, which I like.

How can I make an UILabel appear and disappear 5 times?

When I press a button, a view with a label appears. Then I want the label to disappear and reappear 5 times (with animation), each time the label reappears with different text.
I can't seem to get the any code to work to animate a UILabel and change the text. I have tried this. However, when I press the button, the loop and animations occur instantly.
The duration is a follows: 0.3, 0.6, 0.9, 1.2, 1.5
for i in 1...5 {
infoViewMsgLbl.text = randomReward(real: i) // This retrives a String
UIView.animate(withDuration: ((1.5/5)*Double(i)), delay: 0.0, options: .allowAnimatedContent, animations: {
self.infoViewMsgLbl.alpha = 0
}, completion: { finished in
UIView.animate(withDuration: ((1.5/5)*Double(i)), animations: {
self.infoViewMsgLbl.alpha = 1
})
})
print("in the loop: ", i)
}
You are changing duration, but leaving delay as 0 (i.e. start immediately). In these cases, you might do it the other way around, changing delay and leaving duration unchanged.
But given that you want to change the label text, I might use a timer instead:
var index = 1
Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { [weak self] timer in
self?.infoViewMsgLbl.alpha = 0
self?.infoViewMsgLbl.text = self?.randomReward(real: index)
index += 1
if index > 5 { timer.invalidate() }
UIView.animate(withDuration: 0.3) {
self?.infoViewMsgLbl.alpha = 1
}
}
Maybe something like this:
func animate(alpha: CGFloat, start: Int, end: Int) {
infoViewMsgLbl.text = randomReward(real: Int(start/2)) // This retrives a String
UIView.animate(withDuration: 0.3, animations: {
self.infoViewMsgLbl.alpha = alpha
}, completion: { success in
if start + 1 <= end {
self.animate(alpha: alpha == 1.0 ? 0.0 : 1.0, start: start + 1, end: end)
}
})
}
Usage:
animate(alpha:0.0, start: 0, end: 10) //would flash a total of 5 times
Note that the duration stays 0.3 because it doesn't call the next loop until each additional 0.3 seconds has passed.

Changing opacity during keyframe animation Swift

I am trying to animate a few circles and changing their opacities simultaneously.
UIView.animateKeyframesWithDuration(2, delay: 0, options: .Repeat, animations: {
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0.5, animations: {
self.innerRingView.alpha = 1
})
UIView.addKeyframeWithRelativeStartTime(0.5, relativeDuration: 0.5, animations: {
self.innerRingView.alpha = 0
self.middleRingView.alpha = 1
})
UIView.addKeyframeWithRelativeStartTime(1, relativeDuration: 0.5, animations: {
self.middleRingView.alpha = 0
self.outterRingView.alpha = 1
})
UIView.addKeyframeWithRelativeStartTime(1.5, relativeDuration: 0.5, animations: {
self.outterRingView.alpha = 0
})
}, completion: nil)
For some reason, it never gets to the second animation:
UIView.addKeyframeWithRelativeStartTime(1, relativeDuration: 0.5, animations: {
self.middleRingView.alpha = 0
self.outterRingView.alpha = 1
})
Because of this, my animation isn't work. innerRingView never goes back to 0 opacity and outterRingView never appears (all the views are set to 0 alpha by default).
What seems to be the problem here?
Issue appeared to be with your key frame relative start time
UIView.addKeyframeWithRelativeStartTime
which must be be in the range 0 to 1, where 0 represents the start of the overall animation and 1 represents the end of the overall animation. For example, for an animation that is two seconds in duration, specifying a start time of 0.5 causes the animations to begin executing one second after the start of the overall animation.

UIButton Text Animation Not Animating

Upon a button's touchUpInside, it runs a series of code (all data related, none of the code in the main function modifies a view object) and the last thing that code does is call the animation function that I've pasted below. The problem is, the animation looks nothing like it should.
Desired animation:
The footerImg should fade out
The footerBar text should fade in
The footerBar text should fade out
The footerImg should fade back in
The text and image occupy the same position on the screen, so the visual should be of one replacing the other, and then it going back to the original state. The views are not nested in each other, they share the same container.
Actual animation seen in simulator:
-footerImg is never even visible
-footerBar text appears immediately after trigger is tapped, no animation
-footerBar text fades out
-footerImg fades in
footerBar is a UIButton
-It's starting state, before the action ever begins, is that the button is empty and clear (invisible to user)
-In case it matters, this is not the button that triggered the function
-Has a clear background, so its text should be the only thing animating in/out
footerImg is a UIImageView
-Just an icon image, nothing special
func animateFeedback() {
UIView.animateWithDuration(0.25, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0, options: nil, animations: {
self.footerImg.alpha = 0
self.footerBar.alpha = 0
},
completion: { finished in
self.footerBar.setTitle("Item added", forState: UIControlState.Normal)
}
)
UIView.animateWithDuration(1.5, delay: 0.5, options: nil, animations: {
self.footerBar.alpha = 1
}
, completion: nil
)
UIView.animateWithDuration(0.75, delay: 1.25, options: nil, animations: {
self.footerBar.alpha = 0
}
, completion: { finished in
self.footerBar.setTitle("", forState: UIControlState.Normal)
}
)
UIView.animateWithDuration(0.25, delay: 1.5, options: nil, animations: {
self.footerImg.alpha = 1
}
, completion: nil
)
}
For the sequence animation, you should put behind animations in completion block. E.g.
The footerBar text should fade in
The footerBar text should fade out
Try this:
self.footerBar.alpha = 0;
UIView.animateWithDuration(1.5, delay: 0.5, options: nil, animations: {
self.footerBar.alpha = 1
}
, completion: { finished in
UIView.animateWithDuration(0.75, delay: 1.25, options: nil, animations: {
self.footerBar.alpha = 0
}
, completion: { finished in
self.footerBar.setTitle("", forState: UIControlState.Normal)
}
)
}
)
And the same for others
comment all codes that u have in "animateFeedback()" and paste the below code to it
UIView.animateWithDuration(1.5, delay: 0, options: nil, animations: { () -> Void in
self.footerImg.alpha = 0;
self.footerBar.setTitle("Item added", forState: UIControlState.Normal)
},
completion: { finished in
UIView.animateWithDuration(1.5, delay: 0, options: nil, animations: { () -> Void in
self.footerImg.alpha = 1;
self.footerBar.setTitle("", forState: UIControlState.Normal)
},
completion: { finished in
}
)
}
)

Resources