iOS layer animation - explanation - ios

let flyRight = CABasicAnimation(keyPath: "position.x")
flyRight.toValue = view.bounds.size.width/2
flyRight.duration = 0.5
flyRight.fromValue = -view.bounds.size.width/2
flyRight.fillMode = kCAFillModeBoth
flyRight.beginTime = CACurrentMediaTime() + 3.4
password.layer.position.x = view.bounds.size.width/2
password.layer.addAnimation(flyRight, forKey: nil)
My question is: how is it possible that before I am adding the animation, I first put the layer at the end position, but when the app goes up, I don't see it there before the animation starts? Instead, the animation works after 3.4 seconds as expected.
I mean, how iOS knows that first it needs to run the animation and only then put the layer to it's final position?

This is because there is a presentationLayer and a modelLayer so animation show and modify presentationLayer, so presentationLayer is like a ghost.
You probably know that UIView instances, as well as layer-backed NSViews, modify their layer to delegate rendering to the powerful Core
Animation framework. However, it is important to understand that
animations, when added to a layer, don’t modify its properties
directly.
Instead, Core Animation maintains two parallel layer hierarchies: the model layer tree and the presentation layer tree.
Layers in the former reflect the well-known state of the layers, wheres only layers in the latter approximate the in-flight values of
animations.
Consider adding a fade-out animation to a view. If you, at any point during the animation, inspect the layer’s opacity value, you
most likely won’t get an opacity that corresponds to what is onscreen.
Instead, you need to inspect the presentation layer to get the correct
result.
While you may not set properties of the presentation layer directly, it can be useful to use its current values to create new
animations or to interact with layers while an animation is taking
place.
By using -[CALayer presentationLayer] and -[CALayer modelLayer], you can switch between the two layer hierarchies with ease.
you can find more info here https://www.objc.io/issues/12-animations/animations-explained/
I hope this helps you

It has to do with the way UI changes get applied. When you make a change to a UI object, that change is not rendered to the screen until your code returns and the event loop runs.
When that happens, the system moves your view to it's final position, but then the ACTUAL view/layer hierarchy gets covered with a new layer that's drawn on top, known as the presentation layer. The presentation layer is where the pixels from the animation show up on the screen. That's what you see.
When the animation finishes, the presentation layer is hidden/removed (not honestly sure which) and the actual layer contents are shown. At that point the layer is in it's final position, so everything looks correct.

Related

Swift 2.0 understanding how animation with duration works in animation closure

UIView.animationWithDuration is easy to use:
UIView.animateWithDuration(1.0) {
self.someView.alpha = 1.0
}
How does the function update the variables in the closure to animate instead of just jumping in this case to alpha = 1.0 on the first iteration?
This works because of the special design of CALayer. Every CALayer has two conceptual layers, both exposed as properties. There is the modelLayer which is what you access and modify through properties like someView.alpha and generally holds the state of the layer. The model layer updates immediately when you modify related properties, whether or not you're inside a UIView animation block. Then there is also the presentationLayer which is actually what gets rendered by the system. The presentation layer usually mirrors the model layer, except when animating. I don't know the specifics, but UIView.animateWithDuration does something to tell CoreAnimation, "when I run this animation block, record the changes in the model layer (diff before and after), and then apply them incrementally to the presentation layer over the given duration".
This is why someView.alpha is immediately updated despite the animation not having finished, because your code is interacting with the model layer. If you access the presentation layer, you will see that the updated value is applied incrementally.

CALayer rotation appears to 'lift' it's child above the surface

Here is the iPad Simulator with four nested UIViews, drawing a custom background, and an inner UILabel. I am rotating the top UIView's CALayer, by getting it's layer and setting transform with a rotateY CATransform3D, animated on a separate thread (but the changes to transform are being sent on the main thread, naturally):
Note- this animation does not loop correctly, hence it appears to bounce.
The layers do animate as a whole, but curiously, the first child and it's descendants appear to be floating above the UIView with the transform applied!
The UIViews themselves are children in another UIView, which has a red background. There are no other transformations being applied anywhere else.
The positions for each UIView were set using setFrame initially at the start.
What is causing this strange behaviour, and how can I ensure the child UIViews transform with their parent, giving a flat appearance to the surface as a whole?
Well. Perhaps unsurprisingly I was doing something silly, but since I'd not used CALayer transforms before, I didn't know if things were acting up. I had overridden layoutSubviews on the UIViews I was creating, and the act of rotating the CALayer was triggering this call and then pushing the child components frame around, due to a bug.
The problem is that CALayers don't actually do 3D perspective by default. In order to do that you need to make a minor change to the layer's transform (which is of type CATransform3D)
You want to change the .m34 field of the transform to a small negative value. Try -1/200 to -1/500 as a starting range. If I remember correctly it should be the negative of 1 over the image height/width.
Change the .m34 property of the layer that you want to appear to "come off the page" and rotate in 3D. When you do that the Z setting of the layer does matter, and will both make closer layers bigger and also make layers that are further away disappear behind other things.
I suggest you do a Google search on "CATransform3D m34" for more information. There is a fair amount of information on the net about it.

