Stagehand provides a modern, type-safe API for building animations on iOS

Overview

Stagehand

CI Status Version License Platform

Stagehand provides a modern, type-safe API for building animations on iOS. Stagehand is designed around a set of core ideas:

  • Composition of Structures - Stagehand makes it easy to build complex, multi-part animations that are built from small, reusable pieces that are easier to reason about.
  • Separation of Construction and Execution - Stagehand provides separate mechanisms for the construction and execution, which increases the flexibility of animations and makes concepts like queuing a series of animations work straight out of the box.
  • Compile-Time Safety - Stagehand uses modern Swift features to provide a compile-time safe API for defining animations.
  • Testability - Stagehand builds on the concept of snapshot testing to introduce a visual testing paradigm for animations.

Installation

CocoaPods

To install Stagehand via CocoaPods, simply add the following line to your Podfile:

pod 'Stagehand'

To install StagehandTesting, the animation snapshot testing utilities, add the following line to your test target definition in your Podfile:

pod 'StagehandTesting'

By default, this will use Point-Free's SnapshotTesting to record snapshots and perform comparisons. To instead use Uber's iOSSnapshotTestCase as the snapshotting engine, set your test target dependency to use the iOSSnapshotTestCase subspec.

pod 'StagehandTesting/iOSSnapshotTestCase'

Swift Package Manager

To install Stagehand via Swift Package Manager, add the following to your Package.swift:

dependencies: [
    .package(url: "https://github.com/cashapp/stagehand", from: "4.0.0"),
],

Getting Started with Stagehand

An animation begins with the construction of an Animation. An Animation is generic over a type of element and acts as a definition of how that element should be animated.

As an example, we can write an animation that highlights a view by fading its alpha to 0.8 and back:

var highlightAnimation = Animation<UIView>()
highlightAnimation.addKeyframe(for: \.alpha, at: 0, value: 1)
highlightAnimation.addKeyframe(for: \.alpha, at: 0.5, value: 0.8)
highlightAnimation.addKeyframe(for: \.alpha, at: 1, value: 1)

Let's say we've defined a view, which we'll call BinaryView, that has two subviews, leftView and rightView, and we want to highlight each of the subviews in sequence. We can define an animation for our BinaryView with two child animations:

var binaryAnimation = Animation<BinaryView>()
binaryAnimation.addChild(highlightAnimation, for: \.leftView, startingAt: 0, relativeDuration: 0.5)
binaryAnimation.addChild(highlightAnimation, for: \.rightView, startingAt: 0.5, relativeDuration: 0.5)

Once we've set up our view and we're ready to execute our animation, we can call the perform method to start animating:

let view = BinaryView()
// ...

binaryAnimation.perform(on: view)

Running the Demo App

Stagehand ships with a demo app that shows examples of many of the features provided by Stagehand. To run the demo app, open the Example directory and run:

bundle install
bundle exec pod install
open Stagehand.xcworkspace

From here, you can run the demo app and see a variety of examples for how to use the framework. In that workspace, there is also a playground that includes documentation and tutorials for how each feature works.

Contributing

We’re glad you’re interested in Stagehand, and we’d love to see where you take it. Please read our contributing guidelines prior to submitting a Pull Request.

License

