I'm trying to find a way to animate 3D rotating of UIView. I want it looks like animation of push notification if you select "Banner" style. I'm trying to use something like
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform"];
CATransform3D t = CATransform3DIdentity;
t = CATransform3DTranslate(t, 0, -self.bounds.size.height/2, 0);
t = CATransform3DRotate(t, M_PI_2, 1, 0, 0);
t = CATransform3DTranslate(t, 0, -self.bounds.size.height/2, 0);
[anim setFromValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]];
[anim setToValue:[NSValue valueWithCATransform3D:t]];
anim.removedOnCompletion = NO;
[anim setDuration:0.25];
anim.fillMode = kCAFillModeForwards;
anim.delegate = self;
[self.layer addAnimation:anim forKey:#"Rotation"];
But this looks flat and not so realistic 3D as an banners of push notifications. How can I make this animation more realistic?
You may want to take a deep look at how CMNavBarNotificationView is implemented.
Plus, there are different types of CALayer, and you might want to pick a desired one from +(Class)layerClass of your animated view. It seems me that you're sticking with plain CALayer.
Related
I have 3 animations running in a group: position, scale and rotation
However my rotation animation does not happen smoothly. It happens abruptly in the beginning, while other animations happen smoothly.
Here is my code:
//CABasicAnimation *scaleAnimation
//CABasicAnimation *moveAnimation
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
rotationAnimation.autoreverses = NO;
rotationAnimation.toValue = [NSNumber numberWithFloat: 0];//-(M_PI/3)];
//below line shows the end state, if removed, the layer will assume its initial position after animation, something which I don't want.
[layer setTransform:CATransform3DMakeRotation(0, 0, 1, 0)];
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
//Code for adding all 3 animations to the group
Edit:
rotationAnimation.toValue = [NSNumber numberWithFloat: -(M_PI/3)];
Removed final transform value from the place of animation declaration, and added it to delegate. (note that in case of animation group, individual animation delegates do not get hit, only the group delegate worked).
Under delegate:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
NSString * animName = [theAnimation valueForKey:#"animationName"];
CALayer* layer = [theAnimation valueForKey:#"animationLayer"];
if ([animName isEqualToString:#"MoveZoomOut"])
{
if ([layer.name isEqualToString:#"Layer1"])
[layer setTransform:CATransform3DMakeRotation(-M_PI/3, 0, 1, 0)];
else if ([layer.name isEqualToString:#"Layer2"])
[layer setTransform:CATransform3DMakeRotation(M_PI/3, 0, 1, 0)];
else
[layer setTransform:CATransform3DMakeRotation(0, 0, 1, 0)];
}
if ([layer respondsToSelector:#selector(setContentsScale:)])
{
layer.contentsScale = [UIScreen mainScreen].scale;
}
}
I have 3 animations running in a group: position, scale and rotation
I suspect that since you are animating both scale and rotation, which is done through the transform property, there might be a clash.
Try to animate scale and rotation in a single animation by concatenating the 2 transforms into one:
CATransform3D t = CATransform3DMakeRotation(0, 0, 1, 0)];
[layer setTransform:CATransform3DScale(t, sx, sy, sz)];
EDIT:
I understand that what is happening is pretty unusual, so I will give you a couple of hints as to tricks that in several occasions helped me. I understand, you have 3 layers to animate.
wrap each layer animations in [CATransaction begin]/[CATransaction commit];
if that does not help,
do not set the final value of the property you are going to animate right after creating the animation itself; rather set it in the animation delegate animationDidStop:finished: method.
In other words, given your code:
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
rotationAnimation.autoreverses = NO;
rotationAnimation.toValue = [NSNumber numberWithFloat: 0];//-(M_PI/3)];
//below line shows the end state, if removed, the layer will assume its initial position after animation, something which I don't want.
[layer setTransform:CATransform3DMakeRotation(0, 0, 1, 0)];
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
don't do [layer setTransform:CATransform3DMakeRotation(0, 0, 1, 0)]; there. Rather, do it in:
- (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag {
[layer setTransform:CATransform3DMakeRotation(0, 0, 1, 0)];
}
Since you will have more than one animation going on (and presumably use the same delegate for all of them), you will need a way to tell which animation the animationDidStop: method refers to. To this aim, you could do, e.g.:
//-- se the delegate
rotationAnimation.delegate = self;
[rotationAnimation setValue:layer forKey:#"animationLayer"];
and in the delegate method:
- (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag {
[CATransaction begin];
[CATransaction setDisableActions:YES];
CALayer* layer = [anim valueForKey:#"animationLayer"];
layer.transform = ...;
[CATransaction commit];
}
I know that this seems a bit contrived. And it is. But it is the only way I could fix an issue similar to what you are reporting.
Hope this helps.
EDIT:
For the glitch when using animationDidStop:, try to wrap animationDidStop: content in:
[CATransaction begin];
[CATransaction setDisableActions:YES];
...
[CATransaction commit];
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/
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.
I have a problem with the image rotated.
The serrated(shake) image will be shown when rotating the image if i use CATransform3DMakeRotation(M_PI, 0, 0, -1.0) to make a animation with layer.
The backgroundView is added into another animation view (same as the backgroundView, but the direction is reverse).
The code is:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI , 0, 0, -1.0)];
animation.duration = 30;
animation.cumulative = YES;
animation.repeatCount = INT_MAX;
[backgroundView.layer addAnimation:animation forKey:#"animationOne"];
Thank you for your time.
The problem is fixed , to use the function can remove the serrate of image when rotating.
[self.layer setShouldRasterize:YES];
I'm trying to create a simple iPhone app that displays a picture with a reflection beneath, and I want to rotate the picture around the X axis, using Core Animation.
I've started by creating a new iPhone app using the "View-based Application" template. I added a "Picture.jpg" image file, and then I added this to the view controller:
- (void)awakeFromNib {
CGFloat zDistance = 1500.0f;
// Create perspective transformation
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0f / -zDistance;
// Create perspective transform for reflected layer
CATransform3D reflectedTransform = CATransform3DMakeRotation(M_PI, 1.0f, 0.0f, 0.0f);
reflectedTransform.m34 = 1.0f / -zDistance;
// Create spinning picture
CALayer *spinningLayer = [CALayer layer];
spinningLayer.frame = CGRectMake(60.0f, 60.0f, 200.0f, 300.0f);
spinningLayer.contents = (id)[UIImage imageNamed:#"Picture.jpg"].CGImage;
spinningLayer.transform = transform;
// Create reflection of spinning picture
CALayer *reflectionLayer = [CALayer layer];
reflectionLayer.frame = CGRectMake(60.0f, 360.0f, 200.0f, 300.0f);
reflectionLayer.contents = (id)[UIImage imageNamed:#"Picture.jpg"].CGImage;
reflectionLayer.opacity = 0.4f;
reflectionLayer.transform = reflectedTransform;
// Add layers to the root layer
[self.view.layer addSublayer:spinningLayer];
[self.view.layer insertSublayer:reflectionLayer below:spinningLayer];
// Spin the layers
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
anim.fromValue = [NSNumber numberWithFloat:0.0f];
anim.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
anim.duration = 3.0f;
anim.repeatCount = 1e100f;
[spinningLayer addAnimation:anim forKey:#"anim"];
[reflectionLayer addAnimation:anim forKey:#"anim"];
}
This almost works. The problem is that the spinning of the main picture and of the reflection are not perfectly synchronized. It's very close to perfect, but the edges of the bottom of the picture and of top of the reflection are a few pixels apart. They appear to differ by a few degrees.
On the left side of the screen, the reflection seems to be "ahead" of the picture, and on the right side, the reflection is behind the picture. In other words, it looks like the bottom corners of the top image are pushed toward the screen, while the top corners of the reflection are pulled toward the viewer. This is true both when looking at the front and at the back of the images as they spin.
If I increase zDistance, the effect is less noticeable. If I eliminate the perspective transformations altogether by leaving transform.m34 equal to zero for both layers, then the picture and reflection look to be perfectly synchronized. So I don't think the problem is related to time-synchronization issues.
I suspect my perspective transformations are missing something, but I'm not sure how to determine what's wrong. I think I'm basically doing the same transformation described in this related Stack Overflow question.
Can someone help?
I think I may have found an answer to my question. In my original code, I created the reflection by placing it underneath the picture, and applying the flip and perspective. The right way to do it seems to be to place the reflection at the same position as the picture, apply translation and rotation transforms to get it to the right place, and then apply the perspective transformation.
When animating the spin, it is necessary to spin the reflection in the opposite direction of the picture's animation.
So, here's code that works:
- (void)awakeFromNib {
CGFloat zDistance = 1500.0f;
// Create perspective transformation
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0f / -zDistance;
// Create perspective transform for reflected layer
CATransform3D reflectedTransform = CATransform3DMakeTranslation(0.0f, 300.0f, 0.0f);
reflectedTransform = CATransform3DRotate(reflectedTransform, M_PI, 1.0f, 0.0f, 0.0f);
reflectedTransform.m34 = 1.0f / -zDistance;
// Create spinning picture
CALayer *spinningLayer = [CALayer layer];
spinningLayer.frame = CGRectMake(60.0f, 60.0f, 200.0f, 300.0f);
spinningLayer.contents = (id)[UIImage imageNamed:#"Picture.jpg"].CGImage;
spinningLayer.transform = transform;
// Create reflection of spinning picture
CALayer *reflectionLayer = [CALayer layer];
reflectionLayer.frame = CGRectMake(60.0f, 60.0f, 200.0f, 300.0f);
reflectionLayer.contents = (id)[UIImage imageNamed:#"Picture.jpg"].CGImage;
reflectionLayer.opacity = 0.4f;
reflectionLayer.transform = reflectedTransform;
// Add layers to the root layer
[self.view.layer addSublayer:spinningLayer];
[self.view.layer insertSublayer:reflectionLayer below:spinningLayer];
// Spin the layers
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
anim.fromValue = [NSNumber numberWithFloat:0.0f];
anim.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
anim.duration = 3.0f;
anim.repeatCount = 1e100f;
[spinningLayer addAnimation:anim forKey:#"anim"];
CABasicAnimation *reflectAnim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
reflectAnim.fromValue = [NSNumber numberWithFloat:0.0f];
reflectAnim.toValue = [NSNumber numberWithFloat:(-2 * M_PI)];
reflectAnim.duration = 3.0f;
reflectAnim.repeatCount = 1e100f;
[reflectionLayer addAnimation:reflectAnim forKey:#"reflectAnim"];
}
I'm not sure exactly why this works. It makes intuitive sense to me that placing the reflection at the same position as the original and then transforming it somehow puts the two images into the same "transformation space", but I'd appreciate it if someone could explain the mathematics behind this.
The full project is available at GitHub: https://github.com/kristopherjohnson/perspectivetest
You can use a CAAnimationGroup object to run two animations on the same time space. That should take care of any synchronization issues.
EDIT: removed silly code.