My animations are going too fast. I'm doing a pattern matching game and I have 4 UIViews with different colors. I want one to blink and then about a second later, have another UIView blink. The views are blinking by my tag identifier which I already set differently for each view (1,2,3,4). It seems like they are all going at the same time. I've already tried adjusting the values for the animateWithDuration function and that doesn't seem to help.
Here is my print output so you can see it's executing in the right order...
gary
3
gary
2
gary
2
gary
3
gary
2
func beginGame() {
var level = 5
for _ in 1...level {
self.randomNumber = Int(arc4random_uniform(4)) + 1
let originalColor:UIColor = self.view.viewWithTag(randomNumber)!.backgroundColor!
UIView.animateWithDuration(1, delay: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
self.view.viewWithTag(self.randomNumber)!.backgroundColor = UIColor.whiteColor()
self.view.viewWithTag(self.randomNumber)!.backgroundColor = originalColor
print("gary")
}, completion: nil)
print(randomNumber)
enemyArray.append(randomNumber)
}
}
//New Code
func beginGame(){
var level = 5
for _ in 1...level {
self.randomNumber = Int(arc4random_uniform(4)) + 1
let originalColor:UIColor = self.view.viewWithTag(randomNumber)!.backgroundColor!
UIView.animateWithDuration(1, delay: 4, options: UIViewAnimationOptions.CurveEaseOut, animations: {
self.view.viewWithTag(self.randomNumber)!.backgroundColor = UIColor.whiteColor()
self.view.viewWithTag(self.randomNumber)!.backgroundColor = originalColor
print("gary")
}, completion: nil)
print(randomNumber)
enemyArray.append(randomNumber)
}
}
Listen to what people are telling you. If you use a for loop and create multiple animations that all have a delay value of 0, they will all run at the same time. Don't do that.
Instead, use code more like this:
let pauseBetweenAnimations = 1.0
for step in 1...level {
self.randomNumber = Int(arc4random_uniform(4)) + 1
let originalColor:UIColor =
self.view.viewWithTag(randomNumber)!.backgroundColor!
UIView.animateWithDuration(1, delay: (step - 1) * pauseBetweenAnimations,
options: UIViewAnimationOptions.CurveEaseOut,
animations:
//the rest of your code goes here...
That will make each subsequent animation start 1 second after the previous one. If 1 second is too long, change the value of pauseBetweenAnimations.
Related
I basically have a song that a cartoon needs to dance to.
Is it better to have :
Version A: one full song and dispatch a bunch of queues:
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
//Have the figure dance move 1
}
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
//Have the figure dance move 2
}
Or segment the song and actions:
func dancing(){
timeElapsed += 1
if timeElapsed == 1 {
\\figure does move 1
self.playSound()
} else if timeElapsed == 2 {
\\figure does move 2
self.playSound2()
}
As a summary:
Version a: Dispatch multiple queues at the same time
Version b: Segment the queues, but that would mean my project would have 10+ media files
Is there anyway to test this? Or any alternative methods? I've poked around and seen things like concurrent/sync queues, but don't know much on how to use them in practice.
If you don’t want too much nesting, you can use UIViewPropertyAnimator. For Example:
let animation1 = UIViewPropertyAnimator(duration: 0.5, curve: .linear) {
// animation code
}
let animation2 = UIViewPropertyAnimator(duration: 1, curve: .linear) {
// animation code
}
animation1.addCompletion { _ in
animation2.startAnimation()
}
animation1.startAnimation()
Another option you might want to look at is UIView key frame animations.
You need to figure out your relative start times and durations, but it's a bit nicer than using dispatch queues.
More info: link
A simple demonstration:
UIView.animateKeyframes(withDuration: 4.0, delay: 0.0, options: [], animations: {
// Animation 1 that starts immediately and runs for 2 seconds
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5) {
// Perform animation
}
// Animation 2 that starts after 2 seconds and runs for 2 seconds
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
// Perform animation
}
})
I will go with Version A: one full song and Set the Animation Hierarchy
Better to use UIView Animation with completion block
UIView.animate(withDuration: 0.5, animations: {
//animation 1
}, completion: { (value: Bool) in
UIView.animate(withDuration: 1.0, animations: {
//animation 2
})
})
In Swift, I would like to have several calls to UIView.animate run in series. That is, when one animation finishes, then I would like another animation to continue after, and so on.
The call to UIView.animate has a Trailing Closure which I am currently using to make a second call to UIView.animate to occur.
The Problem is: I want to do N separate animations
From the Apple Documentation for UIView.animate
completion
A block object to be executed when the animation sequence ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called. If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle. This parameter may be NULL.
Ideally, I would like to iterate over an array of animation durations and use in the calls to animate()
For example,
Goal
Iterate over an array and apply those parameters for each animation
let duration = [3.0, 5.0, 10.0]
let alpha = [0.1, 0.5, 0.66]
Xcode Version 11.4.1 (11E503a)
What I've tried
Use a map to iterate, and 🤞 hope that it works
Issue is that that only final animation occurs. So there is nothing in series
Research Q: Is it possible that I need to set a boolean in UIView that says animation occur in series?
let redBox = UIView()
redBox.backgroundColor = UIColor.red
self.view.addSubview(redBox)
let iterateToAnimate = duration.enumerated().map { (index, element) -> Double in
print(index, element, duration[index])
UIView.animate(withDuration: duration[index], // set duration from the array
animations: { () in
redBox.alpha = alpha[index]
}, completion:{(Bool) in
print("red box has faded out")
})
}
How to do one animation
let redBox = UIView()
redBox.backgroundColor = UIColor.red
self.view.addSubview(redBox)
// One iteration of Animation
UIView.animate(withDuration: 1,
animations: { () in
redBox.alpha = 0
}, completion:{(Bool) in
print("red box has faded out")
})
Ugly way to do chain two animate iterations (wish to avoid)
// two iterations of Animation, using a trailing closure
UIView.animate(withDuration: 1,
animations: { () in
redBox.alpha = 0
}, completion:{(Bool) in
print("red box has faded out")
}) { _ in // after first animation finishes, call another in a trailing closure
UIView.animate(withDuration: 1,
animations: { () in
redBox.alpha = 0.75
}, completion:{(Bool) in
print("red box has faded out")
}) // 2nd animation
}
If the parameters are few and are known at compile time, the simplest way to construct chained animations is as the frames of a keyframe animation.
If the parameters are not known at compile time (e.g. your durations are going to arrive at runtime) or there are many of them and writing out the keyframe animation is too much trouble, then just put a single animation and its completion handler into a function and recurse. Simple example (adapt to your own purposes):
var duration = [3.0, 5.0, 10.0]
var alpha = [0.1, 0.5, 0.66] as [CGFloat]
func doTheAnimation() {
if duration.count > 0 {
let dur = duration.removeFirst()
let alp = alpha.removeFirst()
UIView.animate(withDuration: dur, animations: {
self.yellowView.alpha = alp
}, completion: {_ in self.doTheAnimation()})
}
}
you can use UIView.animateKeyframes
UIView.animateKeyframes(withDuration: 18.0, delay: 0.0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 3.0/15.0, relativeDuration: 3.0/15.0, animations: {
self.redBox.alpha = 0
})
UIView.addKeyframe(withRelativeStartTime: 8.0/15.00, relativeDuration: 5.0/15.0, animations: {
self.redBox.alpha = 0.75
})
}) { (completed) in
print("completed")
}
I'm trying to rotate the background colour endlessly between two different gradients, but the actual transition isn't lasting as long as expected (it lasts for less than a second and there's no delay between each transition).
var backgroundColours = [CAGradientLayer()]
var backgroundLoop = 0
override func viewDidLoad() {
super.viewDidLoad()
backgroundColours = [Colors.init().one, Colors.init().two] // These are two CAGradientLayers but I haven't included their code
backgroundLoop = 0
self.animateBackgroundColour()
}
func animateBackgroundColour () {
backgroundLoop = (backgroundLoop + 1) % (backgroundColours.count)
UIView.animate(withDuration: 2.0, delay: 2.0, options: .curveLinear, animations: {
self.view.backgroundColor = UIColor.clear
let backgroundLayer = self.backgroundColours[self.backgroundLoop]
backgroundLayer.frame = self.view.frame
self.view.layer.insertSublayer(backgroundLayer, at: 0)
}, completion: { (Bool) -> Void in
self.animateBackgroundColour()
})
This is all inside the ViewController Class
UIView.animate animates only about five specialized view properties. You are not animating any of those properties. What you want is layer animation (e.g. CABasicAnimation).
Background
Thanks to the excellent answer here, I've managed to make a series of labels fade in sequentially.
However, the delay referred to in the function in the extension only triggers when the previous fade has completed (I think) so there is at least 1 second between them. Ideally I want to edit the model so that the delay refers to from the time the view loads, not the time the previous one has completed.
i.e. If I make the delay quite small, the second one would start fading in before the first one had finished.
This is the extension :
extension UIView {
func fadeIn(duration: TimeInterval = 1.0, delay: TimeInterval = 0.0, completion: ((Bool)->())? = nil) {
self.alpha = 0.0
UIView.animate(withDuration: duration, delay: delay, options: .curveEaseIn, animations: {
self.alpha = 1.0
}, completion: completion)
}
}
and this is my implementation on a VC with 6 labels :
func animateLabels() {
self.titleOutlet.alpha = 0.0
self.line1Outlet.alpha = 0.0
self.line2Outlet.alpha = 0.0
self.line3Outlet.alpha = 0.0
self.line4Outlet.alpha = 0.0
self.line5Outlet.alpha = 0.0
self.line6Outlet.alpha = 0.0
self.titleOutlet.fadeIn(delay: 0.2, completion: { _ in
self.line1Outlet.fadeIn(delay: 0.4, completion: { _ in
self.line2Outlet.fadeIn(delay: 0.6, completion: { _ in
self.line3Outlet.fadeIn(delay: 0.8, completion: { _ in
self.line4Outlet.fadeIn(delay: 1.0, completion: { _ in
self.line5Outlet.fadeIn(delay: 1.2, completion: { _ in
self.line6Outlet.fadeIn(delay: 1.4, completion: { _ in
})})})})})})})
}
What am I trying to get to?
So what I'm trying to get to is having line2outlet start to fade 0.4 seconds after the load of the page but it is starting only when line1 has finished fading in (which takes 1 second and is delayed by 0.2s).
What have I tried?
I have attempted to write my own delay function after reading up. I tried adding this to the extension :
func delay(interval: TimeInterval, closure: #escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
closure()
}
}
and implementing like this :
self.line1Outlet.fadeIn(delay: 0, completion: { _ in
delay(interval: 1, closure: ()->Void)
but that got loads of errors! I also tried setting minus numbers for the delays but that didn't work either.
Any help much appreciated!
One possible solution that I have used before is to use a loop with an incrementing delay variable feeding into the standard UIView.animate() method. This method does not use completion handlers.
Make an array of the labels:
var labels = [titleOutlet, line1Outlet] // etc...
Use this extension:
extension UIView {
func fadeIn(duration: TimeInterval = 1, delay: TimeInterval = 0) {
self.alpha = 0
UIView.animate(withDuration: duration, delay: delay, options: [], animations: { self.alpha = 1 }, completion: nil)
}
}
Then use it like this:
var delay = 0.2
var duration = 0.5
for label in labels {
label.fadeIn(duration: duration, delay: delay)
delay += 0.2 // Increment delay
}
Im trying to take my animating function and turn it into its own class.
Can someone help me figure out how to add repeat functionallity? Ive been trying for two days with no luck.
class FlipAnimator {
class func animate(view: UIView) {
let view = view
view.layer.transform = FlipAnimatorStartTransform
view.layer.opacity = 0.8
UIView.animateWithDuration(0.5) {
view.layer.transform = CATransform3DIdentity
view.layer.opacity = 1
}
}
Look at the available signatures for animateWithDuration here. Note that one of them is animateWithDuration(_:delay:options:animations:completion:). Try using .Repeat as the options parameter (a full list of options can be found here).
Specifically:
UIView.animateWithDuration(0.5, delay: 0, options: .Repeat, {
view.layer.transform = CATransform3DIdentity
view.layer.opacity = 1
}, nil)
Note that because animations is not the last parameter you cannot use the trailing closure
syntax.