Swift, playing multiple animations consecutively from an array? - swift

Sorry, I am new to swift. I can't get each animation to play consecutively and not all at once. I have tried using sleep(), but that then doesn't seem to allow the animation to play. This is how I find which animation to play.
for number in sequence {
switch number {
case 1:
print("blue")
animateB()
case 2:
print("green")
animateG()
case 3:
print("magenta")
animateM()
case 4:
print("orange")
animateO()
case 5:
print("yellow")
animateY()
case 6:
print("red")
animateR()
case 7:
print("purple")
animateP()
case 8:
print("cyan")
animateC()
default:
print("error")
}
}
And this is one of the functions i am using to animate. I realize this is probably very inefficient too, but wasn't sure how to make the function better.
private func animateB(){
let animation = CABasicAnimation(keyPath: "transform.scale")
animation.toValue = 1.3
animation.duration = 0.5
animation.autoreverses = true
self.pulsatingB.add(animation, forKey: "pulsing")
}
Any help would be great thanks. :)

You can use CATransaction to chain the CAAnimations:
class ViewController: UIViewController {
// The animations, to be applied in order
var animationQueue = [() -> Void]()
#IBAction func animate(_ sender: Any) {
animationQueue.removeAll()
// Build the animation queue
for number in sequence {
switch number {
case 1:
print("blue")
animationQueue.append(animateB)
case 2:
print("green")
animationQueue.append(animateG)
// ....
default:
break
}
}
// Start the animation
applyNextAnimation()
}
func applyNextAnimation() {
guard !animationQueue.isEmpty else { return }
let animation = animationQueue.removeFirst()
// When an animation completes, call this function again to apply the next animation
CATransaction.begin()
CATransaction.setCompletionBlock({ self.applyNextAnimation() })
animation()
CATransaction.commit()
}
}

For a sequence of animations, a block based keyframe animation can often do the job, too, e.g.:
UIView.animateKeyframes(withDuration: 4.0, delay: 0, options: .repeat, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.25, animations: {
self.subview.transform = .init(scaleX: 0.5, y: 0.5)
})
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25, animations: {
self.subview.transform = .init(scaleX: 1.3, y: 1.3)
})
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.25, animations: {
self.subview.transform = .init(scaleX: 0.75, y: 0.75)
})
UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25, animations: {
self.subview.transform = .identity
})
}, completion: nil)
Or, if you have an array of functions:
let animations = [animateA, animateB, animateC, animateD]
UIView.animateKeyframes(withDuration: 4.0, delay: 0, options: .repeat, animations: {
for (index, animation) in animations.enumerated() {
UIView.addKeyframe(withRelativeStartTime: Double(index) / Double(animations.count), relativeDuration: 1 / Double(animations.count), animations: {
animation()
})
}
}, completion: nil)
Where,
func animateA() {
subview.transform = .init(scaleX: 0.5, y: 0.5)
}
func animateB() {
subview.transform = .init(scaleX: 1.3, y: 1.3)
}
...

Related

How do you do successive animations?

