Continue rotating an image after rotation gesture has ended - ios

I have an image that i can rotate using a rotation gesture but i want to continue rotating the image based on the velocity of the rotation.
Its rotating after i have released my fingers but when it stops it doesn't stay at the rotated angle. It sets the rotation angle to the last angle after the finger has been released.
- (void)rotateCoaster:(UIRotationGestureRecognizer *)recognizer {
if(recognizer.numberOfTouches >= 2) {
twoTouchesDetected = YES;
}
else {
twoTouchesDetected = NO;
}
if(recognizer.state == UIGestureRecognizerStateBegan ||
recognizer.state == UIGestureRecognizerStateChanged)
{
recognizer.view.transform = CGAffineTransformRotate(recognizer.view.transform,
recognizer.rotation);
_player.rotationVelocity = recognizer.velocity;
_player.rotation = recognizer.rotation;
[recognizer setRotation:0];
}
if( !twoTouchesDetected ) {
[self rotationCalculations:nil];
rotating = YES;
}
if( recognizer.state == UIGestureRecognizerStateEnded ||
recognizer.state == UIGestureRecognizerStateCancelled ) {
[self dropDownCoaster];
}
_player.rotationVelocity = recognizer.velocity;
}
- (void)rotationCalculations:(NSTimer *) dt {
// dTime += dt.timeInterval;
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:2.0] forKey:kCATransactionAnimationDuration];
CABasicAnimation *animation;
animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
// animation.fromValue = [NSNumber numberWithFloat:0.0];
// animation.toValue = [NSNumber numberWithFloat:_player.rotationVelocity * M_PI];
animation.fromValue = nil;
animation.toValue = nil;
animation.byValue = [NSNumber numberWithFloat:_player.rotationVelocity * M_PI];
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
animation.delegate = self;
[_coasterImageView.layer addAnimation:animation forKey:#"rotationAnimation"];
[CATransaction commit];
}

I believe your problem here is that, as WWDC 2011 Session 421 - Core Animation Essentials #~ 26:00 says, "Explicit animations do not effect the model values of your layer hierarchy".
Basically, in addition to using Core Animation to animate a rotation you need to change the property on the model object (UIView or CALayer) as well. So that when the animation finishes and the view renders normally again it is at the correct position and at the correct rotation.
Likely something like:
_coasterImageView.view.transform = CGAffineTransform... //rotation with toValue
If you are going to be doing much work with Core Animation I highly suggest you watch the video.

Related

CABasicAnimation flicker when applying the completion

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.

Constantly update a label with rotation value of an imageView

So I am currently making an app where there is a gauge that animates to show a certain value. I would like to have a label that shows the value of the rotation degree as it is updated. Here is the code I have:
CABasicAnimation *myRotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
myRotation.fromValue = [NSNumber numberWithFloat:0];
myRotation.toValue = [NSNumber numberWithFloat:angle * M_PI / 180];
myRotation.duration = duration;
myRotation.fillMode = kCAFillModeForwards;
myRotation.removedOnCompletion = NO;
myRotation.repeatCount = count;
myRotation.autoreverses = reverse;
if (easeIn == YES & easeOut == YES) {
myRotation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
}else if (easeIn == YES & easeOut == NO) {
myRotation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
}else if (easeIn == NO & easeOut == YES) {
myRotation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
}else {
myRotation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
}
[[self.arrowPicture layer] addAnimation:myRotation forKey:#"transform.rotation"];
arrowPicture is the picture that I am rotating. So basically, I want the label to show the value of the rotation degree of arrowPicture at one given time and this should update as the image view moves. A simple self.arrowPicture.text = angle doesn't work. Any suggestions?
I think it related to this Question: How to get CABasicAnimation progress?
It basically says, you know how long the animation lasts, so you can set a timer which fires as long as the animation is running.

Move UIImageview in Curve

i am trying to add Image and move it on curve path. i have half circle with value 0 to 100. And i want to move that image with value.
This is image of my curve progress bar
I want to rotate the pointer on that line.
If i try bezier curve i wont be able to spot my pointer . it will animation from start to end.
Any help how can i animate this.?
Thanks
Use the following snippet of code for making half circle path. Replace the spin button with the needle you are required to use and provide angle to move the needle. I'm using this code for speedometer. Hope this helps for you.
- (void)spinButton : (UIView *)button : (float)angle
{
button.layer.anchorPoint = CGPointMake(0.5, 0.5);
CABasicAnimation *animation;
animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
// just for testing
// angle +=200;
if(angle >=360){angle = 360;}
animation.fromValue = [NSNumber numberWithFloat:lastAngle];
float m = angle/2 * (M_PI/180 );
animation.toValue = [NSNumber numberWithFloat:(m)];
// [CATransaction setValue:[NSNumber numberWithFloat:1.0] forKey:kCATransactionAnimationDuration];
lastAngle = m;
// animation.duration = 1.0f;
// to stop animation at last frame
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
animation.autoreverses = NO;
[button.layer addAnimation:animation forKey:#"rotationAnimation"];
[CATransaction begin];
// [CATransaction commit];
}
You can call this functions like this:
[self spinButton:btn :0];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self spinButton:btn :50];
});
This way you can achieve your desired result.

