I want to animate my multiple UIImageViews to move from point A to point B linearly.
i'm using options:UIViewAnimationOptionCurveLinear - Apple docs says: "A linear animation curve causes an animation to occur evenly over its duration.".
Here's the code that i'm using:
[UIView animateWithDuration:1.5
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
//start animation of random grasses with const speed and const y
for (int i=0;i<45;i++){
if ([self.view viewWithTag:i+100]){
CGRect frameOfGrass = [self.view viewWithTag:i+100].frame;
frameOfGrass.origin.y = -100;
[self.view viewWithTag:i+100].frame = frameOfGrass;
}}
}
completion:^(BOOL finished){
//
}];
NOTE:
Y position of every imageView is random number from 600-700.
But the result looks more like UIViewAnimationOptionCurveEaseOut - "An ease-out curve causes the animation to begin quickly, and then slow as it completes." Because all the images slows down at the and.
Here's screenshots of app running:
Any idea why this is happening?
The travel distance is not the same for all the grass images. Remember v=d/t. In your case, all the grass images will travel at different speeds because they need to reach y.origin = -100 at the same time.
Try this:
frameOfGrass.origin.y = frameOfGrass.origin.y - 600;
This should make all the grass images travel the same distance over the same time.
Related
I'm trying to animate the size of a UIView with a random factor repeatedly as follows, but I need to make sure it doesn't shrink/grow beyond limits.
This is my attempt:
-(void)animateView{
float percentage =50;
float rnd = (100 +((float)rand())/RAND_MAX * percentage - percentage/2)/100;
[UIView animateWithDuration:5.0
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionBeginFromCurrentState
animations:^{
myView.transform = CGAffineTransformScale(myView.transform, rnd, rnd);
}
completion:^(BOOL finished){
[self animateView];
}
];
}
So, somewhere in this loop, I need to get the original size of myView, and get a relative growth to that size, rather than to the current size of myView.
I am aware of CGAffineTransformIdentity, but can't figure out how to put that into this loop.
Any suggestions?
try to create the instance of CGAffineTransform initialTransform; and preserve initialTransform = myView.transform; initial value in did load later use it below way : -
myView.transform = initialTransform;
in top of your animateView method.
I have an image that I am animating in order to make it look as if it is "breathing".
Currently I have the image moving in a decent manner with the following code below: (I am animating a UIView that contains a few UIImageView's, which all move as one)
- (IBAction)animateButton:(id)sender {
[UIView animateWithDuration:0.64
delay:0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat
animations:^{
_testView.transform = CGAffineTransformMakeScale(1.08f, 1.02f);
} completion:nil];
}
HOWEVER, I can not seem to figure out how to animate stretching the image in the x at a different rate as the y. The point of this is to appear as if the image is actually alive without appearing to cycle through a clear repetitive motion.
I tried by attempting to anchor the center of the UIView to a specific location, then add some number to the width, through an animation of lets say 1.0 seconds.
I then tried to simultaneously call another animation that does the same animation only to the height, with a different amount added, for about 1.3 seconds. I could not get these two to perform at the same time though, as one would take precedence over the other.
If someone could lead me in the right direction as to animating a repetitive stretch of the width and height at different rates I would be most appreciative. Thanks!
Consider that two changes overlapping in time look like this:
|---- change x ---|
|---- change y ----|
If the two intervals are arbitrary and overlapping, the can be represented by three animations: one changing one dimension individually, one changing both dimensions together, and another changing one dimension individually.
You can see that there's numerous ways to specify this, but lets try a straight-forward one. To avoid the arithmetic of compounding scales, lets specify a dimension, a pixel change and a duration. For example...
#[ #{#"dimension":#"width", #"delta":#10, #"duration":0.2},
#{#"dimension":#"both", #"delta":#40, #"duration":0.8},
#{#"dimension":#"width", #"delta":#10, #"duration":0.2} ]
... means a longer change in width straddling a shorter change in height. You can see how this can be a pretty complete language to get done what you want.
We need an animation method that will perform the changes serially. A simple way to do this is to treat the array of actions as a to-do list. The recursive algorithm says: to do a list of things, do the first one, then do the rest....
- (void)animateView:(UIView *)view actions:(NSArray *)actions completion:(void (^)(BOOL))completion {
if (actions.count == 0) return completion(YES);
NSDictionary *action = actions[0];
NSArray *remainingActions = [actions subarrayWithRange:NSMakeRange(1, actions.count-1)];
[self animateView:view action:action completion:^(BOOL finished) {
[self animateView:view actions:remainingActions completion:completion];
}];
}
For the animation, you probably want to use a linear timing curve for the intermediate animations, though I can see you getting more elaborate and change the timing curve at the start and end of the list.
- (void)animateView:(UIView *)view action:(NSDictionary *)action completion:(void (^)(BOOL))completion {
NSString *dimension = action[#"dimension"];
CGFloat delta = [action[#"delta"] floatValue];
NSTimeInterval duration = [action[#"duration"] floatValue];
CGRect frame = view.frame;
if ([dimension isEqualToString:#"width"]) {
frame = CGRectInset(frame, -delta, 0);
} else if ([dimension isEqualToString:#"height"]) {
frame = CGRectInset(frame, 0, -delta);
} else {
frame = CGRectInset(frame, -delta, -delta);
}
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
view.frame = frame;
} completion:completion];
}
If the array of dictionaries is too clumsy to specify (and it is rather general), you could add some convenience methods on top that provide some simpler scheme to the caller and builds the array of more general representation.
I have a UIView called waves and it has a nice endless "floating" animation
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut
animations:^{
CGPoint center = waves.center;
center.y += 5;
waves.center = center;
}
completion:nil];
Now if I add another animation, say moving this view to a different location, "floating" animations stops. It's a reasonable reaction and it's no problem to start the "floating" again in the completion block. I was just wondering if I'm missing something, perhaps in animation Options, to combine the two in a way that doesn't interrupt one another.
I was able to do so if the second animation is based on CGAffineTransfromScale, they combine no problem, but when I move the centre of the view it's not the case.
UPDATE: found a bug in performance. I have a button that calls the method responsible for moving the center of my View with animation. If I press it too fast before previous animation completed View just snaps into new position without animation and completion block is not called. Here's the code for that method:
- (void)wavesAnimationReversed:(BOOL)reversed {
CGFloat y = waves.frame.size.height*0.25;
y = reversed ? -y : y;
// CGFloat damping = reversed ? 1 : 0.65;
CGFloat damping = 1;
[UIView animateWithDuration:kWAVES_ANIMATION_DURATION
delay:0
usingSpringWithDamping:damping
initialSpringVelocity:0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut
animations:^{
CGPoint center = waves.center;
center.y += y;
waves.center = center;
}
completion:^(BOOL finished) {
[self handleStartWavesFloating];
}];
}
If you want to perform multiple animations you should use animateKeyFrames.
That being said you can't animate something that is being animated (your description matches perfectly what will happen). That is, because the values from your view are already changed (it's real values), the animation happens out of your control.
Therefore, if you create a new animation, the default values are the end values of your first animation, when the 2nd animation triggers, it will automatically move the view to the new location to start the second 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'm trying to do a simple horizontal flip animation with a bounce effect.
[UIView animateWithDuration:0.4 delay:0 options:UIViewAnimationCurveEaseIn animations:^{
front.layer.transform=CATransform3DMakeRotation(DEGREES_TO_RADIANS(90), -1.0,0.0,0.0); // flip halfway
}
completion:^(BOOL finished) { // swap view
front.alpha=0;
back.alpha=1;
[UIView animateWithDuration:0.5 animations:^{ // flip remaining + bounce
back.layer.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(45), 1.0,0.0,0.0); // final + 45
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.25 animations:^{
back.layer.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(-22.5), 1.0,0.0,0.0); // bounce back
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.125 animations:^{
back.layer.transform = CATransform3DMakeRotation(0, 1.0,0.0,0.0); // final
}];
}];
}];
}];
Works well except for the 'bounce'. The -22.5 rotation transitions to 0 and than back to 22.5 instead of continuing to -22.5.
I've tried various values and also including an intermediate nested block that transitions the bounce to '0' before going to negative. Didn't help. The rotation always animates to a positive instead of a negative angle.
Just as a test, changing the 'final + 45' to a negative angle does however stop the animation at the desired angle. So the angles themselves are ok.
Problem seems to be doing a 'counter clockwise' rotation starting from zero or going trough zero. Values smaller than zero than always get converted to a positive angle.
Independent if the above is the right technique for implementing a bounce effect, how would a (nested) layer animation be constructed that rotates via CATransform3DMakeRotation from a positive (45) to a negative angle (-45)?
You are rotating around the x-axis. Without a perspective on your transform, how would you see the difference between positive and negative angles? Without perspective, both positive and negative rotations look the same (the decrease the height). You can apply perspective to your transform by changing the value in the third row and fourth column of your transform matrix by setting .m34. Search for "Core Animation perspective" and you will find a good explanation.
I ran your code above (on a simple view, filled with orange).
To more easily be able to see the rotation and the difference between positive and negative angles, I changed it so that it rotates around the z-axis.
These are four screenshots that I took during the animation. As you can see, the negative angle works, unless I misunderstood what you were trying to do.