TrevPennington
TrevPennington

Reputation: 475

Swift - CoreAnimation with duration

Trying to understand CoreAnimation and completion handlers.
Why does this code happen all at once, not over a 5.0 second period?
I am trying to get it to run 6 times with the 5 second duration each time for a total of 30 seconds and get "complete" to print every 5 seconds.

    func animateBox() {

        UIView.animate(withDuration: 5.0, delay: 0.0, options: [], animations: {
            self.myBox.layer.borderColor = self.getRandom()
        }, completion: {finished in
            print("complete")
        })
    
    }

    @objc func buttonTapped() {
        
        for _ in 1...6 {
            animateBox()
            print("animate")
        }
    }

Upvotes: 1

Views: 247

Answers (2)

Duncan C
Duncan C

Reputation: 131398

Another way you could sequence the animations is to use the delay parameter, and add a delay for each step after the first:

func animateBox(step: Int) {
    let duration = 5.0
    UIView.animate(withDuration: duration, 
      delay: duration * Double(step), 
      options: [], 
      animations: {
        self.myBox.layer.borderColor = self.getRandom()
    }, completion: {finished in
        print("complete")
    })

}

@objc func buttonTapped() {
    
    for index in 0...5 {
        animateBox(step: index)
        print("animating step \(index)")
    }
}

(Note that I changed your loop to run from 0...5 so delay =step * duration would start at 0.)

Upvotes: 0

aheze
aheze

Reputation: 30228

for _ in 1...6 { is executed instantly, so animateBox is called 6 times, right after another, in a split second.

What you want to do is call each animation block inside the completion handler (which is called when the animation completes) of the previous one. Something like this:

UIView.animate(withDuration: 5.0, delay: 0.0, options: [], animations: {
    self.myBox.layer.borderColor = self.getRandom()
}, completion: { finished in
    print("complete")

    UIView.animate(withDuration: 5.0, delay: 0.0, options: [], animations: {
        self.myBox.layer.borderColor = self.getRandom()
    }, completion: { finished in
        print("complete")

        UIView.animate(withDuration: 5.0, delay: 0.0, options: [], animations: {
            self.myBox.layer.borderColor = self.getRandom()
        }, completion: { finished in
            print("complete")

            ...
        })
    })
})

But this will result in a huge completion pyramid... instead, try using animateKeyframes:

let totalDuration = CGFloat(30)
let relativeIndividualDuration = CGFloat(1) / CGFloat(6)

UIView.animateKeyframes(withDuration: totalDuration, delay: 0, options: .calculationModeCubic, animations: {
    UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: relativeIndividualDuration) {
        self.myBox.layer.borderColor = self.getRandom()
    }

    UIView.addKeyframe(withRelativeStartTime: relativeIndividualDuration, relativeDuration: relativeIndividualDuration) {
        self.myBox.layer.borderColor = self.getRandom()
    }

    UIView.addKeyframe(withRelativeStartTime: relativeIndividualDuration * 2, relativeDuration: relativeIndividualDuration) {
        self.myBox.layer.borderColor = self.getRandom()
    }

    ...
})

Upvotes: 1

Related Questions