I’m wanting to do successive animations, like one after another instead of on a loop or one at a time. What does it look like when coding that?
Example: fade in # 100% opacity, then fade out # 20% opacity, then fade in 80%, then fade out 10%... so like a pulsing then at 0% change the label text and do the inverse (basically the same as the picture only every time I try and make it progressively fade out- it disrupts the whole animation)
The gif shows it's current state, not what I've tried thus far that didnt work.
import UIKit
class Belief2ViewController: UIViewController {
#IBOutlet var negBeliefLabelGlower: UILabel!
#IBOutlet var posBeliefLabelGlower: UILabel!
#IBOutlet var labelNegBeliefFinal: UILabel!
#IBOutlet var startButton: UIButton!
//PASSING INFO
var negBeliefLabelGlowerText = String()
var posBeliefLabelGlowerText = String()
override func viewDidLoad() {
super.viewDidLoad()
//PASSING INFO
negBeliefLabelGlower.text = negBeliefLabelGlowerText
posBeliefLabelGlower.text = posBeliefLabelGlowerText
//PASSING INFO
labelNegBeliefFinal.text = negBeliefLabelGlowerText
//GLOW
labelNegBeliefFinal.UILableTextShadow(color: UIColor.systemTeal)
//AniamtionOpacityLOOP
labelNegBeliefFinal.alpha = 0.3
// UIView.animate(withDuration: 5, animations: {self.labelNegBeliefFinal.alpha = 100}, completion: { _ in self.labelNegBeliefFinal.alpha = 90;})
UIView.animate(withDuration: 6, delay: 0, options: [.autoreverse, .repeat], animations: { self.labelNegBeliefFinal.alpha = 100 })
//AnimationBreathSizeLOOP
UIView.animate(withDuration: 6, delay: 0, options: [.autoreverse, .repeat], animations: { self.labelNegBeliefFinal.transform = CGAffineTransform(scaleX: 1.3, y: 1.3) })
}
#IBAction func startButtonPress(_ sender: Any) {
//self.labelNegBeliefFinal.text = self.posBeliefLabelGlowerText
// UIView.animate(withDuration: 10, animations: {self.labelNegBeliefFinal.alpha = 0}, completion: { _ in self.labelNegBeliefFinal.text = self.posBeliefLabelGlowerText;})
UIView.animate(withDuration: 10, delay: 0, animations: {self.labelNegBeliefFinal.alpha = 0}, completion: { _ in
self.labelNegBeliefFinal.text = self.posBeliefLabelGlowerText ;})
//
}
}
//GLOW
extension UILabel {
func UILableTextShadow1(color: UIColor){
textColor = UIColor.systemTeal
layer.shadowColor = UIColor.systemTeal.cgColor
layer.masksToBounds = false
layer.shadowOffset = .zero
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale
layer.shadowRadius = 5.0
layer.shadowOpacity = 5.0
}
}
EDIT
Okay so using the keyframes as suggest was a perfect suggestion
but I am running into a bit of an issue. I cannot key in the label text to change
UIView.animateKeyframes(withDuration: 15.0,
delay: 0.0,
options: [],
animations: {
UIView.addKeyframe(withRelativeStartTime: 1,
relativeDuration: 0.0,
animations: { self.labelNegBeliefFinal.text = self.posBeliefLabelGlowerText })
},
completion: nil)
So I wasn't able to incorporate the label into the keyframe animation so I just set a delay to the bale changing and it took a little tweaking but here is what I came up with
//CHANGE LABEL ON TIMER
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 13.5)
{ [self] in
self.labelNegBeliefFinal.text = self.posBeliefLabelGlowerText
}
Here is the glow loop and size loop as before, unrelated to the tween
//AniamtionGlowLOOP
UIView.animate(withDuration: 6, delay: 0, options: [.autoreverse, .repeat],
animations: { self.labelNegBeliefFinal.layer.shadowOpacity = 5.0 })
//AnimationBreathSizeLOOP
UIView.animate(withDuration: 6, delay: 0, options: [.autoreverse, .repeat],
animations: { self.labelNegBeliefFinal.transform = CGAffineTransform(scaleX:
1.3, y: 1.3) })
// }
Here is the tween code I came up with thanks to Matt for the point in the right direction. Had no idea you could tween in SwiftUI. One thing that threw me off is that if the duration is 30sec, relative duration being set to 0.5 is equal to 15 seconds as it's 0.5 of 30 seconds. Some of my tweens didn't seem to work because I didn't realize that.
UIView.animateKeyframes(withDuration: 30.0,
delay: 0,
options: [ ],
animations: {
UIView.addKeyframe(withRelativeStartTime:
0,
relativeDuration: 0.5,
animations: { self.labelNegBeliefFinal.alpha = 0 })
UIView.addKeyframe(withRelativeStartTime:
0.5,
relativeDuration: 0.5,
animations: { self.labelNegBeliefFinal.alpha = 1 })
},
completion: nil)
}
}
Glow style I got from a different post
//GLOW
extension UILabel {
func UILableTextShadow1(color: UIColor){
textColor = UIColor.systemTeal
layer.shadowColor = UIColor.systemTeal.cgColor
layer.masksToBounds = false
layer.shadowOffset = .zero
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale
layer.shadowRadius = 5.0
layer.shadowOpacity = 00.7
}
}

Touch Labels That Keep Animating

I'm having an issue trying to touch a label that keeps animating because during the animation it becomes an image (set the animation on repeat). Is there a way to touch it during animation (I cant't use UITapGestureRecognizer) ?
func animatePro() {
UIView.animate(withDuration: 2, delay: 0, options: [.autoreverse, .repeat], animations: {
//1.5 times it's normal size
self.proLabel.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}){ (finished) in
self.proLabel
.transform = CGAffineTransform.identity
}
}
func animateRev() {
UIView.animate(withDuration: 2, delay: 0, options: [.repeat], animations: {
//1.5 times it's normal size
self.revLabel.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}){ (finished) in
UIView.animate(withDuration: 2, delay: 0, animations: {
self.revLabel.transform = CGAffineTransform.identity
})
}
}
Add allowUserInteraction
options: [.allowUserInteraction,.autoreverse, .repeat]

