I have an app and I need to reproduce a dice animation. Everytime when the dices are rolled I should change a little bit his position on X axis and his rotation.
The dice is an UIButton inside of an UIView with 16 pixels bigger! When the user rolls I create an animation for rotating and in the same time I animate the change of X view value. When the animation is finished I try to fix a random stop position.
Take a look of my code to understand better.
This is my rotating animation:
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat: 2*M_PI];
animation.duration = 0.2;
animation.repeatCount = 2;
[dice.layer addAnimation:animation forKey:#"SpinAnimation"];
int min = -20;
int maxim = 20;
int rndValue = min + arc4random() % (maxim - min);
// Here I change x value;
CGRect rct = dice.superview.frame;
rct.origin.x = n + rndValue;
rct.size.width = dice.superview.bounds.size.width;
rct.size.height = dice.superview.bounds.size.height;
[UIView animateWithDuration:0.4 animations:^{
dice.superview.frame = rct;
}completion:^(BOOL finished){
//Here, after the animation is done, i want to put a random rotation value.
float degrees = arc4random() % 30;
dice.superview.layer.anchorPoint = CGPointMake(0.5, 0.5);
dice.superview.transform = CGAffineTransformMakeRotation(degrees * M_PI/180);
}];
My problem is than everytime(expect first one when I roll) the dices(views too) becomes smaller, because the size of view frame is changing and I can't found the problem or any other solution.
PS:If I use just one of these( rotating or changing X axis value) the dices keeps the same size.
I search already a lot online but nothing useful for me. Anyone have an idea, please? Thanks a lot!
I finally found an answer for my problem and I post my solution here, maybe others people will meet same issue.
The problem become from dice moving.
When I move the dice I change the frame of it, that was my mistake. The problem it's solved if you will change the center point of dice
Replace frame animation change with :
[UIView animateWithDuration:0.4 animations:^{
dice.superview.center = CGPointMake((self.view.frame.size.width - CGRectGetMaxX(imageBack.frame)) / 2 + rndValue + CGRectGetMaxX(imageBack.frame), dice.superview.center.y);
}completion:^(BOOL finished){
}];
Anyway, there are my values.. you can put whatever you want there!
Hope it helps
Related
Context:
I have a graphic that I rotate continuously while a background operation happens:
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = #(M_PI * 2.0);
rotationAnimation.duration = 2;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = HUGE_VALF;
[myImgView.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
At some point this background animation ends, and I stop the animation:
[myImgView.layer removeAllAnimations];
When I do this, though, the rotation stops wherever it is and snaps back to zero rotation. I could just stop it where it is by promoting the presentation value to the model value:
CATransform3D stoppedTransform = myImgView.layer.presentationLayer.transform;
[myImgView.layer removeAllAnimations];
myImgView.layer.transform = stoppedTransform;
This works OK. But ideally I'd like to continue the rotation for the rest of its current circuit, and decelerate and stop smoothly as it approaches and rests at zero rotation. Stop the animation, promote presentation to model, and create a new animation with an ease-out curve to zero. Something like this:
CATransform3D stoppedTransform = myImgView.layer.presentationLayer.transform;
[myImgView.layer removeAllAnimations];
myImgView.layer.transform = stoppedTransform;
CGFloat currRotation = [[myImgView.layer valueForKeyPath:#"transform.rotation.z"] floatValue];
CABasicAnimation *finishAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
finishAnimation.fromValue = #(currRotation);
finishAnimation.toValue = #(0);
finishAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
finishAnimation.duration = 2; //this is what I need to calculate correctly
[myImgView.layer setValue:#(0) forKeyPath:#"transform.rotation.z"];
[myImgView.layer addAnimation:finishAnimation forKey:#"transform.rotation.z"];
My question:
How do I calculate the duration of my finishing animation with its ease-out curve so that its initial velocity matches the velocity of the existing rotation (currently pi radians per second) so there's no visible hiccup? Or is there another way to achieve the desired effect?
I have tried using CASpringAnimation and using its initialVelocity property, but it seems to combine that with the velocity imparted by the spring pulling on the mass, which is governed by mass, stiffness, and damping, and its units are unclear. And I can get close on the ease-out duration by calculating the duration as if it's linear and bumping it 20% or 30% or so.
Side note: there is some minor complexity here, removed to simplify the question, around the toValue of finishAnimation because of how the layer reports its current rotation value. If it's more than pi radians (180 degrees), it reports as negative. So it goes like this:
CGFloat rotationRadians = currRotation / M_PI;
if (rotationRadians < 0)
{
finishAnimation.duration = (-1 * rotationRadians); //negative means more than halfway through, or more than pi radians.
finishAnimation.toValue = #(0);
}
else
{
finishAnimation.duration = (2 - rotationRadians); //positive means less than halfway through, or less than pi radians
finishAnimation.toValue = #(M_PI * 2);
}
I'm building an app that will have an animation of one image "walking" across the screen, using simple CABasicAnimation. I have it set for it to walk a certain distance in a duration, and then stop until the user gives it more commands, in which it will continue walking for the same distance and duration again. The issue that I have is that after the first time, the image will stop where it should, but it won't continue to walk, it will jump back to its original spot and start over. I thought I had this set correctly on the origin point, but I guess not.
CABasicAnimation *hover = [CABasicAnimation animationWithKeyPath:#"position"];
hover.fillMode = kCAFillModeForwards;
hover.removedOnCompletion = NO;
hover.additive = YES; // fromValue and toValue will be relative instead of absolute values
hover.fromValue = [NSValue valueWithCGPoint:CGPointZero];
hover.toValue = [NSValue valueWithCGPoint:CGPointMake(110.0, -50.0)]; // y increases downwards on iOS
hover.autoreverses = FALSE; // Animate back to normal afterwards
hover.duration = 10.0; // The duration for one part of the animation (0.2 up and 0.2 down)
hover.repeatCount = 0; // The number of times the animation should repeat
[theDude.layer addAnimation:hover forKey:#"myHoverAnimation"];
Your from value is set to be zero and it's not being updated.
hover.fromValue = [NSValue valueWithCGPoint:CGPointZero];
You have to update this value with your to value each time.
Here I've put your code in a function where you can update starting and ending points.
- (void)moveFromPoint:(CGPoint)fromPoint toPoint:(CGPoint)toPoint {
CABasicAnimation *hover = [CABasicAnimation animationWithKeyPath:#"position"];
hover.fillMode = kCAFillModeForwards;
hover.removedOnCompletion = NO;
hover.additive = YES; // fromValue and toValue will be relative instead of absolute values
hover.fromValue = [NSValue valueWithCGPoint:fromPoint];
hover.toValue = [NSValue valueWithCGPoint:toPoint]; // y increases downwards on iOS
hover.autoreverses = FALSE; // Animate back to normal afterwards
hover.duration = 10.0; // The duration for one part of the animation (0.2 up and 0.2 down)
hover.repeatCount = 0; // The number of times the animation should repeat
[theDude.layer addAnimation:hover forKey:#"myHoverAnimation"];
}
You can move the guy further by calling this function with new points each time.
[self moveFromPoint:CGPointZero toPoint:CGPointMake(110.0, -50.0)]
[self moveFromPoint:CGPointMake(110.0, -50.0) toPoint:CGPointMake(160.0, -50.0)]
EDIT:
I see that you want to move the guy with the same ratio but in different lenght each time.
Add this variable just after the #interface:
#property (nonatomic) CGPoint oldPointOfTheGuy;
And add this new function just after the previous function:
- (void)moveByDistance:(CGFloat)distance {
CGPoint newPointOfTheGuy = CGPointMake(self.oldPointOfTheGuy.x + 2.2*distance, self.oldPointOfTheGuy.y + distance);
[self moveFromPoint:self.oldPointOfTheGuy toPoint:newPointOfTheGuy];
self.oldPointOfTheGuy = newPointOfTheGuy;
}
And set a starting point for the guy in your viewDidLoad:
self.oldPointOfTheGuy = CGPointMake(110.0, -50)
Now we have set the old position of the guy as where we know he spawns for the first time.
From now on, each time we want to move him, we will call this:
[self moveByDistance:20];
What this function does is, since that it already knows your x / y ratio which is 2.2, it just adds 20 to your old y position and adds 2.2 * 20 to your old x position. And each time a new position is set, old position is updated.
Hope this helps.
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.
I'm trying to make an arm wave effect (Hello!) with UIView animations, but it snaps back to the beginning when the last one goes off. I want the wave to go back and forth.
First keyframe: Rotation 30˚
Second keyframe: Rotation -30˚
Third keyframe: SHOULD BE Rotation 0˚
arm.layer.anchorPoint = CGPointMake(1.0, 1.0);
float x = arm.frame.origin.x + arm.frame.size.width;
float y = arm.frame.origin.y + arm.frame.size.height;
arm.layer.frame = CGRectMake(x, y, arm.frame.size.width, arm.frame.size.height);
[self.scrollView arm];
float degrees = 30.0;
float radians = (degrees/90.0)*M_PI;
[UIView animateKeyframesWithDuration:4.0f delay:0.0 options:UIViewKeyframeAnimationOptionRepeat
animations:^{
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:.5 animations:^{
arm.transform = CGAffineTransformMakeRotation(radians);
}];
[UIView addKeyframeWithRelativeStartTime:.5 relativeDuration:.75 animations:^{
arm.transform = CGAffineTransformMakeRotation(-radians);
}];
[UIView addKeyframeWithRelativeStartTime:1.25 relativeDuration:.5 animations:^{
arm.transform = CGAffineTransformMakeRotation(0);
}];
The reason is that you used up the whole animation on the first two frames of the keyframe animation. These are relative times and relative durations; they must all be less than 1!
So, on the first one you use up half the animation (.5).
On the second one you start halfway through the animation (.5) and then you use up the entire rest of the animation (.75 is larger than .5 which is all you've got left).
So the third one never happens. And in any case it's completely nuts: you can't have a relative start time of 1.75 because 1 is the end of the animation. Read the docs:
This value must be in the range 0 to 1, where 0 represents the start of the overall animation and 1 represents the end of the overall animation. For example, for an animation that is two seconds in duration, specifying a start time of 0.5 causes the animations to begin executing one second after the start of the overall animation.
I've been trying to rotate a button using the following method:
-(IBAction)rotate:(id)sender{
CGPoint pencilCenter = pencil.center;
[pencil setCenter:pencilCenter];
CGFloat floater = 1.0;
[UIView animateWithDuration:0.7 animations:^(void){
[pencil setTransform:CGAffineTransformMakeRotation(floater)];
}];
[UIView animateWithDuration:0.7 animations:^(void){
[pencil setTransform:CGAffineTransformMakeRotation(floater)];
}];
}
This is supposed to make the button do some kind of "shake", then it's supposed to be back in its original position- yet all it does is changing the button's location, moving it to only one side and on another run of the method the button doesn't react at all.
What's the problem with my code?
Thanks!
EDIT 2:
My que is- how do I make a button to do a little shake/wiggle ,e.g. the wiggle app mode when editing sptingboard.
Using this code is giving me rotation to left, smooth animates from left to right then right to left, then rotates to original position. Now, I want this not to just rotate, but do this with an animation, like a wiggle.
[UIView animateWithDuration:0.25
delay:0.0
options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveLinear | UIViewAnimationOptionAutoreverse)
animations:^ {
pencil.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(30));
pencil.transform = CGAffineTransformIdentity;
}
completion:^(BOOL finished){
}
];
Thanks!
Import "QuartzCore/QuartzCore.h" and try this,
CABasicAnimation *fullRotation;
fullRotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
fullRotation.delegate = self;
fullRotation.fromValue = [NSNumber numberWithFloat:0];
fullRotation.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
fullRotation.duration = 1.7;
fullRotation.repeatCount = 2;
[btnTemp.layer addAnimation:fullRotation forKey:#"360"];
Try using CGAffineTransformRotate instead of CGAffineTransformMakeRotation. You can use `CGAffineTransformIdentity1 as the first argument in all calls, so the final transform according to second argument will be applied on the original shape(?) of the frame.
While it didn't perfectly answer the question, Shardul's answer is great to rotate the button (2 full rotations). So here it is, converted to Swift 3:
let fullRotation = CABasicAnimation(keyPath: "transform.rotation")
fullRotation.delegate = self
fullRotation.fromValue = NSNumber(floatLiteral: 0)
fullRotation.toValue = NSNumber(floatLiteral: Double(CGFloat.pi * 2))
fullRotation.duration = 0.5
fullRotation.repeatCount = 2
button.layer.add(fullRotation, forKey: "360")
You will need to import QuartzCore:
import QuartzCore
And your ViewController needs to conform to CAAnimationDelegate:
class ViewController: UIViewController, CAAnimationDelegate {
}
Well, the docs say: "...the specified animations are started immediately on another thread ...". Now, all UIKit code is NOT thread safe. So, maybe it helps if instead calling your UI setters (setTransform, setCenter) from the completion block (which runs in a separate thread) to call them using performSelectorOnMainThread:withObject:.