CGAffineTransformMakeScale causes rotation - ios

I am facing this weird issue, where CGAffineTransformMakeScale is causing rotation. The name suggests that it should only cause scaling, but that's not the case.
[UIView animateWithDuration:1.0 animations:^{
self.logoView.transform = CGAffineTransformMakeScale(6.0, 6.0);
} completion:^(BOOL finished) {
if (finished) {
[UIView animateWithDuration:3.0 animations:^{
self.logoView.transform = CGAffineTransformMakeScale(-6.0, -6.0);
} completion:nil];
}
}];
I would assume that the view should scale 6x and scale back 6x. However, the second animation causes a 90-degree anti-clockwise rotation of the image! Can anyone explain what's going on?

Use the relative scaling transform rather than making an absolute one. So:
self.logoView.transform = CGAffineTransformScale(self.logoView.transform,
6, 6)

you shouldn't be scaling to CGAffineTransformMakeScale(-6.0,-6.0) in order to reverse what you already did (unless thats what you want, but i doubt it) but instead animate back to CGAffineTransformMakeScale(1.0,1.0). a shortcut is CGAffineTransformIdentity constant, which is an empty transform. So change that last line in the completion block to
self.logoView.transform = CGAffineTransformIdentity;
to explain whats currently going on, by scaling to a negative value in both x and y axes you are turning the view 'inside out' in both dimensions at the same time

Related

My scale and rotate animation not performing as intended (CGAffineTransformConcat)

I want to perform scale and rotate animation simultaneously but instead only scale animation is performed over time as intended, while rotation takes place instantly.
this is the code I use:
CGAffineTransform scale = CGAffineTransformMakeScale(scaleRatio, scaleRatio);
CGAffineTransform rotate = CGAffineTransformMakeRotation(rotationRatio);
CGAffineTransform entireAnimation = CGAffineTransformConcat(scale, rotate);
[UIView animateWithDuration:0.5f
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^(void){
view.transform = entireAnimation;
}completion:^(BOOL finished){
}];
even if I change concat order like this, issue remains the same:
CGAffineTransform entireAnimation = CGAffineTransformConcat(rotate,scale);
And the issue is: Instead of both scaling and rotating at the same time, my view is instantly rotated and scaling animation is performed over time.
How can I achieve simultaneous transformation animation ?
EDIT: Rotation doesn't work if rotation ratio is 2*M_PI. Why doesn't the 360 degrees rotation work and how to make it work?

Animation of CGAffineTransform in iOS8 looks different than in iOS7