Swift move image from right to left than from left to right

I want to make an animation that move a background image from right to left than left to right.
I use this code but not works
UIView.animate(withDuration: 5, delay: 0, options: .repeat ,animations: { () -> Void in
imageView.transform = CGAffineTransform(translationX: -imageView.frame.width + self.view.bounds.width, y: 0)
UIView.animate(withDuration: 5, delay: 1, options: .repeat ,animations: { () -> Void in
imageView.transform = CGAffineTransform(translationX: +imageView.frame.width - self.view.bounds.width, y: 0)
})
})
I solved with this
UIView.animate(withDuration: 30.0, delay: 0, options: [.repeat, .autoreverse], animations: {
imageView.transform = CGAffineTransform(translationX: -imageView.frame.width + self.view.bounds.width, y: 0)
}, completion: nil)
Use the following code if you want to use the legacy animate(withDuration:animations:) API. However, you need to wait for the first animation to be finished - this means you have to use the completion block.
However, your value for translationX does not makes sense as well.
UIView.animate(withDuration: 1, animations: {
self.imageView.transform = CGAffineTransform(translationX: -self.imageView.frame.width, y: 0)
}) { _ in
UIView.animate(withDuration: 1) {
self.imageView.transform = CGAffineTransform.identity
}
}
I would strongly recommend having a look at the "new" way of doing Animations: UIViewPropertyAnimator(https://developer.apple.com/documentation/uikit/uiviewpropertyanimator)

UIView on UITableViewCell updating without animating

I had a regression and I don't know what could have caused it.
Desired: smooth animation
Current: update without animation.
Here is the code that caused the break. I don't know why.
var pillModel: PillModel? {
didSet {
guard let pillModel = pillModel else { return }
DispatchQueue.main.async {
self.movePill(pillModel.side)
}
movingPill.backgroundColor = pillModel.movingPillColor
leftLabel.textColor = pillModel.leftLabelColor
rightLabel.textColor = pillModel.rightLabelColor
leftLabel.text = pillModel.leftTekst
rightLabel.text = pillModel.rightTekst
movingPill.layer.applySketchShadow(color: movingPill.backgroundColor!, alpha: 0.7, y: 3)
pillContainer.layoutSubviews()
commonStyle()
}
}
Here is some relevant code.
func movePill( _ sideTouched: Side, _ completion: (() -> ())? = nil) {
constrainPillTo(sideTouched)
pillModel?.side = sideTouched
UIView.animate(withDuration: 0.85, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: 1, options: .transitionCrossDissolve, animations: {
[weak self] in
guard let selfy = self else {return}
selfy.movingPill.backgroundColor = .orange//selfy.pillModel?.movingPillColor
selfy.leftLabel.textColor = .yellow//selfy.pillModel?.leftLabelColor
selfy.rightLabel.textColor = .green//selfy.pillModel?.rightLabelColor
selfy.movingPill.layer.applySketchShadow(color: selfy.movingPill.backgroundColor ?? .socialBlue,
alpha: 0.7, y: 3)
//selfy.pillContainer.layoutSubviews()
}) { _ in
completion?()
}
}
It turns out I created a cycle. When I moved the code out of the pillModel property's didSet method into its own function, the issue was resolved completely.

How do I stop an animation when tapped

I'm making a tap game, and this is the button with the animation. It is very slow, and I want to speed it up so when the user taps, it will reset the animation and count the tap.
Currently, it is slow to the point that it will miss taps if tapped again while the animation is still going on.
#IBAction func slimeTap(_ sender: UIButton) {
tapCount += tapIncrease
checkLevel(tapCount)
UIView.animate(withDuration: 0.03, animations: {
//shrink
self.playSound()
sender.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
}, completion: {_ in
//change it back to how it was
//grow
UIView.animate(withDuration: 0.05, animations: {
sender.transform = CGAffineTransform(scaleX: 1, y: 1)
})
})
}
Try adding .layer.removeAllAnimations() to remove any existing animations on the layer, and .allowUserInteraction as an animation option to enable and register user tap events:
#IBAction func slimeTap(_ sender: UIButton) {
tapCount += tapIncrease
checkLevel(tapCount)
resizingView.layer.removeAllAnimations()
UIView.animate(withDuration: 0.3, delay: 0, options: [.allowUserInteraction], animations: {
self.playSound()
sender.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
}) { _ in
UIView.animate(withDuration: 0.5, delay: 0, options: [.allowUserInteraction], animations: {
sender.transform = CGAffineTransform(scaleX: 1, y: 1)
})
}
}