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.
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.
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.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.
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.
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.