IFTTT / RazzleDazzle

A simple keyframe-based animation framework for iOS, written in Swift. Perfect for scrolling app intros.
http://ifttt.github.io
MIT License
3.36k stars 291 forks source link

Trigger a normal UIView animation - or play a sound or other action - when a certain page is reached #37

Open getaaron opened 8 years ago

getaaron commented 8 years ago

Instead of animations that are always proportional to the scroll view's contentOffset, we sometimes want a normal UIView animation to be triggered at a certain point.

Here's a basic implementation:

public class TriggeredBlockAnimation: Animatable {
    public typealias TriggeredBlock = () -> ()
    private var blocks = [CGFloat:TriggeredBlock]()

    public func animate(time: CGFloat) {
        guard let block = blocks[time] else { return }
        block()
        blocks[time] = nil // ensures block is only run once ever
    }

    public subscript(time: CGFloat) -> TriggeredBlock? {
        get {
            return blocks[time]
        }
        set {
            blocks[time] = newValue
        }
    }

}

This could be used like so:

let animation = TriggeredBlockAnimation()
animation[320] = {
    constraint.constant = 0 // update new value

    self.contentView.setNeedsUpdateConstraints()

    UIView.animateWithDuration(0.8, delay: 0.8 * Double(index), usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: [], animations: {
        self.contentView.layoutIfNeeded()
        }, completion: nil)
}

animator.addAnimation(animation)

You could also use this approach for triggering other one-time events, like playing a sound, or triggering an analytics event, at a specific offset.

amloelxer commented 7 years ago

This helped me so much. Thank you @getaaron !

bryanjclark commented 5 years ago

@getaaron's code was terrific!

I found myself wanting to control whether-or-not the animation blocks were repeatable, so I made a small modification by adding an allowsRepetition property. If allowsRepetition = false, then the behavior matches @getaaron's implementation.

However, if allowsRepetition = true, then the animation can be replayed. (Of course, this could lead to some goofy behavior, so you'll probably want some animation blocks to "reset" the animation, etc.) While it requires a bit of extra debugging / playtesting, I found that it's really nice for people to be able to replay the animation by scrolling back in the tutorial, rather than only seeing it once!

public class TriggeredBlockAnimation: Animatable {
    public typealias TriggeredBlock = () -> Void
    private var blocks = [CGFloat: TriggeredBlock]()

    private let allowsRepetition: Bool

    init(allowsRepetition: Bool) {
        self.allowsRepetition = allowsRepetition
    }

    private var suppressAnimations: Bool = false
    public func animate(_ time: CGFloat) {
        guard let block = blocks[time], !self.suppressAnimations else { return }
        block()
        if !allowsRepetition {
            blocks[time] = nil // ensures block is only run once ever
        }
    }

    public subscript(time: CGFloat) -> TriggeredBlock? {
        get { return blocks[time] }
        set { blocks[time] = newValue }
    }
}