I'm trying to find a reason why animation of UIView transform property looks different in iOS 8 than iOS 6/7.
For a simple example, prior to iOS 8:
myView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, 1.57);
[UIView animateWithDuration:5 animations:^{
myView.transform = CGAffineTransformTranslate(plane.transform, 100, 0);
}];
gives expected result, "myView" is rotated 90 degrees and moves down, but in iOS8 when translation is animated it starts at a point that I couldn't find explanation for (which breaks the animation).
Does anyone know the explanation for it? Thanks in advance!
CGAffineTransformIdentity behaves differently on ios7 and ios8. This has to do with auto-layout and size classes. The solution is to remove constraints that conflict with the animation on ios7.
// solve the constraint-animation problem
if(NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1) {
// iOS7 remove constraints that conflict with animation
if (self.centerYAlignment != nil) {
self.view.removeConstraint(self.centerYAlignment) //is an IBOutlet
}
} else {
// iOS8 constraint animations are fine
}
I think the reason is just iOS8 bug, but I use CAAnimation instead, and it works as expected on iOS8.
I had problems with jerky rotation transform in iOS7 as well. Solved this by nesting my rotated view inside a container and centering the rotated view inside.
I'm also experiencing the same issue with scaling. I guess it could be the same with rotation. Could you try this?
myView.transform = CGAffineTransformConcat(myView.transform , CGAffineTransformMakeRotate(1.57));
[UIView animateWithDuration:5 animations:^{
myView.transform = CGAffineTransformTranslate(plane.transform, 100, 0);
}];
Maybe it's also necessary to use CGAffineTransformMakeTranslate and CGAffineTransformConcat that as well, I'm not sure.
The worst part about this is: You would have to do if/else on iOS versions, because this would look weird on iOS 7. I hope this is getting fixed by Apple before or with iOS 8 release.
I agree with Pbk that it has to do with size classes in io8. uiviewcontrollers need to be resized with uitraitcollections depending on the device orientation. Otherwise, you get a uiviewcontroller in portrait mode, while the phone is in landscape mode, when you try to rotate it. So the correct steps are to rotate AND override uitraitcollections
This isn't entirely related, but I was struggling with CGAffineTransformScale not working at all on iOS7 in a fairly complicated animation. It turns out my problem was iOS7 cannot calculate CGAffineTransformScale with CGAffineTransformRotate at the same time. In iOS7, the last animation call you make is the only one that gets animated, so only the rotation was occurring. This bug is fixed in iOS8.
My solution is to simplify my animation for iOS7, only turning on the fancy stuff in iOS8:
//Pre-animation setup:
CGFloat radians = (M_PI/180) * (-15); //Get a human-readable number in degrees
self.badgeImage.alpha = 0; //Start the image as invisible
self.badgeImage.transform = CGAffineTransformScale(self.badgeImage.transform, 1.5, 1.5); //Start the image as scaled bigger than normal
if(NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1) { //See below. We will not be rotating the image in iOS7
self.badgeImage.transform = CGAffineTransformRotate(self.badgeImage.transform, radians); //Rotate the image if iOS8
}
//Animation Pieces:
//Fade in
[UIView animateWithDuration: 0.5
delay:0
options:0
animations:^{
self.badgeImage.alpha = 1.0f; //Return image to opaque
}
completion:NULL];
//Scale with bounce
[UIView animateWithDuration: 1.1
delay:0
usingSpringWithDamping:0.3 //Not as good as Android's bounce interpolator, but I'll take it
initialSpringVelocity:-1.0f //A negative velocity here makes the animation appear more like gravity than spring
options:0
animations:^{
self.badgeImage.transform = CGAffineTransformScale(self.badgeImage.transform, 0.67, 0.67); //Return image to its original size. These arguments are relative to its current scale.
}
completion:NULL];
//Rotation
if(NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1) { //This second animation call negates the first one on iOS7, so remove it.
[UIView animateWithDuration: 0.9
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.badgeImage.transform = CGAffineTransformRotate(self.badgeImage.transform, (radians * -1)); //Rotate the image back to its original orientation if iOS8
}
completion:NULL];
}
Of course, you can still combine multiple effects in iOS7 if you use the confusingly-named CGAffineTransformMakeScale() function. For instance, in the pre-animation setup, you can set both a rotation AND a scale, then set call CGAffineTransformMakeScale(1,1) to reset the image to its original metrics (MakeScale's arguments are specific, not relative - even more confusing!). This isn't always preferable, such as my example above where "bouncing" the animation would also bounce the rotation.

UIView animation with CGAffineTransformMakeScale changes size instantly with decimal number

- (void)startAnimation {
//reverse - shrinking from full size
if (_reversed == YES) {
//self.transform = CGAffineTransformMakeScale(1.0, 1.0);
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionCurveLinear animations:^{
self.transform = CGAffineTransformMakeScale(0.1, 0.1); //this line does it instantly
self.alpha = 0;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
} else {
//non reverse - expanding from middle
self.transform = CGAffineTransformMakeScale(0.001, 0.001);
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionCurveLinear animations:^{
self.transform = CGAffineTransformMakeScale(1.0, 1.0);
self.alpha = 0;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
}
The non reverse piece of code does work fine as I expect, however when I do the _reversed == YES bit, the transformation inside the animation block happens instantly. If I comment that line of code, then the view stays the right size, but if i uncomment it then it shrinks instantly but the alpha still does the fade animation. Why does this happen?
Edit: I figured out what happened but I don't know how to fix it. The view does do the animation, only the size of the view changes instantly but it still 'slides' into the centre as if it is shrinking (what you see is just a small rectangle sliding to the middle as if it's the top left corner of the object). If I scale the view to 2 first, then scale down to 1 the animation works fine, its only when going from 1 to a decimal number that it doesn't work. Also I used draw rect to create an object with core graphics and the transform problem affects that, but not the actual frame if a set background colour.
I was running into a similar issue, where the position of the view would suddenly jump before animating in a change of transform with CGAffineTransformMakeScale. I noticed that the size of the "jump" seemed to be proportional to the scaling that would occur later in the animation.
I could fix this problem by finding that in a viewWillLayoutSubviews() override, I was setting the frame of the view being animated. As a rule, don't set the frames of views that will have a non-identity transform. So in the viewWillLayoutSubviews() override, I set the view's bounds and layer.position instead and now the animation is smooth as silk.

CATransform3DMakeRotation animating trough zero (from -45 to +45 angle)?

I'm trying to do a simple horizontal flip animation with a bounce effect.
[UIView animateWithDuration:0.4 delay:0 options:UIViewAnimationCurveEaseIn animations:^{
front.layer.transform=CATransform3DMakeRotation(DEGREES_TO_RADIANS(90), -1.0,0.0,0.0); // flip halfway
}
completion:^(BOOL finished) { // swap view
front.alpha=0;
back.alpha=1;
[UIView animateWithDuration:0.5 animations:^{ // flip remaining + bounce
back.layer.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(45), 1.0,0.0,0.0); // final + 45
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.25 animations:^{
back.layer.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(-22.5), 1.0,0.0,0.0); // bounce back
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.125 animations:^{
back.layer.transform = CATransform3DMakeRotation(0, 1.0,0.0,0.0); // final
}];
}];
}];
}];
Works well except for the 'bounce'. The -22.5 rotation transitions to 0 and than back to 22.5 instead of continuing to -22.5.
I've tried various values and also including an intermediate nested block that transitions the bounce to '0' before going to negative. Didn't help. The rotation always animates to a positive instead of a negative angle.
Just as a test, changing the 'final + 45' to a negative angle does however stop the animation at the desired angle. So the angles themselves are ok.
Problem seems to be doing a 'counter clockwise' rotation starting from zero or going trough zero. Values smaller than zero than always get converted to a positive angle.
Independent if the above is the right technique for implementing a bounce effect, how would a (nested) layer animation be constructed that rotates via CATransform3DMakeRotation from a positive (45) to a negative angle (-45)?
You are rotating around the x-axis. Without a perspective on your transform, how would you see the difference between positive and negative angles? Without perspective, both positive and negative rotations look the same (the decrease the height). You can apply perspective to your transform by changing the value in the third row and fourth column of your transform matrix by setting .m34. Search for "Core Animation perspective" and you will find a good explanation.
I ran your code above (on a simple view, filled with orange).
To more easily be able to see the rotation and the difference between positive and negative angles, I changed it so that it rotates around the z-axis.
These are four screenshots that I took during the animation. As you can see, the negative angle works, unless I misunderstood what you were trying to do.

