Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
getaaron opened this issue Aug 31, 2016 · 2 comments

Comments

@getaaron
Copy link

getaaron commented Aug 31, 2016

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
Copy link

This helped me so much. Thank you @getaaron !

@bryanjclark
Copy link

@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 }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants