Swift, playing multiple animations consecutively from an array?

Viewed 859

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. :)

2 Answers

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