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.
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 viewcontroller with a view that I am dismissing using a UIView animation to scale it down to 0 before removing it. My code for dismissing it is:
[UIView animateWithDuration:_dismissAnimationDuration
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^(void) {
_menuContainerView.transform = CGAffineTransformMakeScale(0.0, 0.0);
}
completion:^(BOOL finished){
if ([_delegate respondsToSelector:#selector(popUpMenuDidClose)])
{
[_delegate popUpMenuDidClose];
}
[self.view removeFromSuperview];
[self removeFromParentViewController];
}];
That works perfectly when building from XCode 5 onto devices running both iOS 7 and iOS 8. But, as soon as I build to iOS 8 from XCode 6 (beta 6 and beta 7) the view just cuts away instead of animating. If that wasn't weird enough as soon as I change the target scale to (0.001, 0.001) it animates fine regardless of XCode version. Any ideas as to why I can't animate to an actual (0.0, 0.0) scale with XCode 6?
So after speaking with a developer at Apple the reasoning I got back was that some base frameworks need to work with the inverses of transform matrices quite often, and since there is no inverse for the zero matrix the animation just returns out to avoid crashing. Hopefully this post has helped others who ran into a similar situation.
If acceptable for you, set the scale values to 0.01 like so:
_menuContainerView.transform = CGAffineTransformMakeScale(0.01, 0.01);
Reference
I have the following super simple animation, I'm basically rotating a view 2 radians from its original angle/center, it rotates fine my only misunderstanding is why does the view move from its original position when the rotation occurs.
[UIView animateWithDuration:1.0 animations:^{
self.someView.transform = CGAffineTransformMakeRotation( 2 );
}];
Why does the view moves when rotated with the code above?
I'm currently trying to discern the information in the CGAffineTransform Reference.
Understanding the anchor point.
I found this threads but it doesn't show a concrete answer.
Why rotating imageView, it changes position?
Thanks a lot
You need to set the anchor point of your view to rotate around.
self.somview.layer.anchorPoint = CGPointMake(0.5, 0.5);
Then start the rotation.
From Apple documentations
#property(nonatomic) CGAffineTransform transform Changes to this
property can be animated. Use the beginAnimations:context: class
method to begin and the commitAnimations class method to end an
animation block. The default is whatever the center value is (or
anchor point if changed)
Link: https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CALayer_class/Introduction/Introduction.html#//apple_ref/occ/instp/CALayer/anchorPoint
image from here http://www.raywenderlich.com/9864/how-to-create-a-rotating-wheel-control-with-uikit
- As you see the anchor point is the point with the value from 0.0 - 1.0 for X and Y
when you rotate the rotation will be around these points
NOTE: you need to import QuartzCore
I am adding another answer due to #fs_tigre request. The problem is with the auto layouts in your xib file, unfortunately is it unknown why that affects the transform.
Now here is the steps I did to solve the issue:
1- first you need to get rid off your auto layout (yes, you have to)
uncheck Use Autolayout
2- remove all constraints and autoresizing masks for your view that will be rotated, as in the screenshot
(Here I have my blue box, see on the right autoresizing, nothing is selected)
I have made some changes for your rotation's code
self.someView.layer.anchorPoint = CGPointMake(0.5, 0.5);
// one degree = pi/180. so...
// rotate by 90
CGFloat radians = (M_PI/180) * 90;
[UIView animateWithDuration:1.0 animations:^{
self.someView.transform = CGAffineTransformRotate(self.someView.transform, radians);
}];
Click rotate and see the magic :)
The "anchor" of CGAffineTransformMakeRotation is the X,Y of the view. You can try this:
CGPoint center = self.someView.center;
[UIView animateWithDuration:1.0 animations:^{
self.someView.transform = CGAffineTransformMakeRotation( 2 );
self.someView.center = center;
}];
I am trying to animate a transform of a UIButton with CGAffineTransformRotate, and while it is performing the animation properly, it shifts the button about 15 pixels down and to the left before doing it. Here's the code performing the animated transformation:
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionTransitionNone
animations:^{
self.addCloseButton.transform = CGAffineTransformRotate(self.addCloseButton.transform, degreesToRadians(45));
}
completion:nil];
When I reverse the transformation it does the same thing except it shifts it back to its original position before animating (15 pixels up and 15 pixels to the right), and I do that with this code:
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionTransitionNone
animations:^{
self.addCloseButton.transform = CGAffineTransformIdentity;
}
completion:nil];
Why would this shift occur? The button was created using interface builder, and the shift happens immediately even if I set the animation duration higher or add a delay.
I figured it out: turns out having "Use Autolayout" selected on my xib (which adds a bunch of auto constraints) messes things up when trying to use transforms. Turning it off fixed my problem.
It is posible to fix this while still using auto layout and storyboards. See my answer on this question: https://stackoverflow.com/a/19582959
I am trying to animate the alpha value of a MapKit overlay view (specifically an MKCircleView) in iOS 5 using the following code:
-(void) animateCircle:(MKCircle*)circle onMap:(MKMapView*) mapView
{
MKCircleView * circleView = (MKCircleView*) [mapView viewForOverlay:circle];
UIViewAnimationOptions options = UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionTransitionNone;
[UIView animateWithDuration:5.0
delay:0.0
options:options
animations:^(void) { circleView.alpha = 0.9; }
completion:^(BOOL finished) {}
];
}
The alpha value of the overlay is changing as I want, but it is jumping there instantaneously rather than animating over the specified duration.
Can anyone suggest what might be wrong? Perhaps animation on overlay views os more complex with blocks than I had thought.
Core Animation has interesting behavior when concurrent animations effect the same view... If you try to animate a view before the view's last animation finished, it will assume you intended the subsequent animation to start from the desired end-state of the initial one. This can result in jumps of frames as well as jumps of alpha values.
In your case, this view is likely being animated by something else. Try locating and removing the other animation / or'ing in UIViewAnimationOptionBeginFromCurrentState to its options.