CABasicAnimation does not animate correctly when I update model layer

I'm currently implementing a CABasicAnimation that animates a CALayer transform property.
Now, although I'm new to Core Animation, I have been able to gather through various blogs and articles such as objc.io that it is a very bad idea to use the often (incorrectly) recommended method for getting animations to stick using the fillMode and removedOnCompletion properties of an animation. This method is considered bad practice by many because it creates a discrepancy between the model layer and the presentation layer, and so future queries to one of those layers may not match up with what the user is seeing.
Instead, the recommended way of doing animations is to update the model layer at the same time you add the animation to the layer being animated. However, I'm having trouble understanding exactly how this is working. My animation is simple, and goes like this:
CATransform3D updatedTransform = [self newTransformWithCurrentTransform];
// Location 1
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
transformAnimation.duration = 1;
transformAnimation.fromValue = [NSValue valueWithCATransform3D:self.layerBeingAnimated.transform]; // Does not work without this.
transformAnimation.toValue = [NSValue valueWithCATransform3D:updatedTransform];
// Location 2
[self.layerBeingAnimated addAnimation:transformAnimation forKey:kTransformAnimationKey];
// Location 3
I've denoted three locations where I have attempted to update the model layer using the code
self.layerBeingAnimated.transform = updatedTransform;
In location 1, the layer jumps right to newTransform and does not animate.
In location 2, the layer animates exactly as I want it to from the current transform to newTransform.
In location 3, the layer jumps right to newTransform, jumps back to the the old transform, animates correctly from the fromValue to newTransform, and then stays at newTransform.
What's the deal here? What's the correct location to update the model layer and why are these three locations producing such different results?
Thanks!
I think that it's easiest to explain what is happening for each of the three locations and then a "conclusion" at the end.
I'm also adding some illustrations, showing exactly the behaviour that you are mentioning in your question so that it will be easier to follow for someone who hasn't tried these three things themselves. I'm also extending the illustration to show both a stand alone layer and a backing layer (one that is attached to a view) and I will explain the difference where there is one.
Location 1
In the first location, the model value is updated before the animation is created. Once this is done, the transform property holds the updatedTransform. This means that when you read the transform from the layer for the fromValue, you get the updatedValue back. This in turn means that both to and from value are the same so you can't see the animation.
One thing that could have made this location work as expected is to read the oldValue before assigning the new value and then use that as the fromValue. This will look as expected.
// Location 1
CATransform3D oldValue = layer.transform; // read the old value first
layer.transform = updatedTransform; // then update to the new value
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform"];
anim.duration = 1.0;
anim.fromValue = [NSValue valueWithCATransform3D:oldValue];
anim.toValue = [NSValue valueWithCATransform3D:updatedTransform];
Location 2
In the second example, the value isn't yet updated when you read the transform for the from value, so the fromValue and toValue are different. After that, the model value is updated to it's final value. There is actually a difference between the stand-alone layer and the backing layer here, but we don't see it. The transform property on CALayer is animatable and will automatically perform an "implicit" animation when the value changes. This means that an animation will be added to the layer for the "transform" key path. The view, however, disables this behaviour when the change happens outside of an animation block, so there is not implicit animation there.
The reason why we don't see the implicit animation is that the "explicit" animation is added afterwards for the same key path. This means that the only explicit animation will be visible, in both cases, even thought there are two animation running on the stand-alone layer (more on that later). If you are feeling cautious, then you could disable the implicit action for the stand-alone layer (more on that later).
Location 3
This leaves us with the last location. In this case the animation is created just as above, with different fromValue and toValue. The only difference is the order of adding the explicit animation and changing the property which triggers an implicit animation. In this case the implicit animation is added after the explicit animation and they both run(!). Both animations actually ran for location 2, but we couldn't see it because the explicit (longer) animation was added before.
Since everything is moving so fast, I slowed down the entire layer to try and illustrate what is happening when two animations are running at once. This way it becomes much easier to see what happens when the implicit animation ends. I've overlaid the well behaving backing layer and the misbehaving stand-alone layer and made them both 50% transparent. The dashed outline is the original frame.
A short description of what is happening: the blue view get's only the explicit animation added to it (which has a 1 second duration). The orange layer first has the same explicit animation added to it and then has an implicit 0.25 second animation added to it. Neither explicit nor the implicit animations are "additive", meaning their toValue and fromValue are used as-is.
Disclaimer: I do not work at Apple and I haven't seen the source code for Core Animation so what I'm about to say is guesswork based on how things behave.
In my understanding (see disclaimer) this is what happens for every screen refresh to produce the animation: for the current time stamp, the layer goes through the animations in the order they were added and updates the presentation values. In this case, the explicit animation sets a rotation transform, then the implicit animation comes and sets another rotation transform that completely overrides the explicit transform.
If an animation is configured to be "additive", it will add to the presentation values instead of overwriting (which is super powerful). Even with additive animations, order still matters. A non-additive animation could come later and overwrite the whole thing.
Since the implicit animation is shorter than the explicit one, we see that for the first part of the total animation, the values are strictly coming from the implicit animation (which was added last). Once the implicit animation finishes, the only remaining animation is the explicit animation which has been running underneath the implicit one, all this time. So when the implicit animation finishes, the explicit animation has already progressed 0.25 seconds and we see that the orange layer jumps back to the same value as the blue view, instead of jumping back to the beginning.
Where should we update the value?
At this point, the question is, how can we prevent two animations from being added and where should we update the value? The location where the value is updated doesn't prevent there from being two animations (but it can affect how the end result looks).
To prevent two actions from being added to the stand-alone layer, we temporarily disable all "actions" (a more general term for an animation):
[CATransaction begin];
[CATransaction setDisableActions:YES]; // actions are disabled for now
layer.transform = updatedTransform;
[CATransaction commit]; // until here
When we do this, only one animation is added to the layer so either location 2 or 3 works. That is simply a matter of taste. If you read the oldValue, the you can also use location 1 (as long as the action is disabled).
If you are animating a backing layer then you don't have to disable actions (the view does that for you) but it also doesn't hurt to do so.
At this point I could keep going about other ways to configure an animation, what an additive animation is, and why you needed to specify both the toValue and fromValue in this case. But I think that I have answered the question you asked and that this answer already is a bit on the long side.

