Autolayout violates translation rotation animation - ios

I have UIView, I want to animate it with rotation. I want to use autolayout too. I already saw a lot of stackoverflow common questions, but I didn't find any applicable solution.
So, let's start with my interface and animation code; I have a UIView with image, which must be rotated. And right now I have button, that activates rotation. In the screenshot you can see red cross, this is must be the center of rotation (right now it's on the image, but I want to make center of rotation outside of the rotated UIView, I know that this could be archived with AnchorPoint).
This is my rotation animation code:
#define ROTATE_DURATION 3.0
- (IBAction)rotateArrow {
CGAffineTransform transform = self.hand.transform;
NSLog(#"layer possition before animation x: %f; y: %f",self.hand.layer.position.x,self.hand.layer.position.y);
[UIView animateWithDuration:ROTATE_DURATION/3 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
self.hand.transform = CGAffineTransformRotate(transform, 2*M_PI/3) ;
[self.hand layoutIfNeeded];
NSLog(#"layer possition after animation 1 x: %f; y: %f",self.hand.layer.position.x,self.hand.layer.position.y);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:ROTATE_DURATION/3 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
self.hand.transform = CGAffineTransformRotate(transform, -2*M_PI/3) ;
[self.hand layoutIfNeeded];
NSLog(#"layer possition after animation 2 x: %f; y: %f",self.hand.layer.position.x,self.hand.layer.position.y);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:ROTATE_DURATION/3 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
self.hand.transform = CGAffineTransformRotate(transform, 0) ;
[self.hand layoutIfNeeded];
NSLog(#"layer possition after animation 3 x: %f; y: %f",self.hand.layer.position.x,self.hand.layer.position.y);
}
completion:^(BOOL finished) {
}];
}];
}];
}
So, what's the problem:
When rotation is in progress UIView changes it's center and layer.position property, that's why my UIView is "jumps" when animated. If autolayout is turned off, animation is okay. I watched WWDC 2012 "Auto Layout by Example" and found that is I would use [self.hand layoutIfNeeded]; all be just fine, but it isn't at all. Animation became a little smoother, but I see this "jumps". So, here is my "output".
When it's animating, UIView goes to right as you can see on image, and than backs to normal position. How can I fix this "jumps"?
Here is a log:
layer possition before animation x: 160.000000; y: 99.500000
layer possition after animation 1 x: 197.349030; y: 114.309601
layer possition after animation 2 x: 197.349030; y: 114.309601
layer possition after animation 3 x: 160.000000; y: 99.500000
Thanks.

When I layout my views, all started to work just fine. When you make a transform, it's important to remember, that autolayout always calculate your views bounds and frame, based on constraints that you set. So, in my case, I just added center vertical and horizontal align, width and height to my rotated view, so the autolayout mechanism knows when exactly my view is. And all goes just fine. Here is a good autolayout tutorial:
http://www.raywenderlich.com/50319/beginning-auto-layout-tutorial-in-ios-7-part-1

You may be running into the general incompatibility between autolayout and transforms. Take a look there for more details, but the short story is that you probably don't want to use autolayout on the same view that is being transformed.

The solution is:
Create a container view that uses auto layout.
The rotate view (that has the transform applied to it) should use auto
resizing.
Add the rotate view to the container view.
Create the views:
// Create container view
{
self.mContainerView = [[UIView alloc] init];
self.mContainerView.translatesAutoresizingMaskIntoConstraints = NO;
}
// Create view who will have the transform
{
self.mRotateView = [UIButton buttonWithType:UIButtonTypeCustom];
self.mRotateView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.mContainerView addSubview:self.mRotateView];
}
Handle the transform:
{
CGFloat lAngle = 180.0;
[UIView animateWithDuration:0.5 delay:0.0 options: UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveLinear animations:^{
CGAffineTransform transform = CGAffineTransformMakeRotation(lAngle);
self.mRotateView.transform = transform;
} completion:NULL];
}

Related

ios Animation Image Expanding from within a circle

