SDK Examples
Transition
In this tutorial we will build a transition, learn how to insert views into our animation and adapt an animation to different screen sizes.
Setting up the animation
After Effects
When you set up an animation encompassing a full view in After Effects it is good practice to have the most common iOS screen sizes as overlays for reference (e.g. iPhone 5,6,6+).

If you want to access a layer later on in code make sure it has a unique name.

In our example we want to swap out the solids "viewOut" and "viewIn". We also want to access to the null "scaleAnimation" which will apply a scale animation to the incoming view.

Xcode
Our iOS transition is a push transition by a UINavigationController.

To set off the transiton we call pushViewController on our navigation controller. The navigation controller then asks its navigation delegate for an object implementing the UIViewControllerAnimatedTransitioning protocol, our TransitionManager class.

The TransitionManager class gets a UIViewControllerContextTransitioning object passed into the method animateTransition containing all the relevant information for the transition. It is responsible for choreographing the swap.

This is where our animation comes in.

Adapting the transition
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
        
let animationInfo = try! SLReader().parseFileFromBundle("transition.sqa")
       
//before building we replace our solids with the transitioning views
animationInfo.replaceLayerWithName("viewIn", withLayer: toView!.layer, error: nil)
animationInfo.replaceLayerWithName("viewOut", withLayer: fromView!.layer, error: nil)
First we retrieve the transitioning layers from the passed-in transition context. Once we have built our SLReader and built our SLAnimationInformation object we can use its replaceLayerWithName method to replace any After Effects layer with our own.

We swap out our solids with the transitioning views.

animationInfo.filterLayerProperties { (p, n) -> Bool in
            if (n == "viewIn" || n == "viewOut") {
                if (p.name == "Color" || p.name == "Width" || p.name == "Height") {
                    return false;
                }
            }
            return true
        }
We don't want our existing layers to inherit all properties from the solids in After Effets. The filterLayerProperties gets some select properties and their corresponding layer names passed in and gives us a chance to prevent them from being evaluated. In our case we don't want to inherit the size or color from AE.

Try and logg the layer and property names to the console to see which properties you can prevent from being added.

And alternative solution would be to build our animation and reverse any effects the build process will have on our passed-in layers (i.e. set the correct frame and background-color again).

Either approach may require some experimentation.

let animation  = SLCoreAnimation()
animation.buildWithInformation(animationInfo)
animation.rootLayer?.masksToBounds = false;
       
//getting the offset to the screen center
let aeCompositionSize = animation.frame.size
let aeCompositionCenter = CGPointMake(aeCompositionSize.width*0.5, 
                                      aeCompositionSize.height*0.5)
        
let screenCenter = CGPointMake(UIScreen.mainScreen().bounds.size.width*0.5, 
                               UIScreen.mainScreen().bounds.size.height*0.5)
let diffToScreenCenter = CGPointMake(screenCenter.x-aeCompositionCenter.x, 
                                     screenCenter.y-aeCompositionCenter.y);
        
//centering our main animation
animation.position = CGPointMake(animation.position.x+diffToScreenCenter.x,
                                 animation.position.y+diffToScreenCenter.y)
        
//reversing this offset for our passed in layers
//since they are the correct size and fill the screen
fromView?.layer.position = CGPointMake(fromView!.layer.position.x-diffToScreenCenter.x,
                                       fromView!.layer.position.y-diffToScreenCenter.y)
        
toView?.layer.position = CGPointMake(toView!.layer.position.x-diffToScreenMid.x,
                                     toView!.layer.position.y-diffToScreenMid.y)
        
transitionContext.containerView()?.layer.addSublayer(animation)
        
animation.play()
When we call buildWithInformation on our animation Squall evaluates all static, non-animated properties. Now we can make some adjustements to our non-animated layers.

The animation will inherit the frame in points from your After Effects composition. We calculate its center and the offset to the screen's center.

We use this offset and add it to our main composition's position. We then reverse the translation for our fromView and toView since they are already correctly placed.

Note that we are not setting the position directly but are adding offsets to it. This is because it can be tricky to make any assumptions on what the correct position should be and its easier to rely on the position given to the layers after the buildWithInformation method was called.

Layer frames, coordinates and hierarchies in Squall will likely differ from what you expect. It is generally not recommended to try and change any property on an animated layer by direclty manipulating its CALayer, especially transform or path properties.

Properties that are independent in After Effects may be part of a composite property in Squall behind the scenes. The layer position, for example, is one such property. Animating the scale of a layer in AE and then changing its position in code would have side-effects.

The recommended way is to parent your layer to a null and animate the null instead. Our scale animation, for example, is implemented by animating the scaleAnimation null and not our viewIn solid.

Similarly do not make any assumptions about layer frames or bounds.

Now we add our animation to the transition container and call play.

Wrapping up
animation.onAnimationEvent = {[unowned animation] event in
    if event == .End {
        toView?.frame = UIScreen.mainScreen().bounds
        transitionContext.containerView()?.addSubview(toView!)
                
        //calling complete transition right after results in a bad access violation due to the change in layer hierarchy
        callBlockAfter({
            animation.removeFromSuperlayer()
            transitionContext.completeTransition(true)
        }, duration: 0.01)
    }
}
To end the transition we use the onAnimationEvent callback, listen for the end event and then add our inView to the transition container, correct its bounds and tell the transition context that the transition was completed.


Project Files