Chris
Chris

Reputation: 325

How to properly set the CABasicAnimation (begin) time?

I animate the color of CAShapeLayers stored in an Array using CABasicAnimation. The animation displays erratically depending on the animation.duration and I cannot figure out why. I suspect an issue with animation.beginTime = CACurrentMediaTime() + delay

Animation Description

The animation consists in successively flashing shapes to yellow before turning them to black once the animation ends.

Current State of the animation

When the animation duration is above a certain time, it works properly.

2-second animation

But when I shorten the duration, the result substantially differs.

1-second animation:

You will notice that the animation has already cached/ended for the first 10 bars or so, then waits and starts animating the remainder of the shapes.

0.5s animation

In this case, it seems an even larger number of animation has already ended (shapes are black) before it displays some animation after a certain time. You can also notice that although the shape color animation is supposed to last the same duration (0.5s) some feels quicker than others.

The Code

The animation is called in the viewDidAppear method of the UIViewController class.

I have created a UIView custom class to draw my shapes and I animate them using an extension of the class.

The code to animate the color:

    enum ColorAnimation{
        case continuousSwap
        case continousWithNewColor(color: UIColor)
        case randomSwap
        case randomWithNewColor(color: UIColor)
        case randomFromUsedColors
    }


    func animateColors(for duration: Double,_ animationType: ColorAnimation, colorChangeDuration swapColorDuration: Double){
        guard abs(swapColorDuration) != Double.infinity else {
            print("Error in defining the shape color change duration")
            return
        }
        let animDuration = abs(duration)
        let swapDuration = abs(swapColorDuration)
        let numberOfSwaps = Int(animDuration / min(swapDuration, animDuration))


        switch animationType {
        case .continousWithNewColor(color: let value):
            var fullAnimation = [CABasicAnimation]()
            for i in (0...numberOfSwaps) {
                let index = i % (self.pLayers.count)
                let fromValue = pLayers[index].pattern.color
                let delay =  Double(i) * swapDuration / 3
                let anim = colorAnimation(for: swapDuration, fromColor: value, toColor: fromValue, startAfter: delay)
                fullAnimation.append(anim)
            }

            for i in (0...numberOfSwaps) {
                CATransaction.begin()
                 let index = i % (self.pLayers.count)
                CATransaction.setCompletionBlock {
                    self.pLayers[index].shapeLayer.fillColor = UIColor.black.cgColor
                }
                pLayers[index].shapeLayer.add(fullAnimation[i], forKey: "fillColorShape")
                CATransaction.commit()
            }
        default:
            ()
        }
    }

The segment the whole duration of the animation by the duration of the color change (e.g. if the whole animation is 10s and each shape changes color in 1s, it means 10 shapes will change color). I then create the CABasicaAnimation objects using the method colorAnimation(for: fromColor, toColor, startAfter:).

func colorAnimation(for duration: TimeInterval, fromColor: UIColor, toColor: UIColor, reverse: Bool = false, startAfter delay: TimeInterval) -> CABasicAnimation {
        let anim = CABasicAnimation(keyPath: "fillColor")
        anim.fromValue = fromColor.cgColor
        anim.toValue = toColor.cgColor
        anim.duration = duration
        anim.autoreverses = reverse
        anim.beginTime = CACurrentMediaTime() + delay
        return anim
    }

Finally I add the animation to the adequate CAShapeLayer. The code can obviously be optimized but I chose to proceed by these steps to try to find why it was not working properly.

Attempts so far

So far, I have tried:

I suspect something is not working properly with the beginTime but it might not be the case, or only this because even when the shapes animate, the shape animation duration seems to vary whilst it should not.

Thank very much in advance to have a look to this issue. Any thoughts are welcome even if it seems far-fetched it can open to new ways to address this!

Best,

Upvotes: 0

Views: 1089

Answers (1)

E.Coms
E.Coms

Reputation: 11531

Actually there is a relationship between duration and swapColorDuration

func animateColors(for duration: Double,_ animationType: ColorAnimation, colorChangeDuration swapColorDuration: Double)

when you call it, you may need to keep this relationship

    let colorChangeDuration: TimeInterval = 0.5
    animateColors(for: colorChangeDuration * TimeInterval(pLayers.count), .continousWithNewColor(color: UIColor.black), colorChangeDuration: colorChangeDuration)

Also here :

 let numberOfSwaps = Int(animDuration / min(swapDuration, animDuration)) - 1

This value maybe a little higher than you need.

or

The problem lies in this let index = i % (self.pLayers.count)

if numberOfSwaps > self.pLayers.count, some bands will be double animations.

 let numberOfSwaps1 = Int(animDuration / min(swapDuration, animDuration))
 let numberOfSwaps = min(numberOfSwaps1, self.pLayers.count)

in the rest is

       for i in (0..<numberOfSwaps) {... }

Now if numberOfSwaps < self.pLayers.count. It's not finished.

if numberOfSwaps is larger, It is fine.

If double animations are required, changes the following:

pLayers[index].shapeLayer.add(fullAnimation[i], forKey: nil)

or pLayers[index].shapeLayer.add(fullAnimation[i], forKey: "fillColorShape" + String(i))

Upvotes: 1

Related Questions