Why CABasicAnimation will send the layer's view to back and front? - iphone

There are two UIViews of similar size. UIView one (A) is originally on top of UIView two (B). When I try to perform a CABasicAnimation transform.rotation.y on A's layer:
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
CGFloat startValue = 0.0;
CGFloat endValue = M_PI;
rotateAnimation.fromValue = [NSNumber numberWithDouble:startValue];
rotateAnimation.toValue = [NSNumber numberWithDouble:endValue];
rotateAnimation.duration = 5.0;
[CATransaction begin];
[imageA.layer addAnimation:rotateAnimation forKey:#"rotate"];
[CATransaction commit];
During the animation, the animating layer's UIView (A) will be:
sent back (A is suddenly behind B)
rotating ... passed second half of the animation
sent front (A is now on top of B again)
Is there a way to keep A on top of B for the whole animation period? Thanks!
UPDATE: project source is attached: FlipLayer.zip

The problem is a rotation around the Y axis will, in a true 3D compositing system, make layer A intersect with layer B, such that half of layer A should be visible and half should be behind layer B. CoreAnimation does not provide a true 3D compositing system. Instead it attempts to determine which layer is in front of which other layer, and renders it entirely without intersection. In your case, during the first half of the rotation CoreAnimation has decided that layer B is on top of layer A in the 3D space. The simplest solution is probably to set the zPosition of layer A to layer.bounds.size.width (default is 0) for the duration of the animation (or permanently, if it should always be on top).
If you want to set it only for the duration of the animation, you can add a second animation for the transform.translation.z key path. Here's your -flip method modified to use this:
- (IBAction)flip:(id)sender {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
CGFloat startValue = 0.0;
CGFloat endValue = M_PI;
animation.fromValue = [NSNumber numberWithDouble:startValue];
animation.toValue = [NSNumber numberWithDouble:endValue];
animation.duration = 5.0;
[imageOne.layer addAnimation:animation forKey:#"rotate"];
animation.keyPath = #"transform.translation.z";
animation.fromValue = [NSNumber numberWithFloat:imageOne.layer.bounds.size.width];
animation.toValue = animation.fromValue;
[imageOne.layer addAnimation:animation forKey:#"zPosition"];
}

Have you tried adding a [self.view bringSubviewToFront:imageA]; in your animation sequence?

You could try to translate on the z axis and bring A forward first. Perform the rotation and then set the z back to its starting value.
Also, you probably won't see any perspective on the rotation unless you set the .m34 property on the transform. Try something like this:
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0f / -300.0f;
transform = CATransform3DRotate(transform, 0.0f, M_PI, 0.0f);
CABasicAnimation *rotateAnimation = [CABasicAnimation
animationWithKeyPath:#"transform"];
rotateAnimation.fromValue = [NSValue
valueWithCATransform3D(CATransformIdentity)];
rotateAnimation.toValue = [NSValue valueWithCATransform3D(transform)];
rotateAnimation.duration = 5.0;
[imageA.layer addAnimation:rotateAnimation forKey:#"rotate"];
You transaction block is unnecessary.
Best Regards.

Related

How to move and rotate object in iphone

I am developing a small game in iphone... Game concept is place an object in top of the bar...Rotating a bar using accelerometer. When bar rotating , i need to move an object with respect to bar. How to implement this concept...Any examples or references?
Rotating both img:
barImg,objImg //UIImageView
barImg.transform = CGAffineTransformMakeRotation(Ypos);
objImg.transform=CGAffineTransformMakeRotation(Ypos);
So for rotating 360 Degree with animation:
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat:-M_PI * 2.0]; // full rotation*/ * rotations * duration ];
rotationAnimation.duration = 1;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = MAXFLOAT;
[rotatingTelIamgeview.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
you can play with toValue for changing the angle.
For moving an object:
CGPoint startPoint = CGPointMake(lvMessageInputBar.animationBubbleImageView.layer.frame.origin.x+lvMessageInputBar.animationBubbleImageView.layer.frame.size.width/2
, lvMessageInputBar.animationBubbleImageView.layer.frame.origin.y +lvMessageInputBar.animationBubbleImageView.layer.frame.size.height/2 );
CGPoint endPoint = CGPointMake(endPoint.origin.x+Endpoint.size.width/2, bubleRect.origin.y+bubleRect.size.height/2);
CGPoint middlePoint = // any point between start and end corrdinates
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, startPoint.x, startPoint.y);
CGPathAddQuadCurveToPoint(path, NULL,middlePoint.x, middlePoint.y,endPoint.x,endPoint.y);
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.delegate = self;
pathAnimation.removedOnCompletion = NO;
pathAnimation.path = path;
[pathAnimation setCalculationMode:kCAAnimationCubic];
[pathAnimation setFillMode:kCAFillModeForwards];
pathAnimation.duration = 0.3;
[lvMessageInputBar.animationBubbleImageView.layer addAnimation:pathAnimation forKey:nil];
The above example moves the layer of the object over a path.but CGPathAddQuadCurveToPoint makes the path not diagonal rather circular path (curve).
you can use CGPathAddPath if you do not want this effect.
If you are new to iPhone I would start with layer tutorial to get the idea behind CALayer and then take a look CALayer animations
CALayers introduction:
https://www.raywenderlich.com/3096-calayers-tutorial-for-ios-introduction-to-calayers
CALayer Animation documentation of from Apple:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html
You can also watch Session 424 (Core Animation in Practice, Part 1) and 425 (Core Animation in Practice, Part 2) of WWDC 2010:
https://developer.apple.com/videos/archive/

CABasicAnimation CorePlot scale in effect

I am trying to scale in a core plot scatter plot (like a line graph) so that it scales in to view. CorePlot uses a normal CALayer to implement its plot.
I am using Core animation as follows to achieve this:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"affineTransform"];
animation.duration = 10;
animation.removedOnCompletion = YES;
animation.fillMode = kCAFillModeForwards;
CGAffineTransform start = CGAffineTransformMakeScale(1, 0.1);
CGAffineTransform end = CGAffineTransformMakeScale(1, 1);
animation.fromValue = [NSValue valueWithCGAffineTransform:start];
animation.toValue = [NSValue valueWithCGAffineTransform:end];
[plot addAnimation:animation forKey:#"animation"];
The problem is this doesn't seem to work. This is the first time I am using core animation so I am thinking I am just doing something wrong?
EDIT
OK so I found the issue I was using the wrong keypath.
I can get it to animate now but it looks wierd:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.scale.y"];
...
animation.fromValue = [NSNumber numberWithFloat:0.1];
animation.toValue = [NSNumber numberWithFloat:1];
What I want to do is have the animation 'scale upwards' so it kind of stretches in to place. Right now it kind of grows out from the centre which is not what I want. Any idea how to achieve that?
The transform is relative to the anchorPoint of the layer. You can change the anchor point to be at the bottom by setting it to (0.5, 1,0). Note that the layer also draws relative to the anchor point so it may change its position on screen when you change it. To position it where it should be you could either set the frame after changing the anchor point or set the position to the bottom (where it should scale from).

Sequential UIView Transform

I've got a question about applying multiple transforms to an UIView. When I animate a rotation of an UIView around it's center point, for example, and then try to rotate it around a point that lies outside it's bounds after that, the second animation is all messed up, e.g. it jitters around or rotates in a completely different way than specified. How can I make it so that the first animation doesn't influence the second, but is still present when the second one is played?
EDIT: Here's the code. First I rotate the view around it's center point:
CALayer *layer = view.layer;
CATransform3D aTransform = CATransform3DIdentity;
CGFloat zDistance = 2000;
aTransform.m34 = 1.0 / -zDistance;
scrollView.layer.sublayerTransform = aTransform;
CGFloat subviewX = 0.5;
CGFloat subviewY = 0.5;
[self setAnchorPoint:CGPointMake(subviewX, subviewY) forView:view];
CATransform3D bTransform = CATransform3DIdentity;
CABasicAnimation *rotateAnim = [CABasicAnimation animationWithKeyPath:#"transform"];
rotateAnim.fromValue= [NSValue valueWithCATransform3D:bTransform];
bTransform = CATransform3DRotate(aTransform,-20*M_PI/180, 1, 1, 0);
rotateAnim.duration=0.05;
rotateAnim.toValue=[NSValue valueWithCATransform3D:bTransform];
layer.transform = bTransform;
[layer addAnimation:rotateAnim forKey:nil];
Now that the layer is rotated, I want to flip it around the left screen border:
CALayer *layer = view.layer;
CATransform3D aTransform = CATransform3DIdentity;
CGFloat zDistance = 2000;
aTransform.m34 = 1.0 / -zDistance;
tileScrollView.layer.sublayerTransform = aTransform;
CGFloat subviewX = ((1/view.frame.size.width)*(view.frame.origin.x));
CGFloat subviewY = 0.5;
[self setAnchorPoint:CGPointMake(-subviewX, subviewY) forView:view];
CATransform3D bTransform = layer.transform;
CABasicAnimation *rotateAnim = [CABasicAnimation animationWithKeyPath:#"transform"];
rotateAnim.fromValue= [NSValue valueWithCATransform3D:bTransform];
bTransform = CATransform3DMakeRotation(-M_PI_2, 0, 1, 0);
rotateAnim.duration=0.2;
rotateAnim.toValue=[NSValue valueWithCATransform3D:bTransform];
layer.transform = bTransform;
[layer addAnimation:rotateAnim forKey:nil];
Since I want the rotated layer to be flipped around, I put CATransform3D bTransform = layer.transform instead of CATransform3D bTransform = CATransform3DIdentity at the beginning of the second animation, but again, that only messes the animation up
Another, similar problem I have is that I have an UIView that contains 9 subViews, one of which flips around it's center point every second. But every time I apply a transformation to the superView of those 9 UIViews, the layout of the subViews gets messed up. Does anyone know how to prevent this? Any help would be greatly appreciated. Thanks in advance!
Two things:
In the first transform, you do this:
bTransform = CATransform3DRotate(aTransform,-20*M_PI/180, 1, 1, 0);
meaning that you rotate around an axis that is 45° between the x and y axis. That might be what you're trying to do, but it seems strange. Consider changing to
bTransform = CATransform3DRotate(aTransform,-20*M_PI/180, 0, 1, 0);
In the second step, if you just want to add the transform onto the current transform (i.e. rotate another 180°), you need to change this line
bTransform = CATransform3DMakeRotation(-M_PI_2, 0, 1, 0);
to
bTransform = CATransform3DRotate(bTransform, -M_PI_2, 0, 1, 0);
This applies the transformation to the existing transform, instead of ignoring the current transform.

CABasicAnimation not changing its position property after animation completes

i am applying CABasicAnimation for position property of a layer it is animating well but after animation complete its coming back to original position how to avoid that and to make image to stay in animated position only?
There are a few different ways to solve this. My favorite is to set the object at the end of the animation, and use fromValue instead of toValue to create the animation. You can also set both fromValue and toValue to create this effect. ie
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.duration = 1.0;
animation.fromValue = [NSValue valueWithCGPoint:startPoint];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
viewToAnimate.center = endPoint;
[viewToAnimate.layer addAnimation:animation forKey:#"slide"];
The other way to do this is to set yourself as the delegate and implement:
-(void)animationDidStop:(id)sender finished:(BOOL)finished {
viewToAnimate.center = endPoint;
}
You need to set the final position. If you only use the animation fromValue and toValue it will snap back to the original starting point. The animation uses a copy of the view/layer as it "plays" and then the animation will show the original view/layer when it finishes.
If you don't explicitly set it, it will appear to snap back, even though the view/layer never really moved.
startPoint and endPoint are CGPoints and myView is the UIView that is being animated.
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.duration = .3;
animation.fromValue = [NSValue valueWithCGPoint:startPoint];
animation.toValue = [NSValue valueWithCGPoint:endPoint];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[myView.layer addAnimation:animation forKey:#"position"];
myView.layer.position = endPoint; // Must set end position, otherwise it jumps back
To set the object at the end of the animation just write the code like this
#import <QuartzCore/QuartzCore.h>
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.duration = 1.0;
animation.fromValue = [NSValue valueWithCGPoint:startPoint];
animation.timingFunction = [CAMediaTimingFunction functionWithName:easing];
animation.fillMode = kCAFillModeForwards; // This line will stop the animation at the end position of animation
animation.autoreverses = NO;
viewToAnimate.center = endPoint;
[viewToAnimate.layer addAnimation:animation forKey:#"slide"];
You can just set removedOnCompletion to NO if you want to preserve the final value. And don't forget to set the fillMode.
Swift 4+
animation.fillMode = .forwards;
animation.isRemovedOnCompletion = false
Objective-C
animation.fillMode = kCAFillModeForwards; //The receiver remains visible in its final state when the animation is completed.
animation.removedOnCompletion = NO;
If you look at the docs for addAnimation:forKey:, it says 'Typically this is implicitly invoked'. In other words, you're not supposed to call it directly. You're actually meant to do the following instead, which is even easier than setting from and to values, you simply set the values directly on the layer:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"opacity"];
animation.duration = .3;
myLayer.actions = #{#"opacity": animation};
myLayer.opacity = 0;
This will fade a layer to zero opacity.

How to get layer point?

I am using this code to rotate a needle (like in a speedometer):
CALayer* layer = someView.layer;
CABasicAnimation* animation;
animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0.0 * M_PI];
animation.toValue = [NSNumber numberWithFloat:1.0 * M_PI];
animation.duration = 1.0;
animation.cumulative = YES;
animation.repeatCount = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:rotationAnimation forKey:#"transform.rotation.z"];
Now my question is that during the animation i press one button at that time needle is back to move on particular point. So i must have to get last point of layer when i touch button.
How to get last point of layer?
Your question is very hard to understand, but it seems like you are asking how to determine the state of a progressing animation at the moment a user presses a button. To do this, you can get a CALayer's presentationLayer, and query that for the current value of the property being animated.
The answers to this question show you how to determine the angle of your rotated presentationLayer. You probably want to ignore my needlessly complex answer there.