ios rotation animation with CGAffineTransformRotate - ios

I want to perform some rotation animation. Right now I do it like that:
#define degreesToRadians(x) (M_PI * x / 180.0)
[self.crossButton setTransform:CGAffineTransformRotate(self.crossButton.transform, degreesToRadians(-rotationDegree))];
but when I pass for instance 360 degrees nothong happens. When I pass value above 180 it starts not wokring well. Do you know what im doing wrong?

Your problem is that 'setTransformation' works with matrix rotation. For this reason you will always get the shortest way to the end result. When you pass in a 360 degree rotation, your object will be the same after the transformation. For this reason, the transformation will just do nothing, since it is already where it is supposed to end up. For values between 180 and 360 degrees your rotation will be 'backwards', since again. The rotation uses the 'shortest' path to the end result.
You can try this code:
UIView* toRotate = VIEW_TO_ROTATE;
CGFloat degreesToRotate = DEGREES;
CGFloat animationTime = TOTAL_ANIMATION_TIME;
NSInteger intervals = ((int)degreesToRotate)/179.9;
CGFloat rest = degreesToRotate-(intervals*179.9);
CGFloat radInterval = degreesToRotate>=0?179.9:-179.9;
CGFloat radRest = (M_PI * rest / 180.0);
CGFloat intervalTime = (1-(radRest/M_PI/2))/intervals;
CGFloat restTime = (radRest/M_PI/2)/intervals;
[UIView animateKeyframesWithDuration:animationTime
delay:0.0f
options:UIViewKeyframeAnimationOptionCalculationModeLinear
animations:
^{
for (int i=0; i<intervals; i++) {
[UIView addKeyframeWithRelativeStartTime:intervalTime*i relativeDuration:intervalTime animations:^{
toRotate.transform = CGAffineTransformConcat(toRotate.transform, CGAffineTransformMakeRotation(radInterval));
}];
}
[UIView addKeyframeWithRelativeStartTime:intervalTime*intervals relativeDuration:restTime animations:^{
toRotate.transform = CGAffineTransformConcat(toRotate.transform, CGAffineTransformMakeRotation(radRest));
}];
} completion:^(BOOL finished) {
}];
Make sure to replace VIEW_TO_ROTATE,DEGREES and TOTAL_ANIMATION_TIME with the values you need!

Related

How to chain CGAffineTransform together in separate animations?

I need two animations on a UIView:
Make the view move down and slightly grow.
Make the view grow even bigger about its new center.
When I attempt to do that, the second animation starts in a weird location but ends up in the right location and size. How would I make the second animation start at the same position that the first animation ended in?
#import "ViewController.h"
static const CGFloat kStartX = 100.0;
static const CGFloat kStartY = 20.0;
static const CGFloat kStartSize = 30.0;
static const CGFloat kEndCenterY = 200.0;
#interface ViewController ()
#property (nonatomic, strong) UIView *box;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.box = [[UIView alloc] initWithFrame:CGRectMake(kStartX, kStartY, kStartSize, kStartSize)];
self.box.backgroundColor = [UIColor brownColor];
[self.view addSubview:self.box];
[UIView animateWithDuration:2.0
delay:1.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:0
animations:^{
self.box.transform = [self _transformForSize:50.0 centerY:kEndCenterY];
}
completion:^(BOOL finished) {
[UIView animateWithDuration:2.0
delay:1.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:0
animations:^{
self.box.transform = [self _transformForSize:100.0 centerY:kEndCenterY];
}
completion:^(BOOL finished) {
}];
}];
}
- (CGAffineTransform)_transformForSize:(CGFloat)newSize centerY:(CGFloat)newCenterY
{
CGFloat newScale = newSize / kStartSize;
CGFloat startCenterY = kStartY + kStartSize / 2.0;
CGFloat deltaY = newCenterY - startCenterY;
CGAffineTransform translation = CGAffineTransformMakeTranslation(0.0, deltaY);
CGAffineTransform scaling = CGAffineTransformMakeScale(newScale, newScale);
return CGAffineTransformConcat(scaling, translation);
}
#end
There's one caveat: I'm forced to use setTransform rather than setFrame. I'm not using a brown box in my real code. My real code is using a complex UIView subclass that doesn't scale smoothly when I use setFrame.
This looks like it might be a UIKit bug with how UIViews resolve their layout when you apply a transform on top of an existing one. I was able to at least get the starting coordinates for the second animation correct by doing the following, at the very beginning of the second completion block:
[UIView animateWithDuration:2.0
delay:1.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:0
animations:^{
self.box.transform = [self _transformForSize:50.0 centerY:kEndCenterY];
}
completion:^(BOOL finished) {
// <new code here>
CGRect newFrame = self.box.frame;
self.box.transform = CGAffineTransformIdentity;
self.box.frame = newFrame;
// </new code>
[UIView animateWithDuration:2.0
delay:1.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:0
animations:^{
self.box.transform = [self _transformForSize:100.0 centerY:kEndCenterY];
}
completion:^(BOOL finished) {
}];
}];
Using the same call to -_transformForSize:centerY: results in the same Y translation being performed in the second animation, though, so the box ends up further down in the view than you want when all is said and done.
To fix this, you need to calculate deltaY based on the box's starting Y coordinate at the end of the first animation rather than the original, constant Y coordinate:
- (CGAffineTransform)_transformForSize:(CGFloat)newSize centerY:(CGFloat)newCenterY
{
CGFloat newScale = newSize / kStartSize;
// Replace this line:
CGFloat startCenterY = kStartY + kStartSize / 2.0;
// With this one:
CGFloat startCenterY = self.box.frame.origin.y + self.box.frame.size.height / 2.0;
// </replace>
CGFloat deltaY = newCenterY - startCenterY;
CGAffineTransform translation = CGAffineTransformMakeTranslation(0.0, deltaY);
CGAffineTransform scaling = CGAffineTransformMakeScale(newScale, newScale);
return CGAffineTransformConcat(scaling, translation);
}
and it should do the trick.
UPDATE
I should say, I consider this "trick" more of a work-around than an actual solution, but the box's frame and transform look correct at each stage of the animation pipeline, so if there's a true "solution" it's eluding me at the moment. This trick at least solves the translation problem for you, so you can experiment with your more complex view hierarchy and see how far you get.

