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.
Related
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()
}
}
}
I am trying to create a blinking effect on the UIView. Currently I am using a code which blinks the UIView with infinite number of times. the Code looks like this
WhatI have done so far:
func startBlink() {
UIView.animate(withDuration: 0.8,//Time duration
delay:0.0,
options:[.allowUserInteraction, .curveEaseInOut, .autoreverse, .repeat],
animations: { self.alpha = 0 },
completion: nil)
}
But this code blinks the ui view for infinite number of time. I used another code but that was blinking for one time only.
What I want:
So I am pretty close but I really want to blink the UIView for finite
number of times i.e 30 times, and it must stop after 30th blink.
Please help me in this, I think I have clear in my question. Please help me out.
Use this function to animate View. I hope it can help
extension UIView {
func flash(numberOfFlashes: Float) {
let flash = CABasicAnimation(keyPath: "opacity")
flash.duration = 0.2
flash.fromValue = 1
flash.toValue = 0.1
flash.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
flash.autoreverses = true
flash.repeatCount = numberOfFlashes
layer.add(flash, forKey: nil)
}
}
There is a builtin in class function for the count and call it in the block.
class func setAnimationRepeatCount(_ repeatCount: Float)
func startBlink() {
UIView.animate(withDuration: 0.8,//Time duration
delay:0.0,
options:[.allowUserInteraction, .curveEaseInOut, .autoreverse, .repeat],
animations: {
UIView.setAnimationRepeatCount(30) // repeat 30 times.
self.alpha = 0
},
completion: nil)
}
I have a UIView that I want to reveal after 0.5 seconds, and hide again after 0.5 seconds, creating a simple animation. My code is as follows:
let animation = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear) {
self.timerBackground.alpha = 1
let transition = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear) {
self.timerBackground.alpha = 0
}
transition.startAnimation(afterDelay: 0.5)
}
animation.startAnimation()
When I test it out, nothing happens. I assume it's because they're both running at the same time, which would mean they cancel each other out, but isn't that what the "afterDelay" part should prevent?
If I run them separately, i.e. either fading from hidden to visible, or visible to hidden, it works, but when I try to run them in a sequence, it doesn't work.
My UIView is not opaque or hidden.
You can use Timer, and add appearing / hiding animations blocks on every timer tick to your UIViewPropertyAnimatorobject.
Here's a codebase:
#IBOutlet weak var timerBackground: UIImageView!
private var timer: Timer?
private var isShown = false
private var viewAnimator = UIViewPropertyAnimator.init(duration: 0.5, curve: .linear)
override func viewDidLoad() {
super.viewDidLoad()
viewAnimator.addAnimations {
self.timerBackground.alpha = 1
}
viewAnimator.startAnimation()
isShown = true
self.timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(self.startReversedAction), userInfo: nil, repeats: true)
}
func startReversedAction() {
// stop the previous animations block if it did not have time to finish its movement
viewAnimator.stopAnimation(true)
viewAnimator.addAnimations ({
self.timerBackground.alpha = self.isShown ? 0 : 1
})
viewAnimator.startAnimation()
isShown = !isShown
}
I've implemented the very similar behavior for dots jumping of iOS 10 Animations demo project.
Please, feel free to look at it to get more details.
Use UIView.animateKeyframes you'll structure your code nicely if you have complicated animations. If you'll use UIView animations nested within the completion blocks of others, it will probably result in ridiculous indentation levels and zero readability.
Here's an example:
/* Target frames to move our object to (and animate)
or it could be alpha property in your case... */
let newFrameOne = CGRect(x: 200, y: 50, width: button.bounds.size.width, height: button.bounds.size.height)
let newFrameTwo = CGRect(x: 300, y: 200, width: button.bounds.size.width, height: button.bounds.size.height)
UIView.animateKeyframes(withDuration: 2.0,
delay: 0.0,
options: .repeat,
animations: { _ in
/* First animation */
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5, animations: { [weak self] in
self?.button.frame = newFrameOne
})
/* Second animation */
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: { [weak self] in
self?.button.frame = newFrameTwo
})
/* . . . */
}, completion: nil)
What worked for me, was using sequence of UIViewPropertyAnimators. Here is example of my code:
let animator1 = UIViewPropertyAnimator(duration:1, curve: .easeIn)
animator1.addAnimations {
smallCoin.transform = CGAffineTransform(scaleX: 4, y: 4)
smallCoin.center = center
}
let animator2 = UIViewPropertyAnimator(duration:1, curve: .easeIn)
animator2.addAnimations {
center.y -= 20
smallCoin.center = center
}
let animator3 = UIViewPropertyAnimator(duration:10, curve: .easeIn)
animator3.addAnimations {
smallCoin.alpha = 0
}
animator1.addCompletion { _ in
animator2.startAnimation()
}
animator2.addCompletion { _ in
animator3.startAnimation()
}
animator3.addCompletion ({ _ in
print("finished")
})
animator1.startAnimation()
You can even add afterdelay attribute to manage speed of animations.
animator3.startAnimation(afterDelay: 10)
I'm creating a simple left to right animation for a label using key frames but when the animation repeats, the delay is ignored.
The first time it executes, the delay of 3 seconds has an effect, but when the animation repeats, the delay is ignored. This causes the animation to re-start immediately after it ends.
UIView.animateKeyframes(withDuration: 10, delay: 3, options: [.calculationModePaced, .repeat], animations: {
let xDist = self.Label_ArtistAlbum2.frame.origin.x
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.1, animations: {
self.Label_ArtistAlbum2.frame.origin.x = self.Label_ArtistAlbum2.frame.origin.x - (xDist * 0.1)
})
UIView.addKeyframe(withRelativeStartTime: 0.9, relativeDuration: 0.1, animations: {
self.Label_ArtistAlbum2.frame.origin.x = 0
})
}, completion: nil)
I've tried adding an extra keyframe at the end however this has no effect even with the altered times:
UIView.animateKeyframes(withDuration: 10, delay: 3, options: [.calculationModePaced, .repeat], animations: {
let xDist = self.Label_ArtistAlbum2.frame.origin.x
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.1, animations: {
self.Label_ArtistAlbum2.frame.origin.x = self.Label_ArtistAlbum2.frame.origin.x - (xDist * 0.1)
})
UIView.addKeyframe(withRelativeStartTime: 0.1, relativeDuration: 0.7, animations: {
self.Label_ArtistAlbum2.frame.origin.x = 0
})
//attempted pause - does not appear to work perhaps since the position is unchanged?
UIView.addKeyframe(withRelativeStartTime: 0.8, relativeDuration: 0.2, animations: {
self.Label_ArtistAlbum2.frame.origin.x = 0
})
}, completion: nil)
If the delay will not be repeated along with the rest of the animation, how can I create a pause before the entire animation repeats?
I had a similar problem for animating a loading view. I solved it this way:
I created an enum for the steps in the animation
private enum TriangleToAnimate {
case one
case two
case three
case pause
}
I have my variables
private var triangleViewToFireCount = TriangleToAnimate.one
var triangle1View : TriangleView
var triangle2View : TriangleView
var triangle3View : TriangleView
I start a timer to run each animation
override init(frame: CGRect) {
Timer.scheduledTimer(timeInterval: 0.33, target: self, selector: #selector(LoadingView.timerFire), userInfo: nil, repeats: true)
}
For the selector I have a fire method. In the method I have a switch for each of the enum cases
func timerFire(){
let anim = createAnimation()
switch triangleViewToFireCount {
case .one:
triangle1View.layer.add(anim, forKey: "transform")
triangleViewToFireCount = .two
case .two:
triangle2View.layer.add(anim, forKey: "transform")
triangleViewToFireCount = .three
case .three:
triangle3View.layer.add(anim, forKey: "transform")
triangleViewToFireCount = .pause
default:
triangleViewToFireCount = .one
}
}
This is the code how I created the animation as a keyframe
func createAnimation() -> CAKeyframeAnimation{
let tr = CATransform3DIdentity
let orignalScale = CATransform3DScale(tr, 1, 1, 1)
let doubleScale = CATransform3DScale(tr, 2, 2, 1)
let keyAn = CAKeyframeAnimation(keyPath: "transform")
keyAn.keyTimes = [0, 0.1, 0.6]
keyAn.duration = 1
keyAn.values = [orignalScale,doubleScale,orignalScale]
keyAn.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
return keyAn
}
I've done a lot of testing on this issue and have found what I believe is the problem.
I think that the animation option for the Curve is my issue.
In my case choosing
calculationModePaced
has the effect of recalculating my keyframe parameters and not guaranteeing to hit any of them except the beginning and end. All intermediate keyframes become 'suggestions'.
You can't create a pause at the end because the keyframe is 'consumed' when recalculating and does not stand on it's own.
I changed to calculationModeLinear and got the keyframes I expected, and the pause too.
However, it is as smooth as I would like so I'll have to keep tinkering...
Apple's docs are here - they're descriptive but could really use some graphics and/or examples:
https://developer.apple.com/reference/uikit/uiview?language=swift
A good graph of the curves can be found here: https://www.shinobicontrols.com/blog/ios7-day-by-day-day-11-uiview-key-frame-animations
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.