Swift UIView transform animation never goes counterclockwise [duplicate] - ios

I'm using CGAffineTransformMakeRotation to rotate a UIView inside an animation block, and I want to rotate it counterclockwise for 180º.
However when I put
myView.animateWithDuration(0.5)
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI))
it still rotates clockwise. What's confusing me is that if I write "manually" the value of M_PI, it works as expected
// this will rotate counterclockwise
myView.transform = CGAffineTransformMakeRotation(CGFloat(-3.14159265358979))
Why this behavior?
Edit: added I'm using an animation block

As I stated in my first comment and as Segii already wrote: the animation will take the shortest distance between where it's value is at now and where it is trying to go. The best solution imho is to use CABasicAnimation for forcing a counterclockwise rotation:
let anim = CABasicAnimation(keyPath: "transform.rotation")
anim.fromValue = M_PI
anim.toValue = 0
anim.additive = true
anim.duration = 2.0
myView.layer.addAnimation(anim, forKey: "rotate")
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI))
This will rotate it by 180 counter clockwise in one go.
The angle you specify in the last line + the fromValue is the starting angle -> 0. And then the rotation will go the distance (toValue - fromValue) = 0 - M_PI = - M_PI -> counter clockwise 180 degree rotation.

As I understood, you're trying to rotate view within animation block. animateWithDuration will always perform animation with the shortest way. So, M_PI and -M_PI gives the same destination position. The reason, why you get expected animation direction when setting it manually is that your manual value is really smaller that M_PI, so the shortest way to rotate is counterclockwise.
To get expected behaviour, you'll have to chain at least two animations,
splitting you rotation angle. For example
//first animation block
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI/2))CGAffineTransformMakeRotation;
//second animation block
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI/2))CGAffineTransformMakeRotation;
Swift 3.0
myView.transform = CGAffineTransform(rotationAngle: CGFloat(-M_PI/2))
Or, much more reliable, use CAKeyframeAnimation

Related

IOS/Objective-C:Animation of Multiple CGAffineTransformations

I am trying to perform an animation that does three things at once: translates, rotates and changes the size of an image.
I can do two at once, translate and size. However, when I add in rotation at the end of the following code, it is ignored. And if I place it at the beginning of the code, the size change is ignored. I've read that you can do a composite transition with view.transform, however, I have not been able to get that to work.
Here is my current code:
CGPoint destPoint = CGPointMake(-100,-50);
float radians =[self Degrees2Radians:-35];
[UIView animateWithDuration:2
animations:^{
//TRANSLATE
self.imageView.center = CGPointMake(self.imageView.center.x + destPoint.x, self.imageView.center.y + destPoint.y);
//ROTATE
self.imageView.transform = CGAffineTransformMakeRotation(radians);
//SCALE
self.imageView.transform = CGAffineTransformMakeScale(0.2, 0.2); // here the final size will be 20%
}
completion:nil
];
}
Can anyone recommend way to get all three things to occur simultaneously.
Here is some code for swift that uses the transform property of the view, but I have not been able to find the equivalent in Objective-C.
view.transform= CGAffineTransform(scaleX: 1.5, y: 1.5)
view.transform = view.transform.rotated(by angle: CGFloat(45 * M_PI / 180))
Thanks in advance for any suggestions.
You can use CGAffineTransformRotate function on the existing transform to apply a rotation. You can also use CGAffineTransformTranslate and CGAffineTransformScale to apply translation and scaling. Please note that, order of the operations matter.
For example if you have an existing transform myTransform you can rotate it like:
myTransform = CGAffineTransformRotate(myTransform, M_PI / 2);
The operation does not affect the input variable, instead, it returns a new transform so make sure you use the return value of the function. That's why I started the line with myTransform = ....
More information is available at https://developer.apple.com/documentation/coregraphics/cgaffinetransform-rb5?language=objc.

How can I calculate the duration of an ease-out animation to achieve a desired initial velocity?