Scale animation in background from another anchorPoint

Ive done a Scaling animation for background image with an arrow in it,well by scaling the image(it includes the arrow) meaning its a whole background. it scales from the center of the background. but i want to start scaling from the center of arrow.
i want to achieve this :
Then to become with scale:
What i have to change in my code:
self.blueBackground.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1);
[UIView animateWithDuration:5 animations:^{
self.blueBackground.transform = CGAffineTransformScale(CGAffineTransformIdentity, 30, 30);
} completion:^(BOOL finished) {
}];
With my current code the scale start from center of background so the arrow end up at the right (out the bounds of screen).
Try something like this:
- (void)animateArrow
{
self.blueBackground.transform = CGAffineTransformIdentity;
[UIView animateWithDuration:5 animations:^{
ScaleViewAboutPointInBounds(self.blueBackground, arrowCenter, 30);
} completion:^(BOOL finished) {
}];
}
static void ScaleViewAboutPointInBounds(UIView *view, CGPoint point, CGFloat scaleAmount)
{
CGFloat xAnchor = view.layer.anchorPoint.x * CGRectGetWidth(view.bounds);
CGFloat yAnchor = view.layer.anchorPoint.y * CGRectGetHeight(view.bounds);
CGFloat xOffset = point.x - xAnchor;
CGFloat yOffset = point.y - yAnchor;
CGAffineTransform shift = CGAffineTransformMakeTranslation(-xOffset, -yOffset);
CGAffineTransform unshift = CGAffineTransformMakeTranslation(xOffset, yOffset);
CGAffineTransform scale = CGAffineTransformMakeScale(scaleAmount, scaleAmount);
view.transform = CGAffineTransformConcat(shift, CGAffineTransformConcat(scale, unshift));
}
It's probably more general than what you need, and you could do it simpler, but this should work too. Just change the point to something that looks like the center of the arrow.

Rotating iOS UILayer like a ticking clock, not smoothly in a circle

