Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CABasicAnimation delay between repeat

The code below creates a "shimmer" effect and works great. (The look of reflected light moving across a glossy surface.) It's currently set to repeat forever which is fine, however, I'd like there to be a pause between each shimmer. I can delay the start of the effect, but I can't seem to figure out how to delay each run. Bonus points if the delay time is random.

func startShimmering() {

    let light = UIColor.white.cgColor
    let alpha = UIColor.white.withAlphaComponent(0.0).cgColor

    let gradient = CAGradientLayer()
    gradient.colors = [alpha, light, alpha]
    gradient.frame = CGRect(x: -self.bounds.size.width, y: 0, width: 3 * self.bounds.size.width, height: self.bounds.size.height)
    gradient.startPoint = CGPoint(x: 1.0, y: 0.525)
    gradient.endPoint = CGPoint(x: 0.0, y: 0.5)
    gradient.locations = [0.1, 0.5, 0.9]
    self.layer.mask = gradient

    let animation = CABasicAnimation(keyPath: "locations")
    animation.fromValue = [0.0, 0.1, 0.2]
    animation.toValue = [0.8, 0.9, 1.0]
    animation.duration = 1.5
    animation.repeatCount = HUGE
    gradient.add(animation, forKey: "shimmer")
}
like image 690
squarehippo10 Avatar asked Sep 08 '17 19:09

squarehippo10


2 Answers

As mentioned in Kevin's answer, you can use a CAAnimationGroup to add a delay to the end of your shimmer animation.

The "shimmer" animation will have a duration longer than the group's animation. Any difference between these two durations will be the delay between animations.

Here is the code to do so, ready for a Swift Playground:

class ShimmerView: UIView {

    func startShimmering() {

        let light = UIColor.white.cgColor
        let alpha = UIColor.white.withAlphaComponent(0.0).cgColor

        let gradient = CAGradientLayer()
        gradient.colors = [alpha, light, alpha]
        gradient.frame = CGRect(x: -self.bounds.size.width, y: 0, width: 3 * self.bounds.size.width, height: self.bounds.size.height)
        gradient.startPoint = CGPoint(x: 1.0, y: 0.525)
        gradient.endPoint = CGPoint(x: 0.0, y: 0.5)
        gradient.locations = [0.1, 0.5, 0.9]
        self.layer.mask = gradient

        let shimmer = CABasicAnimation(keyPath: "locations")
        shimmer.fromValue = [0.0, 0.1, 0.2]
        shimmer.toValue = [0.8, 0.9, 1.0]
        shimmer.duration = 1.5
        shimmer.fillMode = kCAFillModeForwards
        shimmer.isRemovedOnCompletion = false

        let group = CAAnimationGroup()
        group.animations = [shimmer]
        group.duration = 2
        group.repeatCount = HUGE
        gradient.add(group, forKey: "shimmer")

    }

}

let view = ShimmerView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
PlaygroundPage.current.liveView = view

view.backgroundColor = .blue
view.startShimmering()

Here is the end result:

Shimmer gif

(The gif animates the shimmer three times, which is why every third "shimmer" the delay is shorter.)

like image 183
nathangitter Avatar answered Oct 08 '22 11:10

nathangitter


I haven't tried this myself but I believe you can wrap your animation in an animation group, set the group's duration to longer than the real animation's duration, and then set the group to repeat.

If that doesn't work, you could use a keypath animation instead, where most of the keypath is a reproduction of the normal animation, and the rest of the keypath just keeps the animation value pinned to either the start or end value.

If you want a random delay, your only choice is to actually run a timer (or use a CAAnimationDelegate) to set up a new animation each time.

like image 5
Lily Ballard Avatar answered Oct 08 '22 12:10

Lily Ballard