Context:
I have a graphic that I rotate continuously while a background operation happens:
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = #(M_PI * 2.0);
rotationAnimation.duration = 2;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = HUGE_VALF;
[myImgView.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
At some point this background animation ends, and I stop the animation:
[myImgView.layer removeAllAnimations];
When I do this, though, the rotation stops wherever it is and snaps back to zero rotation. I could just stop it where it is by promoting the presentation value to the model value:
CATransform3D stoppedTransform = myImgView.layer.presentationLayer.transform;
[myImgView.layer removeAllAnimations];
myImgView.layer.transform = stoppedTransform;
This works OK. But ideally I'd like to continue the rotation for the rest of its current circuit, and decelerate and stop smoothly as it approaches and rests at zero rotation. Stop the animation, promote presentation to model, and create a new animation with an ease-out curve to zero. Something like this:
CATransform3D stoppedTransform = myImgView.layer.presentationLayer.transform;
[myImgView.layer removeAllAnimations];
myImgView.layer.transform = stoppedTransform;
CGFloat currRotation = [[myImgView.layer valueForKeyPath:#"transform.rotation.z"] floatValue];
CABasicAnimation *finishAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
finishAnimation.fromValue = #(currRotation);
finishAnimation.toValue = #(0);
finishAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
finishAnimation.duration = 2; //this is what I need to calculate correctly
[myImgView.layer setValue:#(0) forKeyPath:#"transform.rotation.z"];
[myImgView.layer addAnimation:finishAnimation forKey:#"transform.rotation.z"];
My question:
How do I calculate the duration of my finishing animation with its ease-out curve so that its initial velocity matches the velocity of the existing rotation (currently pi radians per second) so there's no visible hiccup? Or is there another way to achieve the desired effect?
I have tried using CASpringAnimation and using its initialVelocity property, but it seems to combine that with the velocity imparted by the spring pulling on the mass, which is governed by mass, stiffness, and damping, and its units are unclear. And I can get close on the ease-out duration by calculating the duration as if it's linear and bumping it 20% or 30% or so.
Side note: there is some minor complexity here, removed to simplify the question, around the toValue of finishAnimation because of how the layer reports its current rotation value. If it's more than pi radians (180 degrees), it reports as negative. So it goes like this:
CGFloat rotationRadians = currRotation / M_PI;
if (rotationRadians < 0)
{
finishAnimation.duration = (-1 * rotationRadians); //negative means more than halfway through, or more than pi radians.
finishAnimation.toValue = #(0);
}
else
{
finishAnimation.duration = (2 - rotationRadians); //positive means less than halfway through, or less than pi radians
finishAnimation.toValue = #(M_PI * 2);
}

Rotate CAShapeLayer around it center without moving postion

I want to rotate a CAShapeLayer with objective c around it center point without moving it around. The CAShapeLayer contain UIBezierPath point of rect. I'm not able to rotate the CAShapeLayer becouse i dont know how. Please show me how tp rotate around it center without moving it postion.
Here is some code that does that:
//Create a CABasicAnimation object to manage our rotation.
CABasicAnimation *rotation = [CABasicAnimation animationWithKeyPath:#"transform"];
totalAnimationTime = rotation_count;
rotation.duration = totalAnimationTime;
//Start the animation at the previous value of angle
rotation.fromValue = #(angle);
//Add change (which will be a change of +/- 2pi*rotation_count
angle += change;
//Set the ending value of the rotation to the new angle.
rotation.toValue = #(angle);
//Have the rotation use linear timing.
rotation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
/*
This is the magic bit. We add a CAValueFunction that tells the CAAnimation we are modifying
the transform's rotation around the Z axis.
Without this, we would supply a transform as the fromValue and toValue, and for rotations
> a half-turn, we could not control the rotation direction.
By using a value function, we can specify arbitrary rotation amounts and directions, and even
Rotations greater than 360 degrees.
*/
rotation.valueFunction = [CAValueFunction functionWithName: kCAValueFunctionRotateZ];
/*
Set the layer's transform to it's final state before submitting the animation, so it is in it's
final state once the animation completes.
*/
imageViewToAnimate.layer.transform = CATransform3DRotate(imageViewToAnimate.layer.transform, angle, 0, 0, 1.0);
//Now actually add the animation to the layer.
[imageViewToAnimate.layer addAnimation:rotation forKey:#"transform.rotation.z"];
(That code is taken (and simplified) from my github project KeyframeViewAnimations)
In my project I'm rotating the layer of a UIImageView, but the same approach will work for any CALayer type.

CGAffineTransformMakeRotation with negative M_PI

I'm using CGAffineTransformMakeRotation to rotate a UIView inside an animation block, and I want to rotate it counterclockwise for 180º.
However when I put
myView.animateWithDuration(0.5)
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI))
it still rotates clockwise. What's confusing me is that if I write "manually" the value of M_PI, it works as expected
// this will rotate counterclockwise
myView.transform = CGAffineTransformMakeRotation(CGFloat(-3.14159265358979))
Why this behavior?
Edit: added I'm using an animation block
As I stated in my first comment and as Segii already wrote: the animation will take the shortest distance between where it's value is at now and where it is trying to go. The best solution imho is to use CABasicAnimation for forcing a counterclockwise rotation:
let anim = CABasicAnimation(keyPath: "transform.rotation")
anim.fromValue = M_PI
anim.toValue = 0
anim.additive = true
anim.duration = 2.0
myView.layer.addAnimation(anim, forKey: "rotate")
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI))
This will rotate it by 180 counter clockwise in one go.
The angle you specify in the last line + the fromValue is the starting angle -> 0. And then the rotation will go the distance (toValue - fromValue) = 0 - M_PI = - M_PI -> counter clockwise 180 degree rotation.
As I understood, you're trying to rotate view within animation block. animateWithDuration will always perform animation with the shortest way. So, M_PI and -M_PI gives the same destination position. The reason, why you get expected animation direction when setting it manually is that your manual value is really smaller that M_PI, so the shortest way to rotate is counterclockwise.
To get expected behaviour, you'll have to chain at least two animations,
splitting you rotation angle. For example
//first animation block
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI/2))CGAffineTransformMakeRotation;
//second animation block
myView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI/2))CGAffineTransformMakeRotation;
Swift 3.0
myView.transform = CGAffineTransform(rotationAngle: CGFloat(-M_PI/2))
Or, much more reliable, use CAKeyframeAnimation

calayer rotation

I'm attempting to rotate my calayer object in my callback for a rotation gesture recognizer as such:
CGFloat angle = [(NSNumber *)[hitlayer valueForKeyPath:#"transform.rotation.z"] floatValue];
hitlayer.transform = CATransform3DMakeRotation( (angle+90) / 180.0 * M_PI, 0.0, 0.0, 1.0);
[hitlayer setValue:[NSNumber numberWithFloat:(angle+90)] forKey:#"transform.rotation.z"];
This works fine the first time. But subsequent times, the value returned by transform.rotation.z is still the original value (not the transformed value) so the layer doesn't rotate any further. Any ideas what i'm missing ?
thanks
The transform property does not change the view's or layer's bounds or center. It just applies a transformation when it gets drawn, so it simply looks differently. So in your case, you have to keep track of the last degree value and add 90 on each call.

Resources