I have a page in my app in which there is user's profile pic in a circle as its there in Twitter profile page.
I have to add a functionality such that when I tap on it, the image should expand. Animation needs to be added in such a way that a new bigger circle should slowly appear from the centre of the smaller circle and reach the centre of the screen.
Any ideas as to how this can be implemented?
Try UIView Animation block with changing bigger circle's position, size, alpha, and corner radius (if required).
Place your bigger circle (with smaller size initially) on top of smaller circle and hide it initially. Then before animation, un-hide it and animate using a block like this:
[UIView animateWithDuration:1.0 animations:^{
// set bigger circle destination position i.e. centre of screen
// set bigger circle final size
// set other properties like alpha, corner radius etc
} completion:nil];
Use CATransform3DScale transform and UIView Animation
CATransform3D avatarTransform = CATransform3DIdentity;
avatarTransform = CATransform3DScale(avatarTransform, avatarScaleFactor,avatarScaleFactor, 0);
[UIView animateWithDuration:1.0 animations:^{
//Change the frame to reach the center of screen
self.avatar.layer.transform = headerTransform
} completion:nil]
You can simply use CGAffineTransformScale and scale your ImageView inside UIView's animateWithDuration block.
imgV.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.0, 0.0);
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut
animations:^{
imgV.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.0, 1.0);
}
completion:^(BOOL finished) {
NSLog(#"done");
}];
For further references you can look into these Stackoverflow questions :
Animate a UIImageView
iOS View Transform Animation

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.

Shake animation rotating display as well as animating

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

Weird behaviour happens when using UIView animate and CGAffineTransform

I created some animations in my project. Basically, I use UIView animate and CGAffineTransform, but a very strange thing happened and I have no idea. Hope someone can help me solve this problem. Thanks in advance.
This is the strange thing:
After the user clicks on a button, the button slides off screen and another two buttons slide on the screen (I just changed the center point of these buttons to achieve this animation). And, some time later, a view on the screen start shaking (I use CGAffineTransform to achieve this).
At this moment, the strange thing happens - the button that previous slid off screen show up at its original position again and the other two buttons disappear (No animation, just shows up and disappear).
The following is the related code,
1) Button slide off and slide in animation related code
- (IBAction)start:(id)sender
{
// 1. Slide in cancel and pause button
[UIView animateWithDuration:0.5f animations:^{
[startButton setCenter:CGPointMake(startButton.center.x + 300.0f, startButton.center.y)];
[cancelButton setCenter:CGPointMake(cancelButton.center.x + 300.0f, cancelButton.center.y)];
[pauseButton setCenter:CGPointMake(pauseButton.center.x + 300.0f, pauseButton.center.y)];
} completion:^(BOOL finished) {
if (finished) {
NSLog(#"Move finished");
}
}];
}
2) The view shaking animation related code
- (void)shakeView:(UIView *)viewToShake
{
CGFloat t = 2.0;
CGAffineTransform translateRight = CGAffineTransformTranslate(CGAffineTransformIdentity, t, 0.0);
CGAffineTransform translateLeft = CGAffineTransformTranslate(CGAffineTransformIdentity, -t, 0.0);
viewToShake.transform = translateLeft;
[UIView animateWithDuration:0.07 delay:0.0 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{
[UIView setAnimationRepeatCount:2.0];
viewToShake.transform = translateRight;
} completion:^(BOOL finished) {
if (finished) {
[UIView animateWithDuration:0.05 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
viewToShake.transform = CGAffineTransformIdentity;
} completion:nil];
}
}];
}
This isn't weird, it's a common problem with
auto layout. If you move UI elements by changing frames, then as soon as something else takes place that requires laying out views, the moved views will revert to the position defined by their constraints. To fix it, you either need to turn off auto layout, or do your animations by changing constraints not frames.
I have tried your code in my test project. The problem is probably because you are using Autolayout in your xib.
Please checking your xib file and uncheck the Autolayout property.

Smooth horizontal flip using CATransform3DMakeRotation

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.

Resources