I'm spinning continuously an UIImageView using CABasicAnimation and I want to be able to get the rotation angle.
-(void)viewDidAppear:(BOOL)animated{
[self spinAnimationOnView:self.myImageView
duration:2
repeat:HUGE_VAL];
}
- (void)spinAnimationOnView:(UIView*)view duration:(CGFloat)duration repeat:(float)repeat{
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0];
rotationAnimation.duration = duration;
rotationAnimation.repeatCount = repeat;
[view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
The animation behaves as expected. But then I try to get the rotation angle like this:
CGFloat angle = [(NSNumber *)[self.myImageView valueForKeyPath:#"layer.transform.rotation.z"] floatValue];
NSLog(#"ROTATION = %f", angle);
And the output is always 0. Any thoughts?
While an animation is in progress, you need to use the CALayer's presentationLayer to get the state of the layer. See the CALayer docs.
CGFloat angle = [[self.myImageView valueForKeyPath:#"layer.presentationLayer.transform.rotation.z"] floatValue];
NSLog(#"ROTATION = %f", angle);
Related
I am trying to apply a rotation animation by number of degrees to a UIImageView and persist the rotation transformation in the completion block.
The problem that I am facing is that when the completion block is executed there is a visible flicker generated by passing from the end state of the animation to the completion block.
Here is the code that I am currently using:
if (futureAngle == currentAngle) {
return;
}
float rotationAngle;
if (futureAngle < currentAngle) {
rotationAngle = futureAngle - currentAngle;
}else{
rotationAngle = futureAngle - currentAngle;
}
float animationDuration = fabs(rotationAngle) / 100;
rotationAngle = GLKMathDegreesToRadians(rotationAngle);
[CATransaction begin];
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.byValue = [NSNumber numberWithFloat:rotationAngle];
rotationAnimation.duration = animationDuration;
rotationAnimation.removedOnCompletion = YES;
[CATransaction setCompletionBlock:^{
view.transform = CGAffineTransformRotate(view.transform, rotationAngle);
}];
[view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
[CATransaction commit];
When you say flicker, I assume you mean that at the end of the animation, that it momentarily returns to the initial state before returning back to the final state? This can be solved either by
setting the final view.transform before you start the animation (and you no longer need the completionBlock);
by setting the animation's fillMode to kCAFillModeForwards and set removedOnCompletion to false.
Personally, I think setting the animated property to its destination value before you start the animation is the easiest way to do this.
Thus:
- (void)rotate:(UIView *)view by:(CGFloat)delta {
float animationDuration = 2.0;
CGFloat currentAngle = self.angle; // retrieve saved angle
CGFloat nextAngle = self.angle + delta; // increment it
self.angle = nextAngle; // save new value
view.transform = CGAffineTransformMakeRotation(nextAngle); // set property to destination rotation
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"]; // now rotate
rotationAnimation.fromValue = #(currentAngle);
rotationAnimation.toValue = #(nextAngle);
rotationAnimation.duration = animationDuration;
rotationAnimation.removedOnCompletion = YES;
[view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
Or, I think even easier, just adjust the transform:
- (void)rotate:(UIView *)view by:(CGFloat)delta {
float animationDuration = 2.0;
CGAffineTransform transform = view.transform; // retrieve current transform
CGAffineTransform nextTransform = CGAffineTransformRotate(transform, delta); // increment it
view.transform = nextTransform; // set property to destination rotation
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform"]; // now rotate
rotationAnimation.fromValue = [NSValue valueWithCGAffineTransform:transform];
rotationAnimation.toValue = [NSValue valueWithCGAffineTransform:nextTransform];
rotationAnimation.duration = animationDuration;
rotationAnimation.removedOnCompletion = YES;
[view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
I was seeing flickering even when using the suggested answer from Rob, but turns out it seems to just be a simulator bug. On real devices I dont see the flicker, if you have only been testing on simulator, try on a real device unless you want to waste hours of your life potentially like myself.
I am trying to animate a refresh button so that it rotates indicating that the refresh is in progress. It needs to be smooth so that if the refresh only takes 0.1 seconds we still do a complete rotation so the user can acknowledge something happened and that its a smooth transition. It should also continue rotating until i stop it however stopping shouldn't abruptly stop it only tell it to complete the current turn.
Originally i did something like this
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2.0 * 10];
rotationAnimation.cumulative = YES;
rotationAnimation.duration = 10;
[self.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
And stopping like so
[self.layer removeAllAnimations];
This Worked fine in the sense that the animation continued past 2pi radians smoothly, however when the refresh took less than 1/10 of the second it wouldnt look very smooth as the animation would get get 10% of the way round and then suddenly stop and the removeAllAnimations method resets the image back to its default.
I managed to get around this an alternative stop method
CALayer *presentLayer = self.layer.presentationLayer;
float currentAngle = [(NSNumber *) [presentLayer valueForKeyPath:#"transform.rotation.z"] floatValue];
[self.layer removeAllAnimations];
if (currentAngle < 0) {
currentAngle = 2 * ABS(currentAngle);
}
float rotationProgressPercent = currentAngle / (2 * M_PI);
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.fromValue = [NSNumber numberWithFloat:currentAngle];
rotationAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2];
rotationAnimation.cumulative = YES;
rotationAnimation.duration = 1 - rotationProgressPercent;
Basically I get the current angle of the rotation in radians, stop the animation and start a new animation from that position to two pi. I have to do some work with the duration to keep the speed constant, the speed aspect works fine but the problem is that somethings the animation has a very slight lag/twitch to it. I believe this is because the stop animation is asynchronously posting this request to the system (this is just speculation) and that my current angle is stale by the time i go to do my second animation.
Are there any other approaches i can try.
So i eventually found a solution, how this is useful
-(void)startSpinning {
if (animating) {
return;
}
animating = YES;
[self rotateViewWithDuration:1 byAngle:M_PI * 2];
}
- (void)stopSpinning {
animating = NO;
}
- (void)rotateViewWithDuration:(CFTimeInterval)duration byAngle:(CGFloat)angle {
[CATransaction begin];
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.byValue = [NSNumber numberWithFloat:angle];
rotationAnimation.duration = duration;
rotationAnimation.removedOnCompletion = YES;
[CATransaction setCompletionBlock:^{
if (animating) {
[self rotateViewWithDuration:duration byAngle:angle];
}
}];
[self.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
[CATransaction commit];
}
im looking to Rotate a UIImageView, and i found some helpfull code
- (void) runSpinAnimationOnView:(UIView*)view duration:(CGFloat)duration rotations: (CGFloat)rotations repeat:(float)repeat;
{
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 /* full rotation*/ * rotations * duration ];
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = repeat;
[view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
The issue is that the image flips back when the animation finishes, im looking to rotate it and keep it there. Would i need to flip the actual image or can i flip the ImageView. And how would i do that`?
Why not use a standard UIView animation?
If you really need the repeatCount, then I suggest you use the old style:
[UIView beginAnimations]
[UIView setAnimationRepeatCount:repeatCount];
[UIView setAnimationDuration:duration];
yourView.transform = CGAffineTransformMakeRotation(degrees * M_PI);
[UIView commitAnimations];
Otherwise, you can use the much preferred new style:
[UIView animateWithDuration:duration animations:^{
yourView.transform = CGAffineTransformMakeRotation(angle);
} completion:^(BOOL finished) {
// your completion code here
}]
If you need other options like autoreverse, allow user interaction or repeating use [UIView animateWithDuration:delay:options:animations:completion:];
I prefer to cover all my bases with CoreAnimation. Set the final value before the animation, set the fill mode, and set do not remove.
- (void) runSpinAnimationOnView:(UIView*)view duration:(CGFloat)duration rotations: (CGFloat)rotations repeat:(float)repeat;
{
NSString * animationKeyPath = #"transform.rotation.z";
CGFloat finalRotation = M_PI * 2.0f * rotations;
[view.layer setValue:#(finalRotation) forKey:animationKeyPath];
CABasicAnimation* rotationAnimation = [CABasicAnimation animationWithKeyPath:animationKeyPath];
rotationAnimation.fromValue = #0.0f;
rotationAnimation.toValue = #(finalRotation);
rotationAnimation.fillMode = kCAFillModeBoth;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = repeat;
[view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
I am creating a game in which the dice action is shows in a circle.When user clicks on the wheel it rotates and stop at random position.
I search and found some code which I tried to implement like:-
[UIView beginAnimations:#"RotationAnimation" context:nil];
CALayer *myLayer = diceCircle.layer;
CABasicAnimation *fullRotationAnimation;
fullRotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
fullRotationAnimation .fromValue = [NSNumber numberWithFloat:0];
// fullRotationAnimation.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
fullRotationAnimation.toValue = [NSNumber numberWithFloat:angle];
fullRotationAnimation.duration = 0.5; // speed for the rotation. Smaller number is faster
fullRotationAnimation.repeatCount = 1; // number of times to spin. 1 = once
[myLayer addAnimation:fullRotationAnimation forKey:#"360"];
[UIView commitAnimations];
angle is coming from:-
-(IBAction)diceButtonClicked:(id)sender
{
float angle=arc4random()%360;
NSLog(#"%f",angle);
float rad=radians(angle);
NSLog(#"%f",rad);
[self rotateDiceMethod:rad];
}
But the wheels stop at same position everytime and also does not always animate.Please suggest me some approach or sample code for implementing the required functionality.
Thanks in advance!
It's my old code for the same task:
CGFloat randValue= arc4random() % 10;
self.angleRotate = (randValue+30);
CABasicAnimation *spinAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
spinAnimation.fromValue = [NSNumber numberWithFloat:self.angle];
spinAnimation.toValue = [NSNumber numberWithFloat:self.angle+self.angleRotate];
spinAnimation.duration = 2.0f;
spinAnimation.cumulative = YES;
spinAnimation.additive = YES;
spinAnimation.removedOnCompletion = NO;
spinAnimation.delegate = self;
spinAnimation.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionEaseOut];
spinAnimation.fillMode = kCAFillModeForwards;
[self.arrow.layer addAnimation:spinAnimation forKey:#"spinAnimation"];
and when rotation is stopped
- (void)animationDidStop:(CAAnimation *)spinAnimation finished:(BOOL)flag{
self.angle += self.angleRotate;
while (self.angle >= M_PI*2.0) {
self.angle -= M_PI*2.0;
}
NSLog(#"angleRotate value is: %f", self.angleRotate);
NSLog(#"angle value is: %f", self.angle);
self.angleRotate = 0;
}
I have some problem with CABasicAnimation. It`s similar to that post: CABasicAnimation rotate returns to original position
So, i have uiimageview that rotate in touchMove. In touchEnd invoke method that do "animation of inertia" :
-(void)animationRotation: (float)beginValue
{
CABasicAnimation *anim;
anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
anim.duration = 0.5;
anim.repeatCount = 1;
anim.fillMode = kCAFillModeForwards;
anim.fromValue = [NSNumber numberWithFloat:beginValue];
[anim setDelegate:self];
anim.toValue = [NSNumber numberWithFloat:(360*M_PI/180 + beginValue)];
[appleView.layer addAnimation:anim forKey:#"transform"];
CGAffineTransform rot = CGAffineTransformMakeRotation(360*M_PI/180 + beginValue);
appleView.transform = rot;
}
This animation works fine, but if I invoke touchBegan before animationRotation ended, angle of rotation is beginValue. I need cath current angle of rotation. As an experiment, i declare method
-(vod) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
NSLog(#"Animation finished!");
}
and it's seems working. but I don't know how get that value of angle or CGAffineTransform for my UIImageView in animationDidStop.
It's even possible to do? Thanks.
you should use presentationLayer method to get layer properties during animation in flight.
so your code should be like this,
#define RADIANS_TO_DEGREES(__ANGLE__) ((__ANGLE__) / (float)M_PI * 180.0f)
-(void)animationRotation: (float)beginValue
{
CABasicAnimation *anim;
anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
anim.duration = 0.5;
anim.repeatCount = 1;
anim.fillMode = kCAFillModeForwards;
anim.fromValue = [NSNumber numberWithFloat:beginValue];
[anim setDelegate:self];
//get current layer angle during animation in flight
CALayer *currentLayer = (CALayer *)[appleView.layer presentationLayer];
float currentAngle = [(NSNumber *)[currentLayer valueForKeyPath:#"transform.rotation.z"] floatValue];
currentAngle = roundf(RADIANS_TO_DEGREES(currentAngle));
NSLog(#"current angle: %f",currentAngle);
anim.toValue = [NSNumber numberWithFloat:(360*M_PI/180 + beginValue)];
[appleView.layer addAnimation:anim forKey:#"transform"];
CGAffineTransform rot = CGAffineTransformMakeRotation(360*M_PI/180 + beginValue);
appleView.transform = rot;
}