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?
Related
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
I have a method to perform a small shake animation. This animation works somewhat, but it rotates every time I call it from landscape position. When the animation is called while the simulator is in landscape, the entire view rotates itself to portrait, and then performs the animation. The rotation itself is not animated, it just changes suddenly and with no delay. Everything in the view shifts as well, all buttons, text fields, image views, etc.
Animation Code:
- (void)shakeView
{
CGFloat t = 8.0;
CGAffineTransform translateRight = CGAffineTransformTranslate(CGAffineTransformIdentity, 0.0, t);
CGAffineTransform translateLeft = CGAffineTransformTranslate(CGAffineTransformIdentity, 0.0, -t);
self.view.transform = translateLeft;
[UIView animateWithDuration:0.07 delay:0.0 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{[UIView setAnimationRepeatCount:3.0];
self.view.transform = translateRight;
}
completion:^(BOOL finished){
if (finished)
{
[UIView animateWithDuration:0.05 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{self.view.transform = CGAffineTransformIdentity;
}
completion:NULL];
}
}];
}
Honestly, I don't do much with animations, so I have no idea what I am doing wrong here, much less what else I should try or where I should look for an answer.
I am looking to keep the animation while also keeping the orientation.
The issue is that the view has a non identity transform on it already. In order for a view to display properly in landscape, the system applies a transform on it that rotates the view. The transform of the view is CGAffineTransformIdentity ONLY when the device is in UIDeviceOrientationPortrait. This orientation rotation ONLY applies to the root child of the application, the first subview of the window. When you are initializing your translation animation with:
CGAffineTransform translateRight = CGAffineTransformTranslate(CGAffineTransformIdentity, 0.0, t);
You are telling the rotated view to ignore its rotation and snap back to its orientation in portrait mode. You can solve this issue by doing one of two things:
Make the animated view a subview of the root view. This has the advantage that the system will still handle rotations for free, and there isn't any chance that your animation will screw up the the orientation changes. Furthermore, the code will be simpler and easier to maintain in the end.
Store the initial transformation of the view, and apply all transformations to it.
I'll try to do #2 here.
- (void)shakeView
{
CGFloat t = 8.0;
//store the initial transform and use it to create the animated transforms
CGAffineTransform initialTransform = self.view.transform;
CGAffineTransform translateRight = CGAffineTransformTranslate(initialTransform, 0.0, t);
CGAffineTransform translateLeft = CGAffineTransformTranslate(initialTransform, 0.0, -t);
self.view.transform = translateLeft;
[UIView animateWithDuration:0.07
delay:0.0
options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat
animations:^{
[UIView setAnimationRepeatCount:3.0];
self.view.transform = translateRight;
}
completion:^(BOOL finished){
if (finished)
{
[UIView animateWithDuration:0.05
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
//restore the initial transform
self.view.transform = initialTransform;
}
completion:NULL];
}
}];
}
Note - As of iOS 8, the root view of the UIWindow is no longer transformed on rotation. Instead the bounds of the UIWindow itself changes. For another question about this, see here: UIWindow frame in ios 8 different from ios 7 in Landscape
I have set up the following animation to rotate between views of different sizes. The midpoint of the animation seems to have a flicker as the new, taller view comes into view. Is there anything I can do to smoothen the transition.
newView.layer.transform = CATransform3DMakeRotation(M_PI_2, 0.0, 1.0, 0.0);
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{oldView.layer.transform = CATransform3DMakeRotation(M_PI_2, 0.0, -1.0, 0.0);}
completion:^(BOOL finished) {
[oldView removeFromSuperview];
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{newView.layer.transform = CATransform3DMakeRotation(M_PI_2, 0.0, 0.0, 0.0);}
completion:nil];
}];
Got this working thanks to this thread, so I thought I'd share my to-from 3D transform using the m34 matrix.
UIView *toView = // show this
UIView *fromView = // hide this one
// set up from
CATransform3D fromViewRotationPerspectiveTrans = CATransform3DIdentity;
fromViewRotationPerspectiveTrans.m34 = -0.003; // 3D ish effect
fromViewRotationPerspectiveTrans = CATransform3DRotate(fromViewRotationPerspectiveTrans, M_PI_2, 0.0f, -1.0f, 0.0f);
// set up to
CATransform3D toViewRotationPerspectiveTrans = CATransform3DIdentity;
toViewRotationPerspectiveTrans.m34 = -0.003;
toViewRotationPerspectiveTrans = CATransform3DRotate(toViewRotationPerspectiveTrans, M_PI_2, 0.0f, 1.0f, 0.0f);
toView.layer.transform = toViewRotationPerspectiveTrans;
[UIView animateWithDuration:1.0
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{fromView.layer.transform = fromViewRotationPerspectiveTrans; }
completion:^(BOOL finished) {
[fromView removeFromSuperview];
[UIView animateWithDuration:1.0
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{toView.layer.transform = CATransform3DMakeRotation(M_PI_2, 0.0, 0.0, 0.0);}
completion:nil];
}];
I was halfway there, but the missing piece, setting the m34 cell value of the transformation matrix, did the trick.
As David pointed out, your code doesn't make sense as written. You're setting the final rotation of your newView to a rotation around nothing, which will PROBABLY be equivalent to the identity matrix, but I'm not sure.
Here's what I would try (I'm tired, so let's see if I can explain this coherently...)
Animate the oldView from 0 to pi/2 as animation step 1. Set the newView to -pi/2 before beginning the second animation (rotated 90 degrees the other way.)
In the completion method, remove the old view and start an animation to set the new view's rotation back to zero. That will cause the new view to look like it's continuing to flip around in a 180 degree flip.
Here's the tricky part. Calculate the difference in size (horizontal and vertical) between the old and new views. Add (concatenate) a scale transform along with the rotation, so that when the first part of the rotation is finished, it is scaled to the average of the old and new size. Pseudocode might look like this:
//Scale to apply to oldView for the first part of the animation:
scale height = ((oldView.size.height+newView.size.height)/2) / oldView.size.height
scale width = ((oldView.size.width+newView.size.width)/2) / oldView.size.width
/*
Before beginning the second part of the animation, rotate newView to -pi/2,
and scale it by an amount that makes it the same size that oldView will be
at the end of the first animation (the average of the sizes of both views)
*/
newView scale height = ((oldView.size.height+newView.size.height)/2) /
newView.size.height
newView scale width = ((oldView.size.width+newView.size.width)/2) /
newView.size.width
in the completion block, remove oldView from it's superview,
and animate newView back to the identity transform.
If my approach is right, at the end of the first animation, oldView should be scaled to a size halfway between the sizes of oldView and newView.
The second animation, triggered in the completion block of the first, will start out with newView being the same size that oldView was scaled to at the end of the first animation. The second animation will end with the new view rotating into place and scaling back to it's original size.
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.
I have a view which I would like to transform in two ways. First I'd like to move it on its y-axis. Then, I'd like to zoom in on it.
However, when I use the following code, the object is first moved and then moved back to its original position while being zoomed.
Is there a way to apply the two transformation at once without cancelling the first?
Sorry if this is basic, but any help would be very much appreciated!
[UIView animateWithDuration:animationDuration
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
currentCover.transform = CGAffineTransformMakeTranslation(0, 0-keyboardTop+35);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:animationDuration
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[currentCover setTransform:CGAffineTransformMakeScale (1.3, 1.3)];
}
completion:^(BOOL finished) { }
];
}
];
You should multiply one transform by the other. Each transform (scale and translate) are transform matrices. To combine them, simply multiple one by the other before using it. The order of the multiplication determines the order that the tranforms are applied
I'd similar issue where my view moves back to original position.
Documentation of CGAffineTransformMakeTranslation reads "it constructs a new translation matrix from x and y values that specify how much to move the origin." I don't think it applies actual transformation.
I think you'd rather apply translation by calling CGAffineTransformTranslate(). At least it works for me!!
Here we are creating the two changes (CGAffineTransform's) you want to do together, and apply the transformation to the view.
CGAffineTransform translate = CGAffineTransformMakeTranslation(0, 0-keyboardTop+35);
CGAffineTransform scale = CGAffineTransformMakeScale(1.3, 1.3);
CGAffineTransform transform = CGAffineTransformConcat(translate, scale);
[UIView animateWithDuration: animationDuration
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
currentCover.transform = transform;
}completion:^(BOOL finished){
}];