affine translation prevents CGPoint relocation

I expanded and moved a label (instructionLabel), then returned it to its original size but left it in the new position.
[UIView animateWithDuration:0.5
animations:^{
CGAffineTransform scale = CGAffineTransformMakeScale(2.0, 2.0);
CGAffineTransform translate = CGAffineTransformMakeTranslation(0.0, -70.0); // up 70
self.instructionsLabel.transform = CGAffineTransformConcat(scale,translate);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.25
animations:^{
CGAffineTransform scale = CGAffineTransformMakeScale(1.0,1.0);
CGAffineTransform translate = CGAffineTransformMakeTranslation(0,-70.0); //left in place up 70
self.instructionsLabel.transform = CGAffineTransformConcat(scale, translate);
}
completion:^(BOOL finished) {}
];
Later, I explicitly use CGPointMake to put the label back in its original spot, but it remains in the translated position (70 pts up from its original place).
instructionsLabel.frame = CGRectMake(384, 601, 655, 40);
//Adding this doesn't make any difference, in or out.
instructionsLabel.center=CGPointMake(384, 601);
I have verified by Breaks and NSLog that the CGPointMake and CGRectMake statements are reached...they just don't work after that affine transformation. Does anyone know why?
(I don't want to move the label back immediately after the translation routine, but I might have to if I can't figure out why the CGPointMake routine doesn't do it.)
Thanks for any suggestions.
-Rob
Unless Im mistaken one of the major reason for using affine transform to scale, move etc views is that, that you can later set transform to CGAffineTransformIdentity this will cancel out any transforms you have applied. I believe that your problem here is that you are setting center to what ever position it of before you applied translate transform. View indeed moves to that point + whatever transform is applied to that view. So just set transform identity.

Resources