Rotate Button Or Image From One Point To another - ios

I want to create a rotating Button Which can be rotate from my given points
I tried this but it gives angles and i want to give points
self.theImageView.transform=CGAffineTransformMakeRotation (angle);
angle=30;
I also tried this but it has same problem
CABasicAnimation *halfTurn;
halfTurn = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
halfTurn.fromValue = [NSNumber numberWithFloat:0];
halfTurn.toValue = [NSNumber numberWithFloat:((90*M_PI)/360)];
halfTurn.duration = 1;
halfTurn.repeatCount = false;
[[button layer] addAnimation:halfTurn forKey:#"180"];
can any one suggest be or give code snippiest
thanks in advance

If you want to want to give two points and rotate your view around those two points, there is a catch. You have to see to it that those two points are equidistant from the anchor of rotation. You can calculate the arc-angle of such a rotation and set it accordingly.
You can find the length of the arc here:
http://math.about.com/od/formulas/ss/surfaceareavol_9.htm
You can find the formula for finding the arc angle, in this link: http://www.regentsprep.org/Regents/math/algtrig/ATM1/arclengthlesson.htm
Edit
I am now given a premise that there are 7 buttons arranged circularly around an anchor point. What you do now, is maintain an instance level variable called currentAngle and initialize it to 0.
CGFloat nextAngle = currentAngle + (360/7);
CABasicAnimation *halfTurn;
halfTurn = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
halfTurn.fromValue = [NSNumber numberWithFloat:((currentAngle*M_PI)/360)];
halfTurn.toValue = [NSNumber numberWithFloat:((nextAngle*M_PI)/360)];
halfTurn.duration = 1;
halfTurn.repeatCount = false;
[[button layer] addAnimation:halfTurn forKey:#"clock_wise_rotation"];
currentAngle = nextAngle;
The above code is for clockwise rotation only. If you want to perform anticlockwise animation, you have to subtract 360/7 and perform the animation.

Related

Rotate CAShapeLayer around it center without moving postion

I want to rotate a CAShapeLayer with objective c around it center point without moving it around. The CAShapeLayer contain UIBezierPath point of rect. I'm not able to rotate the CAShapeLayer becouse i dont know how. Please show me how tp rotate around it center without moving it postion.
Here is some code that does that:
//Create a CABasicAnimation object to manage our rotation.
CABasicAnimation *rotation = [CABasicAnimation animationWithKeyPath:#"transform"];
totalAnimationTime = rotation_count;
rotation.duration = totalAnimationTime;
//Start the animation at the previous value of angle
rotation.fromValue = #(angle);
//Add change (which will be a change of +/- 2pi*rotation_count
angle += change;
//Set the ending value of the rotation to the new angle.
rotation.toValue = #(angle);
//Have the rotation use linear timing.
rotation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
/*
This is the magic bit. We add a CAValueFunction that tells the CAAnimation we are modifying
the transform's rotation around the Z axis.
Without this, we would supply a transform as the fromValue and toValue, and for rotations
> a half-turn, we could not control the rotation direction.
By using a value function, we can specify arbitrary rotation amounts and directions, and even
Rotations greater than 360 degrees.
*/
rotation.valueFunction = [CAValueFunction functionWithName: kCAValueFunctionRotateZ];
/*
Set the layer's transform to it's final state before submitting the animation, so it is in it's
final state once the animation completes.
*/
imageViewToAnimate.layer.transform = CATransform3DRotate(imageViewToAnimate.layer.transform, angle, 0, 0, 1.0);
//Now actually add the animation to the layer.
[imageViewToAnimate.layer addAnimation:rotation forKey:#"transform.rotation.z"];
(That code is taken (and simplified) from my github project KeyframeViewAnimations)
In my project I'm rotating the layer of a UIImageView, but the same approach will work for any CALayer type.

how to reset/restart an animation and have it appear continuous?

So, I am fairly new to iOS programming, and have inherited a project from a former coworker. We are building an app that contains a gauge UI. When data comes in, we want to smoothly rotate our "layer" (which is a needle image) from the current angle to a new target angle. Here is what we have, which worked well with slow data:
-(void) MoveNeedleToAngle:(float) target
{
static float old_Value = 0.0;
CABasicAnimation *rotateCurrentPressureTick = [CABasicAnimation animationWithKeyPath:#"transform.rotation");
[rotateCurrentPressureTick setDelegate:self];
rotateCurrentPressureTick.fromValue = [NSSNumber numberWithFloat:old_value/57.2958];
rotateCurrentPressureTick.removedOnCompletion=NO;
rotateCurrentPressureTick.fillMode=kCAFillModeForwards;
rotateCurrentPressureTick.toValue=[NSSNumber numberWithFloat:target/57.2958];
rotateCurrentPressureTick.duration=3; // constant 3 second sweep
[imageView_Needle.layer addAnimation:rotateCurrentPressureTick forKey:#"rotateTick"];
old_Value = target;
}
The problem is we have a new data scheme in which new data can come in (and the above method called) faster, before the animation is complete. What's happening I think is that the animation is restarted from the old target to the new target, which makes it very jumpy.
So I was wondering how to modify the above function to add a continuous/restartable behavior, as follows:
Check if the current animation is in progress and
If so, figure out where the current animation angle is, and then
Cancel the current and start a new animation from the current rotation to the new target rotation
Is it possible to build that behavior into the above function?
Thanks. Sorry if the question seems uninformed, I have studied and understand the above objects/methods, but am not an expert.
Yes you can do this using your existing method, if you add this bit of magic:
- (void)removeAnimationsFromView:(UIView*)view {
CALayer *layer = view.layer.presentationLayer;
CGAffineTransform transform = layer.affineTransform;
[layer removeAllAnimations];
view.transform = transform;
}
The presentation layer encapsulates the actual state of the animation. The view itself doesn't carry the animation state properties, basically when you set an animation end state, the view acquires that state as soon as you trigger the animation. It is the presentation layer that you 'see' during the animation.
This method captures the state of the presentation layer at the exact moment you cancel the animation, and then applies that state to the view.
Now you can use this method in your animation method, which will look something like this:
-(void) MoveNeedleToAngle:(float) target{
[self removeAnimationsFromView:imageView_Needle];
id rotation = [imageView_Needle valueForKeyPath:#"layer.transform.rotation.z"];
CGFloat old_value = [rotation floatValue]*57.2958;
// static float old_value = 0.0;
CABasicAnimation *rotateCurrentPressureTick = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
[rotateCurrentPressureTick setDelegate:self];
rotateCurrentPressureTick.fromValue = [NSNumber numberWithFloat:old_value/57.2958];
rotateCurrentPressureTick.removedOnCompletion=NO;
rotateCurrentPressureTick.fillMode=kCAFillModeForwards;
rotateCurrentPressureTick.toValue=[NSNumber numberWithFloat:target/57.2958];
rotateCurrentPressureTick.duration=3; // constant 3 second sweep
[imageView_Needle.layer addAnimation:rotateCurrentPressureTick forKey:#"rotateTick"];
old_value = target;
}
(I have made minimal changes to your method: there are a few coding style changes i would also make, but they are not relevant to your problem)
By the way, I suggest you feed your method in radians, not degrees, that will mean you can remove those 57.2958 constants.
You can get the current rotation from presentation layer and just set the toValue angle. No need to keep old_value
-(void) MoveNeedleToAngle:(float) targetRadians{
CABasicAnimation *animation =[CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.duration=5.0;
animation.fillMode=kCAFillModeForwards;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
animation.removedOnCompletion=NO;
animation.fromValue = [NSNumber numberWithFloat: [[layer.presentationLayer valueForKeyPath: #"transform.rotation"] floatValue]];
animation.toValue = [NSNumber numberWithFloat:targetRadians];
// layer.transform= CATransform3DMakeRotation(rads, 0, 0, 1);
[layer addAnimation:animation forKey:#"rotate"];
}
Another way i found (commented line above) is instead of using fromValue and toValue just set the layer transform. This will produce the same animation but the presentationLayer and the model will be in sync.

how to move an UIImageview from point A to B in a certain degree

I would like to move a UIImageview from point A to B in certain degree.
the Red and Blue arrow denote the degree i want the dots to move.
i can move the dots from one point to another, but how do i move the note on a degree ?
NSLog(#"cgpoint %#",NSStringFromCGPoint(self.aggressiveDots.frame.origin));
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
NSArray *times = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0], nil];
[anim setKeyTimes:times];
NSArray *values = [NSArray arrayWithObjects:[NSValue valueWithCGPoint:CGPointMake(118, 188.)],
[NSValue valueWithCGPoint:CGPointMake(115, 300.)], nil];
[anim setValues:values];
[anim setDuration:6.0]; //seconds
[self.redDots.layer addAnimation:anim forKey:#"position"];
Since you'd like to move the UIView along a straight line (not a curve) you only need two points for your CAKeyFrameAnimation - the starting and endoing point. In fact you could even use [UIView transistionWithView:] for this if you wanted - just manipulate the frame in the animation block.
You already know the starting point.
To calculate the ending point you need to find the point where the edge of the red circle intersects the white circle. There's an equation to solve the intersection points of two circles detailed here
If you'd like to avoid tricky algebra, you can draw a triangle as follows:
Just let the first circle travel down the line, using the simple line equation y=mx + b
Test if the edge of the circle (which is radius points along the line ahead of the center) is inside the triangle using the formula outlined here
"In general, x and y must satisfy (x - center_x)^2 + (y - center_y)^2 < radius^2"

Using CABasicAnimation to rotate a UIImageView more than once

I am using a CABasicAnimation to rotate a UIImageView 90 degrees clockwise, but I need to have it rotate a further 90 degrees later on from its position after the initial 90 degree rotation.
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.duration = 10;
animation.additive = YES;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.fromValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(0)];
animation.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(90)];
[_myview.layer addAnimation:animation forKey:#"90rotation"];
Using the code above works initially, the image stays at a 90 degree angle. If I call this again to make it rotate a further 90 degrees the animation starts by jumping back to 0 and rotating 90 degrees, not 90 to 180 degrees.
I was under the impression that animation.additive = YES; would cause further animations to use the current state as a starting point.
Any ideas?
tl;dr: It is very easy to misuse removeOnCompletion = NO and most people don't realize the consequences of doing so. The proper solution is to change the model value "for real".
First of all: I'm not trying to judge or be mean to you. I see the same misunderstanding over and over and I can see why it happens. By explaining why things happen I hope that everyone who experience the same issues and sees this answer learn more about what their code is doing.
What went wrong
I was under the impression that animation.additive = YES; would cause further animations to use the current state as a starting point.
That is very true and it's exactly what happens. Computers are funny in that sense. They always to exactly what you tell them and not what you want them to do.
removeOnCompletion = NO can be a bitch
In your case the villain is this line of code:
animation.removedOnCompletion = NO;
It is often misused to keep the final value of the animation after the animation completes. The only problem is that it happens by not removing the animation from the view. Animations in Core Animation doesn't alter the underlying property that they are animating, they just animate it on screen. If you look at the actual value during the animation you will never see it change. Instead the animation works on what is called the presentation layer.
Normally when the animation completes it is removed from the layer and the presentation layer goes away and the model layer appears on screen again. However, when you keep the animation attached to the layer everything looks as it should on screen but you have introduced a difference between what the property says is the transform and how the layer appears to be rotated on screen.
When you configure the animation to be additive that means that the from and to values are added to the existing value, just as you said. The problem is that the value of that property is 0. You never change it, you just animate it. The next time you try and add that animation to the same layer the value still won't be changed but the animation is doing exactly what it was configured to do: "animate additively from the current value of the model".
The solution
Skip that line of code. The result is however that the rotation doesn't stick. The better way to make it stick is to change the model. Set the new end value of the rotation before animating the rotation so that the model looks as it should when the animation gets removed.
byValue is like magic
There is a very handy property (that I'm going to use) on CABasicAnimation that is called byValue that can be used to make relative animations. It can be combined with either toValue and fromValue to do many different kinds of animations. The different combinations are all specified in its documentation (under the section). The combination I'm going to use is:
byValue and toValue are non-nil. Interpolates between (toValue - byValue) and toValue.
Some actual code
With an explicit toValue of 0 the animation happens from "currentValue-byValue" to "current value". By changing the model first current value is the end value.
NSString *zRotationKeyPath = #"transform.rotation.z"; // The killer of typos
// Change the model to the new "end value" (key path can work like this but properties don't)
CGFloat currentAngle = [[_myview.layer valueForKeyPath:zRotationKeyPath] floatValue];
CGFloat angleToAdd = M_PI_2; // 90 deg = pi/2
[_myview.layer setValue:#(currentAngle+angleToAdd) forKeyPath:zRotationKeyPath];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:zRotationKeyPath];
animation.duration = 10;
// #( ) is fancy NSNumber literal syntax ...
animation.toValue = #(0.0); // model value was already changed. End at that value
animation.byValue = #(angleToAdd); // start from - this value (it's toValue - byValue (see above))
// Add the animation. Once it completed it will be removed and you will see the value
// of the model layer which happens to be the same value as the animation stopped at.
[_myview.layer addAnimation:animation forKey:#"90rotation"];
Small disclaimer:
I didn't run this code but am fairly certain that it runs as it should and that I didn't do any typos. Correct me if I did. The entire discussion is still valid.
pass incremental value of angle see my code
static int imgAngle=0;
- (void)doAnimation
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.duration = 5;
animation.additive = YES;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.fromValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(imgAngle)];
animation.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(imgAngle+90)];
[self.imgView.layer addAnimation:animation forKey:#"90rotation"];
imgAngle+=90;
if (imgAngle>360) {
imgAngle = 0;
}
}
Above code is just for idea. Its not tested

CAKeyFrameAnimation of transform property on CALayer not working as expected

I have been working at this for a while and searched SO thoroughly for a solution but to no avail. Here is what I am trying to do.
I have a UIScrollView on which the user can zoom and pan for 5 seconds. I have a separate CALayer which is not layered on top of the UIScrollView. I want to scale and translate this CALayer's contents to reflect the zoom and pans occurring on the UIScrollView. I want to achieve this via key frame animation CAKeyFrameAnimation. When I put this into code, the zoom occurs as expected but the position of the content is offset incorrectly.
Here is how I do it in code. Assume that UIScrollView delegate passes zoom scale and content offset to the following method:
// Remember all zoom params for late recreation via a CAKeyFrameAnimation
- (void) didZoomOrScroll:(float)zoomScale
contentOffset:(CGPoint)scrollViewContentOffset {
CATransform3D scale = CATransform3DMakeScale(zoomScale, zoomScale, 1);
CATransform3D translate = CATransform3DMakeTranslation(-scrollViewContentOffset.x,
-scrollViewContentOffset.y, 0);
CATransform3D concat = CATransform3DConcat(scale, translate);
// _zoomScrollTransforms and _zoomScrollTimes below are of type NSMutableArray
[_zoomScrollTransforms addObject:[NSValue valueWithCATransform3D:concat]];
// secondsElapsed below keeps track of time
[_zoomScrollTimes addObject:[NSNumber numberWithFloat:secondsElapsed]];
}
// Construct layer animation
- (void) constructLayerWithAnimation:(CALayer *)layer {
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
animation.duration = 5.0;
animation.values = _zoomScrollTransforms;
// Adjust key frame times to contain fractional durations
NSMutableArray *keyTimes = [[NSMutableArray alloc] init];
for( int i = 0; i < [_zoomScrollTimes count]; i++ ) {
NSNumber *number = (NSNumber *)[_zoomScrollTimes objectAtIndex:i];
NSNumber *fractionalDuration = [NSNumber numberWithFloat:[number floatValue]/animation.duration];
[keyTimes addObject:fractionalDuration];
}
animation.keyTimes = keyTimes;
animation.removedOnCompletion = YES;
animation.beginTime = 0;
animation.calculationMode = kCAAnimationDiscrete;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:animation forKey:nil];
}
When the above layer is animated, the content is zoomed properly but it is positioned incorrectly. The content appears to be x and y shifted more than I expected and as a result doesn't exactly retrace the zoom/pans done by the user on the UIScrollView.
Any ideas what I may be doing wrong?
Answer
Ok, i figured out what I was doing wrong. It had to do with the anchor point for the CALayer. Transforms on CALayers are always applied to the anchor point. For CALayers, the anchor point is (0.5, 0.5) by default. So scaling and translations were being conducted along the center point. UIScrollViews, on the other hand, gives offsets from the top left corner of the view. Basically you can think of the anchor point for UIScrollView, for the purposes of thinking about the CGPoint offset value, as being (0.0, 0.0).
So the solution is to set the anchor point of CALayer to (0.0, 0.0). And then everything works as expected.
There are other resources that present this info in a nicer way. See this other question on Stackoverflow that is similar. Also see this article in Apple's documentation that discusses position, anchorpoint and general layer in geometry in great detail.
You are concatenating translation matrix with scaled matrix. The final value of displacement offset will be Offset(X, Y) = (scaleX * Tx, scaleY * Ty).
If you want to move the UIView with (Tx, Ty) offset, than concatenate the translate and scale matrices as below:
CATransform3D scale = CATransform3DMakeScale(zoomScale, zoomScale, 1);
CATransform3D translate = CATransform3DMakeTranslation(-scrollViewContentOffset.x,
-scrollViewContentOffset.y, 0);
CATransform3D concat = CATransform3DConcat(translate, scale);
Try this and let me know if it works.

Resources