CAAnimations, and updating the layer tree - iphone

This apple doc https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreAnimation_guide/CreatingBasicAnimations/CreatingBasicAnimations.html#//apple_ref/doc/uid/TP40004514-CH3-SW3
shows how you to use an explicit animation to animate some property, and then to immediately set that property on the layer so that when the explicit animation is done, the final value of the property is correct.
However, setting a property directly on a layer also causes an implicit animation. I feel like I am seeing a bug in my program to this effect, where both the implicit and explicit animations are running.
Can somebody explain how this works? In the sample code, is the implicit animation ignored when you set up an explicit one?
--- update ---
So the problem I am seeing specifically is i have an animation to change the position of a layer. It looks basically identical to the apple sample code
CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:#"position"];
anim.fromValue = startValue;
anim.toValue = endValue;
anim.duration = 1.0;
[theLayer addAnimation:anim forKey:#"myAnimation"];
// Change the actual data value in the layer to the final value.
position.position = endValue; // As a CGPoint
The glitch I am observing is first I see the position change fast (which I'm guessing is the implicit animation), then it goes slow (which is my animation).
One thing I just noticed that is different in my code is that the ID I give my explicit animation is "myAnimation", not "position". But that begs another question, if you give both animations the same ID ("opacity" in the apple sample code), and the implicit animation is set up second, why doesn't that one win out?

In the off change somebody else encounters this, my problem was that two animations were being added to my layer. They were both animating the position property, but they had different IDs so both were running.
I solved my problem by wrapping the implicit animation in a transaction that turned off actions.
[CATransaction begin];
[CATransaction setDisableActions:YES];
theLayer.position = endValue;
[CATransaction commit];

Related

Strange behaviour in UIView Animation

I am facing a strange behavior in UIView animation. I am developing an iPad application which is using some UIView animations.
The duration of all animation is set to 0.5. Initially on launching the application all animations are working fine. But after some continuous use no animation is happening, all UIView changes are happening quickly, just like the duration is not set in animation.
I am not sure why this is happening. Has anyone else faced this kind of issue?
Following is one of the animation I am using. Like this I am using a lot of animations but after some time none of the animation is happening but all the codes inside the animation block is working fine
[UIView animateWithDuration:0.5 animations:^{
[tempLabel setFont:titleFont.font];
[tempLabel setTransform: CGAffineTransformMakeRotation((-90 * M_PI / 180))];
tempLabel.frame = CGRectMake(2,0,23,30);
}];
From the UIView class reference, the frame property:
Changes to this property can be animated. However, if the transform property contains a non-identity transform, the value of the frame property is undefined and should not be modified. In that case, you can reposition the view using the center property and adjust the size using the bounds property instead.
Don't animate the frame when you've also set a transform. Use bounds and center instead.
The problem you have seems that you are transforming the control every time, when you apply a transformation for the first time, there is no transformation on control, but next you have a transform, in this case the next transformation may not work as you want.. You have few options to solve this as follows, I haven't tried the code it might work
tempLabel.transform = CGAffineTransformIdentity;
Put the above code before applying any transformation this will reset any transformation applied and your new transformation will be freshly applied or, you can do
tempLabel.transform = CGAffineTransformConcat(tempLabel.transform, YOUR TRANSFORM);
The above will append to your last transformation..
Hope it helps.
Thanks.

struggle to understand toValue, byValue of CABasicAnimation, ios

I am learning a different way to create a custom indicator. Below is a partial code from tutorial using CABasicAnimation to achieve a task.
-(void)spin
{
CABasicAnimation *spinAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
spinAnimation.toValue = [NSNumber numberWithFloat:2*M_PI];
spinAnimation.duration = self.animationDuration;
spinAnimation.delegate = self;
[self.layer addAnimation:spinAnimation forKey:#"spinAnimation"];
}
What is the toValue at line number 2 and what it is used for. When I tried to use
spinAnimation.byValue = [NSNumber numberWithFloat:2*M_PI];
I dont get the idea of these interpolation values.Was searching over the internet but cant still get the whole picture of it..
Please help if you have any ideas about it. All comments are appreciated.
CABasicAnimations can be a little tough to wrap your head around, but the properties associated with the animation really aren't that tough, once you can visualize what they're trying to accomplish. For instance, if I have a red square that represents a layer, and I want to rotate it 360˚ (as you're doing there), then I have to initialize an animation object, tell it what I want to animate, and where I want the animation to go.
The animation you've provided mutates a CALayer's internal matrix so that it is rotated to a given value (in this case, 2 * M_PI, or 360˚) from it's current position (because you haven't specified a fromValue) over the given duration. A given by value tells the animation that over the given period of time, you want the animation to interpolate (or move) by the given value for the provided duration (for instance, you could chunk the animation into 45˚ "blocks" by specifying a byValue of #(M_PI/2)). The default byValue is a division of the difference of the toValue and fromValue over the duration of the animation such that the animation is smooth and continuous.
So, you can think about the animation as going from the layer's initial rotational value, to the layer's new rotational value, by interpolating a given amount or value for a period of time.
You can comprehend "byValue" its means plus the value in original.

CAKeyFrameAnimation not repeat

Hi I have a CAKeyFrameAnimation scale, that makes an object fade down from full size (1) to near nothing (0.01)
Then this is called:
- (void)animationDidStop:(CAKeyframeAnimation *)anim finished:(BOOL)flag
{
[self setHidden:YES];
}
It seems to hide the object, but not before making it re-appear again as full sized for a split second, which ruins the fade to small/nothing effect :P
How do I stop the animation from going back to frame 1 for the "animationDidStop" is called? Thanks!
I believe you need to set the fillMode property of your animations to kCAFillModeForwards. That should freeze the animations at their end time.
Another suggestion (and honestly, this is what I'd usually do) is just set the properties of the layer itself to their final position after you've set up the animation. That way when the animation is removed, the layer will still have the final properties as part of its model.
You can set .scale object property instead of CAKeyframeAnimation.
You can disable animations and set target scale after creating CAKeyframeAnimation.
You can set scale (0.1) in animationDidStop method (do not sure if it will work)
The point is to set target properties of the object. It jumps to them after finishing the animation.

iPhone animation based on input values (touches) not time

For an animation effect perfectly suited to an animation group approach as shown in Brad Larson's answer here, I need the animation to proceed according to inputs. Specifically touch and position of detected touches. It is easy to handle touchesMoved: and to set the position of the elements for every touch but it just isn't smooth like the core animation approach.
Imagine a marble in a grooved track. I want to push the marble along to any position at any speed in one direction or the other. The animation has to do something like that, moving a visual element along a path in response to touches. CAKeyframeAnimation has the path bit exactly but seems to always want to base the transition from frame to frame on time elapsed, not on any other factor, and in one direction.
31 January update - Thanks all for the responses so far however none is really solving the problem. I have a circular menu that is dragged to select an option. All of it needs to move together and I have worked around it by using an view that has a rotational transform applied and the inverse rotational transform applied to its subviews so the icons all rotate with appropriate ferris wheel orientation. It really looks better when the icons are animated along a slightly ovoid path though... the marble description is an attempt to make clear what I'm trying to do. Better perhaps to imagine magnets oriented to repel all travelling in a groove - move one and its neighbours move too but not necessarily in the direction that the dragged magnet moves as the path curves.
Right now the problem is one of following a simple path created with one circle but I'd really like to know how to animate objects along an arbitrary path, position controlled purely by touch with no calculations involving velocity or direction.
You may be able to use the hierarchical nature of timelines in layer trees to achieve what you’re looking for. Objects implementing CAMediaTiming, which include both CAAnimation and CALayer, inherit a timespace from their parent, which they can modify (via scaling, shifting and repeating) and propagate to their children. By setting the speed property of a layer to 0.0 and adjusting the timeOffset property manually, you can decouple that layer (and all of its sublayers) from the usual notion of time.
What you’d do in your case is define the animations for all your menu items to animate their position along your desired CGPath from time t0 to t1, with the appropriate timeOffset on each animation to keep your items appropriately spaced. Note that a beginTime of 0.0 on an animation is usually interpreted as starting from the time the animation was added to its layer, so if you want t0 to be 0.0, you'll probably have to set it to a tiny epsilon > 0.0.
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.beginTime = 1e-100;
animation.duration = 1.0;
animation.fillMode = kCAFillModeBoth;
animation.removedOnCompletion = NO;
animation.path = path;
animation.calculationMode = kCAAnimationPaced;
animation.timeOffset = timeOffset;
You’d then set the speed property to 0.0 on a parent layer (containing only these menu items) and update its timeOffset to values between t0 and t1 in response to your touch events.
This approach has two potential caveats. Because you’ve taken over the nature of time on this layer subtree, you probably won’t be able to animate other properties at the same time. Additionally, if you want coasting behavior for a fast flick, you’ll probably need to animate time forward on your own.
If you are targeting iOS 4.0 or greater then you can use the new block based class methods to start animatable changes to view objects. So from your touch event you can initiate an animatable change to properties on the view:
[UIView animateWithDuration:1.0 animations:^{
yourView.alpha = 0.0; // fade out yourView over 1 second
}];
N.B. This could just as easily be a change to another property on the view, like its location. You can animate the following properties on the view this way:
#property frame
#property bounds
#property center
#property transform
#property alpha
#property backgroundColor
#property contentStretch
If you are targetting earlier versions of iOS you will need to use UIView beginAnimations and commitAnimations methods to create an animation block:
[UIView beginAnimations:nil context:context];
[UIView setAnimationDuration:1.0];
yourView.alpha = 0.0;
[UIView commitAnimations];
This stuff works really well and once you start using it, you will have to be careful you don't get addicted. ;)
Update for comment:
You can bind the position of the marble to the location of the touch event. Once you get the touchesEnded event you can then animate the location of the marble using an animation block.
velocity = distance/time. You can try by giving delay according to touches moved and time. You can calculate time between touchesBegan and touchesEnded methods.
I'm not entirely certain I understand exactly what you want to do but... Why not just draw the marble wherever the user's finger is? No animation required.
If you want to keep the marble moving after the user lets go, you need to maintain a concept of momentum and use that to animate the marble slowing down. You could use either CoreAnimation for that or a timer.
I would make a velocity calculation in touches moved, and add it to a local variable that a block can mutate via a timer.
in other words, in touches moved make a velocity calculation bearing in mind the direction of a previous velocity. In touches ended, fire a block that translates the 'marble' decaying the velocity as you disire. If the user moves the marble again, modify the local variable, this will in turn speed up the animation of the marble or slow it down/change direction depending on on the direction of the touch.

How to let a CAKeyframeAnimation pick up the current state when overriding another?

I have one like this:
CALayer *layer = stripeButton.layer;
CAKeyframeAnimation *moveAnim = [CAKeyframeAnimation animationWithKeyPath:#"bounds.origin.y"];
moveAnim.duration = 3.35;
moveAnim.repeatCount = 0;
moveAnim.autoreverses = NO;
moveAnim.removedOnCompletion = NO;
moveAnim.calculationMode = kCAAnimationLinear;
moveAnim.fillMode = kCAFillModeForwards;
// ... keys and values here ...
[theLayer addAnimation:moveAnim forKey:#"theAnimation"];
You see this animation goes 3.35 seconds. In the meantime, it can happen that at i.e. 2 seconds a new animation with the same key #"theAnimation" is kicked off, for different values.
Problem what's happening: New animation does not pick up the current visible state. It begins hard from scratch. The view jumps to start position in a ugly manner and from there, the new animation begins. For UIView there is an setAnimationsBeginFromCurrentState=YES, but I haven't found anything like that for CAKeyFrameAnimation. Any idea?
You want to query the layer for its presentation layer, which is its current interpolated state including any animations currently running.
-
Excerpt from the CALayer Class Reference:
-(id)presentationLayer
Returns a copy of the layer containing all properties as they were at the start of the current transaction, with any active animations applied.
-
The relationship of the Layer Tree (your data), the Presentation Tree (current interpolation values of your data), and the Render Tree are explained here in the CA programming guide.
Core Animation Rendering Architecture