How to capture contents of a CALayer that has a CAAnimation applied to it?

I'm interested in recording a sequence of CoreAnimations into a video file. For this I've built a system that renders out the current state of the CALayer 30 times a second and composites this into a video. However, these individual frame renders do not include the CoreAnimation changes.
Current method of capturing a frame:
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
In the Core Animation docs it reads:
Animatable properties can also be explicitly animated. To explicitly
animate a property you create an instance of one of Core Animation’s
animation classes and specify the required visual effects. An explicit
animation doesn’t change the value of the property in the layer, it
simply animates it in the display.
Do you know where/how to intercept the actual appearance of the layer, after the CoreAnimation has been applied ?
Pretty sure you can call -presentationLayer to get a layer that roughly corresponds to the currently displayed version of your layer... There are restrictions, check in CALayer.h.
If you aren't able to capture the output at the full 30 frames, you may be able to pause your presentations during capture and then "single step" them, by manipulating the speed and timeOffset properties of your root layer.

Why does CALayer not maintain its contents?

I'm using CALayers to display a couple images. I do so by creating the layer and setting its contents property to a CGImageRef. I do not set a delegate on my CALayer.
The layer displays fine, but when another layer moves on top of the first layer, the lower layer's contents are "erased." I'm assuming the CALayer is calling the default delegate and drawing nothing. How do I make my CALayer persist its contents?
Thanks.
The lower layer should not be erased by adding a new layer on top. My guess is that the lower layer is being covered (and thus obscured) by the layer you've added. Try making the new layer smaller than the original layer as a test.
Note that if you call certain methods like setNeedsDisplay on a layer, it WILL cause the layer to discard it's contents.
Do you have any code that might be forcing the layer to redraw? (Like calling setNeedsDisplay, as mentioned above.) That would cause the symptom you are seeing.

Resources