Rotate a refresh button image smoothly so that it completes to the nearest turn iOS

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];
}

UIview Flip darkens the Views

I've been implementing a simple FlipView in iOS : A UIView that contains two subviews, displaying one at a time, and when you click on it, it flips them.
I'm using the following to animate the flipping.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
#synchronized(self){
if(!self.flipping){
self.flipping = YES;
UIView *toView = self.currentView == self.primaryView ? self.secondaryView : self.primaryView;
[UIView transitionFromView:self.currentView toView:toView duration:self.speed options:UIViewAnimationOptionTransitionFlipFromLeft|UIViewAnimationOptionCurveEaseInOut completion:^(BOOL finished) {
[self.currentView removeFromSuperview];
self.currentView = toView;
self.flipping = NO;
}];
}
}
}
Pretty straight forward, right ?
But what bugs me is that, while the views are flip, the flipped content is darkened. Which shows, against a light background.
Would anyone knows a solution to have the exact same animation, but without the darkening (<= is that even a word ?)
Thanks in advance !
PS : I'm targeting IOS 5 and above.
I recently had a problem with similar symptoms and I was adding a subview over and over again else where in my code whenever I committed a certain action. Maybe you are doing something similar? When your touches end, are you doing something else to your flipped content? You probably need to remove the subviews being added IF that is your problem.
I succeeded, getting inspiration in the code I found here http://www.mycodestudio.com/blog/2011/01/10/coreanimation/ (and he, himself, took inspiration from http://www.mentalfaculty.com/mentalfaculty/Blog/Entries/2010/9/22_FLIPPIN_OUT_AT_NSVIEW.html)
Anyway, what I do spin between two views.
- (void)flip{
#synchronized(self){
if(!self.flipping){
self.flipping = YES;
UIView *bottomView = self.currentView == self.primaryView ? self.secondaryView : self.primaryView;
CALayer *top = self.currentView.layer;
CALayer *bot = bottomView.layer;
CAAnimation *topAnimation = [self flipAnimationWithDuration:self.speed/2.0 forLayerBeginningOnTop:YES scaleFactor:1];
CAAnimation *bottomAnimation = [self flipAnimationWithDuration:self.speed/2.0 forLayerBeginningOnTop:NO scaleFactor:1];
CGFloat zDistance = 1500.0f;
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1. / zDistance;
top.transform = perspective;
bot.transform = perspective;
topAnimation.delegate = self;
[CATransaction setCompletionBlock:^{
[top removeAllAnimations];
[self.currentView removeFromSuperview];
self.currentView = bottomView;
[self addSubview:bottomView];
[CATransaction setCompletionBlock:^{
self.flipping = NO;
[bot removeAllAnimations];
}];
[CATransaction begin];
[bot addAnimation:bottomAnimation forKey:#"flip"];
[CATransaction commit];
}];
[CATransaction begin];
[top addAnimation:topAnimation forKey:#"flip"];
[CATransaction commit];
}
}
}
-(CAAnimation *)flipAnimationWithDuration:(NSTimeInterval)aDuration forLayerBeginningOnTop:(BOOL)beginsOnTop scaleFactor:(CGFloat)scaleFactor
{
// Rotating halfway (pi radians) around the Y axis gives the appearance of flipping
CABasicAnimation *flipAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
CGFloat startValue = beginsOnTop ? 0.0f : M_PI/2;
CGFloat endValue = beginsOnTop ? -M_PI/2 : 0.0f;
flipAnimation.fromValue = [NSNumber numberWithDouble:startValue];
flipAnimation.toValue = [NSNumber numberWithDouble:endValue];
// Shrinking the view makes it seem to move away from us, for a more natural effect
// Can also grow the view to make it move out of the screen
CABasicAnimation *shrinkAnimation = nil;
if (scaleFactor != 1.0 ) {
shrinkAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
shrinkAnimation.toValue = [NSNumber numberWithFloat:scaleFactor];
// We only have to animate the shrink in one direction, then use autoreverse to "grow"
shrinkAnimation.duration = aDuration * 0.5;
shrinkAnimation.autoreverses = YES;
}
// Combine the flipping and shrinking into one smooth animation
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = [NSArray arrayWithObjects:flipAnimation, shrinkAnimation, nil];
// As the edge gets closer to us, it appears to move faster. Simulate this in 2D with an easing function
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:beginsOnTop?kCAMediaTimingFunctionEaseIn:kCAMediaTimingFunctionEaseOut];
animationGroup.duration = aDuration;
// this really means keep the state of the object at whatever the anim ends at
// if you don't do this then it reverts back to the original state (e.g. brown layer)
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.removedOnCompletion = NO;
return animationGroup;
}
The two views are named primaryView and secondaryView. You can use any view, (ImageView, text view...)

Resources