I am able to rotate an image a certain number of degrees continuously, but I want to rotate the image a tiny amount, pause, a little more, pause, etc.
The follow code does this continuously:
// rotate
CGFloat finalValue = 360 / 14.f;
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
[rotationAnimation setFromValue:[self degreesToNumber:0]];
[rotationAnimation setToValue:[self degreesToNumber:finalValue]];
[rotationAnimation setDuration:5.0];
[rotationAnimation setRemovedOnCompletion:NO];
[rotationAnimation setFillMode:kCAFillModeForwards];
[self.secondHandImageView.layer addAnimation:rotationAnimation forKey:#"rotate"];
Is there a way to wrap this in a for-loop with the number of angle changes I need and set the duration and delay of the specific animations? Nothing I have tried works. Below is what I am currently trying:
// rotate in ticks, so
NSTimeInterval delay = 0;
CGFloat currentAngle = 0;
CGFloat finalAngle = 360 / 14.f;
// angle difference
CGFloat numberOfTicks = 25.f;
CGFloat angleDelta = finalAngle / numberOfTicks;
for (NSUInteger tick = 0; tick < numberOfTicks; tick++) {
[UIView animateWithDuration:0.1 delay:delay options:UIViewAnimationOptionCurveLinear animations:^{
self.secondHandImageView.layer.transform = CATransform3DMakeRotation(0, 0, angleDelta, 1.0);
} completion:nil];
// update delay
delay += .2; // 200 milliseconds, 5 tickets every second
currentAngle += angleDelta;
}
You code is OK - except that you can't animate layers inside UIView animation blocks. Instead you can animate the view. Replace the (3D)layer transform with a (2D)view transform:
self.secondHandImageView.transform =
CGAffineTransformRotate(self.tickView.transform,angleDelta);

How to slow the rotation of an object?

I have an issue.
i have this code:
if(CGRectContainsPoint(compassTarcsa.frame, curLoc))
{
compassTarcsaTouch = YES;
float fromAngle = atan2( compassFirstLoc.y-compassTarcsa.center.y,compassFirstLoc.x-compassTarcsa.center.x );
float toAngle = atan2( curLoc.y-compassTarcsa.center.y,curLoc.x-compassTarcsa.center.x );
newAngle = (angle + (toAngle - fromAngle));
iranyFok = (newAngle / (M_PI / 180.0f)) ;
if (iranyFok < 0.0f) {
iranyFok += 360.0f;
}
iranyFok = 360-(fmodf(( 360 + iranyFok ), 360));
CGAffineTransform cgaRotate = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(- iranyFok));
compassTarcsa.transform = cgaRotate;
repcsi.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(iranyFok));
This rotates a compass from 0 to to 360 degrees, but the rotation speed is fast for me...
Can somebody help me understand how to slow this rotate?
Thx,
Alex
Try this:
[UIView animateWithDuration:2.0 delay:0 options: UIViewAnimationOptionCurveEaseInOut
animations:^(void){
compassTarcsa.transform = cgaRotate;
} completion:NULL
];
This will wrap your transform rotation in an animation that allows you to control the rotation duration.
You should consider using rotation animation and set its duration as per your required speed if you want slow rotation of an object.
Following links may be helpful to you:
UIView Infinite 360 degree rotation animation?
http://iosdevelopertips.com/user-interface/rotate-an-image-with-animation.html

How to scale (zoom) a UIView to a given CGPoint

I've spent a lot of time trying to find a way to use CGAffineScale to transform a view to a given point, including messing around with anchor points, moving the centre of a view before and after transforming and comprehensive Googling. I am aware this would be a lot simpler with a UIScrollview; but I know it's technically possible to do without one, and it's become a splinter in my mind.
This answer gets remarkably close to what I want to achieve, but the answer only gives details on how to zoom to a given corner (instead of a given point) by cleverly moving the centre to the corner opposite the one you want to zoom in to.
How can I modify mvds' code to scale a UIView to any given point in a UIView?
CGFloat s = 3;
CGAffineTransform tr = CGAffineTransformScale(self.view.transform, s, s);
CGFloat h = self.view.frame.size.height;
CGFloat w = self.view.frame.size.width;
[UIView animateWithDuration:2.5 delay:0 options:0 animations:^{
self.view.transform = tr;
self.view.center = CGPointMake(w-w*s/2,h*s/2);
} completion:^(BOOL finished) {}];
There are 2 steps involved: First you scale up the view you want to zoom in to. Then you set the center of this blown up view such that the part you want to see ends up in the middle of the view.
You should draw this out on paper and the formulas will follow: (untested)
CGFloat s = 3;
CGPoint p = CGPointMake(100, 200);
CGAffineTransform tr = CGAffineTransformScale(self.view.transform, s, s);
CGFloat h = self.view.frame.size.height;
CGFloat w = self.view.frame.size.width;
[UIView animateWithDuration:2.5 delay:0 options:0 animations:^{
self.view.transform = tr;
CGFloat cx = w/2-s*(p.x-w/2);
CGFloat cy = h/2-s*(p.y-h/2);
self.view.center = CGPointMake(cx, cy); //was: (w*s/2,h-h*s/2);
} completion:^(BOOL finished) {}];
I actually ran into this very same problem myself. To fix it, all I did was change the anchor point of the view I was scaling because CGAffineTransforms are performed on the view in relation to its anchor point, so depending on where the anchor point is, the transform will scale, translate, or rotate the view differently. Here's the basic idea:
CGPoint pointToScaleTo = CGPointMake(x, y); //Just enter the coordinates you
//want to scale your view towards
CGFloat viewWidth = self.view.bounds.size.width;
CGFloat viewHeight = self.view.bounds.size.height;
CGFloat scaleFactorX = ...;
CGFloat scaleFactorY = ...;
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scaleFactorX, scaleFactorY);
[UIView animateWithDuration:2.5f delay:0.0f options:0 animations:^{
//I divide the x and y coordinates by the view width and height
//because the anchor point coordinates are normalized to the range
//0.0-1.0.
self.view.layer.anchorPoint = CGPointMake(pointToScaleTo.x/viewWidth, pointToScaleTo.y/viewHeight);
//Now that the anchor point has been changed, apply the scale transform
self.view.layer.transform = scaleTransform;
} completion:^(BOOL finished) {}];

Resources