Chain multiple UIView animations without endless closure nesting

What's New



Swift 5.8 compatibility: ⚠️ Possibly breaking changes

Swift 5.8 changed some type-checking logic around result builders, complicating AnimationPlanner’s ‘clever’ logic of allowing some type of animations for both group and sequence animations. Now the following has been updated

  • Sequence and group animation builders now use their own result builder (@SequenceBuilder and @GroupBuilder respectively)
  • The Loop convenience struct and its static method Loop.through() have been removed, with a build error suggesting other approaches
  • The Swift.Sequence extension function mapAnimations() is now replaced with mapSequence() and mapGroup(). While not as clearly named, the type-checker now clearly knows what to do.
  • Usage for for and for _ in loops is more encouraged, as the result builder is perfectly capable to handle these.

What's Changed

Full Changelog: 1.0.0...v1.1.0

Animation Planner logo


⛓ Chain multiple UIView animations without endless closure nesting. Create your animation sequence all on the same indentation level using a clear, concise syntax.

🤹 Used for all exuberant animations in OK Video 📲

📖 Check out the documentation to get up to speed, or read on to see a little example.

How do I plan my animations?

📦 Add AnimationPlanner to your project (using Swift Package manager) and start typing AnimationPlanner.plan to embark on your animation journey. Like what‘s happening in the code below.

AnimationPlanner.plan {
    Animate(duration: 0.32, timingFunction: .quintOut) {
        view.alpha = 1 = self.view.bounds.midY
    Animate(duration: 0.32) {
        view.transform = CGAffineTransform(scaleX: 2, y: 2)
        view.layer.cornerRadius = 40
        view.backgroundColor = .systemRed
    AnimateSpring(duration: 0.25, dampingRatio: 0.52) {
        view.backgroundColor = .systemBlue
        view.layer.cornerRadius = 0
        view.transform = .identity
    Animate(duration: 0.2) {
        view.alpha = 0
        view.transform = .identity
        view.frame.origin.y = self.view.bounds.maxY
}.onComplete { finished in

The above code results in the following animation sequence. For more examples see the Sample App available when cloning the repo.

Note: The example uses custom extension methods on CAMediaTimingFunction, included with the framework


🛠 Adding AnimationPlanner as a package dependency

  1. Go to File -> Add Packages
  2. Paste in the search bar and click on "Add Package"
  3. Select the target(s) in which you want to use AnimationPlanner

📦 Swift Package Manager

Manually add AnimationPlanner as a package dependency in package.swift, by updating your package definition with:

  dependencies: [
    .package(name: "AnimationPlanner", url: "", .branch("main"))

And updating your target‘s dependencies property with dependencies: ["AnimationPlanner"]

🔮 Future plans

While this API removes a lot of unwanted nesting in completion closures when using traditional UIView.animate... calls, a project is never finished and for future versions I have the following plans:

  • Remove usage of inaccurate DispatchQueue.main.asyncAfter, currently used to add delays for non-UIView animations or bridging gaps between steps.
  • Maybe even allow this package to play more nicely with SwiftUI? No idea what that would look like though, any ideas?

Got any feedback or suggestions? Please let me know! ✌🏻


  • Swift Tools 5.5.0
View More Packages from this Author


  • None
Last updated: Fri Nov 24 2023 04:21:19 GMT-1000 (Hawaii-Aleutian Standard Time)