Copyright 2020 Square, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Comments
  • Use matrix decomposition for interpolating CGAffineTransforms

    Use matrix decomposition for interpolating CGAffineTransforms

    This switches over to using proper matrix decomposition for interpolating between two CGAffineTransforms.

    This follows the same approach as WebKit's implementation of TransformationMatrix (see the decompose2 function). By fully decomposing the matrices this way, we are better able to interpolate transforms that don't follow the RST format.

    This also significantly improves the performance of interpolating CGAffineTransforms. The base case (identity transform) measured 67% better, and the complex case (RST transform) measured 64% better.

    Best to review by commit:

    • Add performance tests for CGAffineTransform interpolation. Note that these aren't run on CI (for various reasons) but help to check changes during development and with debugging performance issues.
    • Move CGAffineTransform conformance to AnimatableProperty to a separate file. No functional changes here.
    • Add snapshot tests for CGAffineTransform interpolation. These initial snapshot tests show where the old solution broke down.
    • Use matrix decomposition for CGAffineTransform interpolation.
    opened by NickEntin 5
  • Add support for animating CATransform3D

    Add support for animating CATransform3D

    This adds conformance for CATransform3D to AnimatableProperty by decomposing the two matrices into their respective components, interpolating the values in the decomposed transforms, then recomposing the interpolated value into a CATransform3D. This follows the same pattern we use to interpolate CGAffineTransform.

    Resolves #19.

    opened by NickEntin 4
  • Add conveniences for animating collection properties

    Add conveniences for animating collection properties

    Currently there isn't a great way to animate a collection of subelements.

    For example, say your element has a property containing an array of subviews that should each be animated. If the count is known during construction, you can add each one by key path, but you lose the compiler safety by using indices in the key path. The other option here is to use an AnimationGroup, which brings back the compiler safety but loses the separation of construction and execution.

    In order to support building animations of collections for which the number of elements isn't known during construction, we need a new type of content that can be added to the Animation.

    enhancement wontfix 
    opened by NickEntin 3
  • Move source of truth for duration and repeat style to the execution phase

    Move source of truth for duration and repeat style to the execution phase

    • Adds duration and repeatStyle parameters to the Animation.perform(...) and AnimationQueue.enqueue(...) methods to allow for specifying an explicit duration and repeat style at the start of the execution phase.
    • Renames the duration properties on Animation and AnimationGroup to implicitDuration.
    • Renames the repeatStyle properties on Animation and AnimationGroup to implicitRepeatStyle.
    • Renames AnimationRepeatStyle.none to .noRepeat to avoid an ambiguous reference when using an optional repeat style.
    • Updates a lot of headerdocs to better explain how the duration and repeat styles are resolved.

    Resolves #43.

    opened by NickEntin 2
  • Move source of truth for duration and repeat style to the execution phase

    Move source of truth for duration and repeat style to the execution phase

    Stagehand separates the animation process into two phases: construction and execution. The construction phase consists of building up an Animation. The execution phase begins with a call to Animation.perform(...), optionally followed by calls to the returned AnimationInstance to control the animation.

    Setting the animation's duration and repeat style are currently considered to be part of the construction phase and are controlled by the Animation.duration and Animation.repeatStyle properties, respectively.

    This has some interesting characteristics when it comes to composing animations. An animation's duration and repeat style is effectively meaningless when it's added as a child to another animation. This can cause some confusion, since consumers may expect the parent animation to take its children's values into account.

    This also affects the reusability of common animation components, even if they aren't being used as part of a larger animation. For example, consumers could define an animation factory that vends a common fadeOutAnimation that is used whenever they need to fade a view out. It is quite likely that the same duration and repeat style should not be used for all instances of a reusable animation across the app, so each use case may need to set these properties before executing the animation.

    // Most construction happens in the factory.
    var animation = AnimationFactory.fadeOut
    
    // Do some final construction to get it ready.
    animation.duration = 5
    animation.repeatStyle = .repeating(count: 2, autoreversing: true)
    
    // Execute the animation.
    animation.perform(on: view)
    

    Note that this code snippet could be cleaned up by injecting the duration and repeat style into the factory; however, this has the same root issue in that these values are being defined by the code responsible for the execution of a particular instance of this animation, rather than the construction of the common animation.

    To solve this, we can move the source of truth for an animation's duration and repeat style to the execution phase. Specifically, we can add duration and repeatStyle parameters to the Animation.perform(...) method.

    // Construction happens in the factory.
    let animation = AnimationFactory.fadeOut
    
    // Execute the animation with the desired duration and repeat style.
    animation.perform(
        on: view,
        duration: 5,
        repeatStyle: .repeating(count: 2, autoreversing: true)
    )
    

    A similar change would also be made to the AnimationQueue.enqueue(...) method for that execution path.

    animationQueue.enqueue(
        animation: animation,
        duration: 5,
        repeatStyle: .repeating(count: 2, autoreversing: true)
    )
    

    There are also cases, however, where consumers want to promote consistency in usage of a given animation. This is currently well-supported by the duration and repeatStyle properties on Animation. To continue supporting this use case, instead of removing these properties, we can rename them to defaultDuration and defaultRepeatStyle. The duration and repeatStyle parameters of the Animation.perform(...) method will then be made optional, where a value of nil indicates that the animation's default should be used.

    // Construct the animation with a default duration and repeat style.
    animation.defaultDuration = 5
    animation.defaultRepeatStyle = .none
    
    // Execute the animation using the default duration and repeat style.
    animation.perform(on: view)
    
    // Execute the animation with a duration of 2 and the default repeat style.
    animation.perform(on: view, duration: 2)
    

    The same changes will be reflected on AnimationGroup as well: updating the current AnimationGroup.duration and AnimationGroup.repeatStyle properties to the equivalent default-prefixed versions and adding duration and repeatStyle parameters to the AnimationGroup.perform(...) method.

    Alternatives

    This would be a breaking change in the framework. Alternatively, we could leave the Animation.duration and Animation.repeatStyle properties as-is, while still introducing the parameters in Animation.perform(...) to allow overriding the values in the execution phase. This would not be a breaking change, since unchanged code would use the default values. I think the breaking version of this is better for the framework in the long term, however, since using the default-prefixed property names makes it clearer that these do not represent the source of truth for the duration of the final animation instance.

    Related Changes

    Alongside this change, we may also want to update AnimationRepeatStyle.none to .noRepeat. This avoids a warning that appears when using .none with an optional AnimationRepeatStyle:

    Assuming you mean 'Optional<AnimationRepeatStyle>.none'; did you mean 'AnimationRepeatStyle.none' instead?
    
    opened by NickEntin 2
  • Remove the collection keyframes feature

    Remove the collection keyframes feature

    This feature was never included in the public API and still had a lot of work left in order to get it working properly (in particular around interacting with other animation content). No progress has been made on this feature in the last 9 months and AFAIK there is no work planned for this in the near future.

    This removes it from the framework for now to simplify things.

    opened by NickEntin 1
  • Demo app fails to build in Xcode 12

    Demo app fails to build in Xcode 12

    When building the demo app with the Xcode 12 GM, the compiler has trouble resolving the type of expressions:

    Example/Stagehand/CollectionKeyframesViewController.swift:93:27: The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
    Example/Stagehand/CollectionKeyframesViewController.swift:99:27: The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
    Example/Unit Tests/CGAffineTransformInterpolationSnapshotTests.swift:59:9: Type of expression is ambiguous without more context
    
    bug 
    opened by NickEntin 1
  • Improve performance of CGAffineTransform interpolation

    Improve performance of CGAffineTransform interpolation

    Previously, we were creating an array of decomposition candidates and finding the one with the shortest distance. This changes over to modifying the decomposed transform as necessarily using some simpler logic, similar to how WebKit approaches this (see blend2).

    This change resulted in a 48% faster identity-to-identity interpolation and a 44% faster complex-to-complex interpolation.

    By commit:

    • Use quadrant view for CGAffineTransform interpolation snapshot tests. This makes it way easier to tell what's going on in a few of the trickier cases.
    • Improve performance of CGAffineTransform interpolation.
    opened by NickEntin 1
  • Add GitHub Actions for CI builds

    Add GitHub Actions for CI builds

    This moves us to a combination of GitHub Actions and Travis CI for our CI builds. This separation prefers GitHub Actions when available, since it has some advantages (more concurrent builds, artifact upload, etc.), but falls back to Travis CI for iOS versions that aren't easily supported with GitHub Actions.

    opened by NickEntin 1
  • Add common base class for snapshot test classes

    Add common base class for snapshot test classes

    This adds a SnapshotTestCase class that other snapshot test classes can inherit from to get common behavior. In particular, this helps to ensure we use the same file name options across the project. This also adds enforcement that we're running on the set of devices for which we have snapshots.

    opened by NickEntin 1
  • Add animation curve snapshot tests

    Add animation curve snapshot tests

    This adds snapshot tests for a handful of animation curves, with snapshots that show the curve drawn on a grid.

    The code to draw the grid is broken out into a separate utility shared with the sample app.

    opened by lukebradford 1
  • Add property to check whether an animation queue has an in-progress animation

    Add property to check whether an animation queue has an in-progress animation

    It's often useful to know whether an AnimationQueue is currently performing an animation. We should be able to add a hasInProgressAnimation property to AnimationQueue that looks at the queue and checks whether (1) there are any animations in the queue, and if so (2) whether the first animation is still active (i.e. not complete or cancelled).

    enhancement 
    opened by NickEntin 0
  • Improve consistency of terminology in documentation

    Improve consistency of terminology in documentation

    We should be more consistent with the terminology we use in the project - both in the long form documentation and headerdocs, as well as some of the (private) variable names. For example, when talking about durations, the "duration" can apply to different parts of the animation, so I've started differentiating by saying:

    • The time from the start of the execution phase to when the animation completes is the end-to-end duration. This is usually a calculated value based on the cycle duration and the repeat style (when available).
    • The time for the animation to go from a relative timestamp of 0 to 1 (or 1 to 0) is the cycle duration. This is typically how consumers specify the duration.
    • The time for the animation to go between two arbitrary relative timestamps is the segment duration. This is used in a few places like snapshot testing and the upcoming interactive animations.

    See original suggestion in https://github.com/cashapp/stagehand/pull/49#r499112281

    documentation 
    opened by NickEntin 1
  • Interactive Animations

    Interactive Animations

    This adds support for interactive animations.

    Very much still a draft a this point. The exact requirements are still TBD (see #34). This is a proposal for one potential API, to give us something to play around with and see if it works well.

    opened by NickEntin 3
  • Add support for interactive animations

    Add support for interactive animations

    Adding support for interactive animations will greatly increase the variety of use cases where Stagehand can be applied.

    The exact requirements for what this feature should support is still an open question. At this point we're collecting data on what use cases we want to support, so we can design the API around that.

    enhancement 
    opened by NickEntin 1
Releases(4.0.0)
  • 4.0.0(Jan 8, 2021)

    • Changes the minimum supported iOS version to 12.0
    • Adds duration and repeatStyle parameters to the Animation.perform(...) and AnimationQueue.enqueue(...) methods to allow for specifying an explicit duration and repeat style at the start of the execution phase.
    • Renames the duration properties on Animation and AnimationGroup to implicitDuration.
    • Renames the repeatStyle properties on Animation and AnimationGroup to implicitRepeatStyle.
    • Renames AnimationRepeatStyle.none to .noRepeat to avoid an ambiguous reference when using an optional repeat style.
    • Updates a lot of headerdocs to better explain how the duration and repeat styles are resolved.
    • Adds support for using SnapshotTesting as the snapshot engine for StagehandTesting. This is now the default behavior.

    This release changes the default snapshotting engine from iOSSnapshotTestCase to SnapshotTesting. If you already use StagehandTesting and plan on continuing to use iOSSnapshotTestCase as your snapshotting engine, you can change your dependency to be on the iOSSnapshotTestCase subspec instead:

    pod 'StagehandTesting/iOSSnapshotTestCase', '~> 4.0'
    
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0(Sep 18, 2020)

    • Updates CGAffineTransform interpolation to use matrix decomposition. This improves the interpolation of non-RST transforms and greatly improves performance for all transform interpolation.
    • Adds support for animating CATransform3D using matrix decomposition.
    Source code(tar.gz)
    Source code(zip)
  • 2.1.1(May 29, 2020)

  • 2.1.0(Apr 9, 2020)

  • 2.0.4(Mar 18, 2020)

    • Updates handling of execution and per-frame execution blocks in child animations so they behave properly when used in a parent with a curve applied.
    • Delays calling the completion handler until the final frame of the animation is drawn to screen, to fix a bug where the drawing of the final frame would be delayed if the completion handler did a significant amount of work.
    Source code(tar.gz)
    Source code(zip)
  • 2.0.3(Mar 6, 2020)

  • 2.0.2(Feb 22, 2020)

  • 2.0.1(Feb 13, 2020)

  • 2.0(Feb 10, 2020)

Owner
Cash App
Cash App
A Swift library to take the power of UIView.animateWithDuration(_:, animations:...) to a whole new level - layers, springs, chain-able animations and mixing view and layer animations together!

ver 2.0 NB! Breaking changes in 2.0 - due to a lot of requests EasyAnimation does NOT automatically install itself when imported. You need to enable i

Marin Todorov 3k Dec 27, 2022
Type-safe CAAnimation wrapper. It makes preventing to set wrong type values.

TheAnimation TheAnimation is Type-safe CAAnimation wrapper. Introduction For example, if you want to animate backgroundColor with CABasicAnimation, yo

Taiki Suzuki 222 Dec 6, 2022
(Animate CSS) animations for iOS. An easy to use library of iOS animations. As easy to use as an easy thing.

wobbly See Wobbly in action (examples) Add a drop of honey ?? to your project wobbly has a bunch of cool, fun, and easy to use iOS animations for you

Sagaya Abdulhafeez 150 Dec 23, 2021
(Animate CSS) animations for iOS. An easy to use library of iOS animations. As easy to use as an easy thing.

wobbly See Wobbly in action (examples) Add a drop of honey ?? to your project wobbly has a bunch of cool, fun, and easy to use iOS animations for you

Sagaya Abdulhafeez 150 Dec 23, 2021
A collection of animations for iOS. Simple, just add water animations.

DCAnimationKit A collection of animations for iOS Simply, just add water! DCAnimationKit is a category on UIView to make animations easy to perform. E

Dalton 797 Sep 23, 2022
Simple Interface Core Animation. Run type-safe animation sequencially or parallelly

Simple Interface Core Animation Sica can execute various animations sequentially or parallelly. Features Animation with duration and delay parallel /

CATS Open Source Softwares 1k Nov 10, 2022
MapTeam - A type-safe, Swift-language layer over SQLite3

SQLite.swift A type-safe, Swift-language layer over SQLite3. SQLite.swift provid

Théotime 0 Jan 18, 2022
A library for building an internal/development support app easily

Scenarios A library supporting fast prototyping for iOS Projects. Introduction Challenges of mobile frontend development Stories with multiple require

An Tran 29 Sep 15, 2022
Physics-based animations for iOS, tvOS, and macOS.

Advance An animation library for iOS, tvOS, and macOS that uses physics-based animations (including springs) to power interactions that move and respo

Tim Donnelly 4.5k Dec 29, 2022
An iOS library to natively render After Effects vector animations

Lottie for iOS, macOS (and Android and React Native) View documentation, FAQ, help, examples, and more at airbnb.io/lottie Lottie is a mobile library

Airbnb 23.6k Dec 31, 2022
A library to simplify iOS animations in Swift.

Updated for Swift 4.2 Requires Xcode 10 and Swift 4.2. Installation Drop in the Spring folder to your Xcode project (make sure to enable "Copy items i

Meng To 14k Jan 3, 2023
Sample way of integrating animations into a design system for iOS app projects.

Animations in Design System The project presents a sample way of integrating animations into a design system for iOS developers. Project setup A sampl

Bulat Khabirov 1 Nov 26, 2021
Easily build advanced custom animations on iOS.

INTUAnimationEngine makes it easy to build advanced custom animations on iOS. INTUAnimationEngine provides a friendly interface to drive custom animat

Intuit 1.1k Dec 14, 2022
A library of custom iOS View Controller Animations and Interactions.

RZTransitions is a library to help make iOS7 custom View Controller transitions slick and simple. Installation CocoaPods (Recommended) Add the followi

Rightpoint 1.9k Nov 20, 2022
MotionBlur allows you to add motion blur effect to iOS animations.

MotionBlur MotionBlur allows you to add motion blur effect to your animations (currently only position's change). See the accompanying blog post to le

Arek Holko 1.5k Nov 3, 2022
Wave is a spring-based animation engine for iOS that makes it easy to create fluid, interruptible animations that feel great.

Wave is a spring-based animation engine for iOS and iPadOS. It makes it easy to create fluid, interactive, and interruptible animations that feel great.

Janum Trivedi 1.2k Jan 8, 2023
This repo contains swift collection of gui, games, menu, animations, music, payment, etc... for iOS, macOS, watchOS and tvOS

Swift-Collections About: This repo contains a collection of projects built using swift and objective-c Contains projects for macOS iOS iPad watchOS tv

Krisna Pranav 6 Nov 15, 2022
A library for fancy iOS animations that you will definitely love.

EazelAnimationsKit Table of Contents Introduction Animations Usage Installation Contribution Authors Introduction The drive for developing this animat

Eazel 7 Dec 13, 2022
iOS framework for impressive transition animations between views.

CoreTransition iOS framework for impressive transition animations between views. Built using Swift, and supports a lot of animations to navigate to a

Ahmed Abdelkarim 4 Nov 17, 2022