Smoothest Way To Implement Back to Back UIAnimations - ios

I need to have my character jump (increase the origin.y by 50 and reverse) in a game I am creating.
So far I have come across two ways of doing this:
Method 1:
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAutoreverse
animations:^{
CGRect frame = self.kevinImageView.frame;
frame.origin.y -= 50;
self.kevinImageView.frame = frame;
} completion:^(BOOL finished){
CGRect frame = self.kevinImageView.frame;
frame.origin.y = 135;
self.kevinImageView.frame = frame;
}];
Issues: At the end of every complete jump animation (up and down) the ImageView jumps up and then back down (due probably to the tiny amount it takes for the completion block to be called). This is noticeable and I would much rather do without it.
Method 2:
[UIView animateWithDuration:1.0
animations:^{
CGRect frame = self.kevinImageView.frame;
frame.origin.y -= 50;
self.kevinImageView.frame = frame;
} completion:^(BOOL finished){
[UIView animateWithDuration:1.0
animations:^{
CGRect frame = self.kevinImageView.frame;
frame.origin.y += 50;
self.kevinImageView.frame = frame;
}];
}];
Issues: If I tap the view (I use a UITapGuestureRecognizer) and the ImageView is in the completion animation (going back down), the Image View will snap back to the bottom instead of finishing its animation. Again not a major issue, but something which I should be able to avoid.
Is there any method I have not come across which will resolve both of these issues, or a way to fix one of the methods?

Can you disable your UITapGuestureRecognizer when the animation is still running? And then enable it when the animation finishes.
if (!self.isAnimationRunning) {
self.isAnimationRunning = YES;
[UIView animateWithDuration:1.0
animations:^{
CGRect frame = self.kevinImageView.frame;
frame.origin.y -= 50;
self.kevinImageView.frame = frame;
} completion:^(BOOL finished){
[UIView animateWithDuration:1.0
animations:^{
CGRect frame = self.kevinImageView.frame;
frame.origin.y += 50;
self.kevinImageView.frame = frame;
} completion:^(BOOL finished){
self.isAnimationRunning = NO;
}];
}];
}

I'm very interested in this question so I do a simple demo to do a back to back animation.
I think maybe you can just use UIViewAnimationOptionAutoreverse and UIViewAnimationRepeat option to achieve a autoreverse animation.
[UIView animateWithDuration:1.0
delay:0
options:UIViewAnimationOptionAutoreverse|UIViewAnimationRepeat
animations:^{
[UIView setAnimationRepeatCount:1];
self.kevinImageView.center = CGPointMake(self.kevinImageView.center.x ,self.kevinImageView.center.y - 25);
} completion:^(BOOL finished){
self.kevinImageView.center = CGPointMake(self.kevinImageView.center.x ,self.kevinImageView.center.y + 25);
}];
and you can also set UIViewAnimationOptionAllowUserInteraction if you want to enable use interaction during the animation.

Related

Strange behavior animating a UIView using [UIView animateKeyframesWithDuration]

I'm trying to create an effect where three arrows go from visible to invisible alternatively.
I made a simple algorithm where every arrow would start from a different alpha value (if there is 3 arrows, the first one would start at alpha=1, the second one at alpha=0.6667, the third one at alpha=0.3337). I then start a key frame animation that:
Change the arrow opacity from its current alpha to 0 (the duration is computed)
Set instantly the arrow opacity to 1
Change the arrow opacity from 1 to the first value set
However it seems that some step are skipped for some reason.
A simple example:
[UIView animateKeyframesWithDuration:2 delay:0 options:UIViewKeyframeAnimationOptionCalculationModeLinear | UIViewKeyframeAnimationOptionRepeat animations:^{
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:0 animations:^{
_animatedView.alpha = 0.5;
}];
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:0.5 animations:^{
_animatedView.alpha = 0;
}];
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0 animations:^{
_animatedView.alpha = 1;
}];
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
_animatedView.alpha = 0.5;
}];
} completion:nil];
In that case it should go to 0.5 instantly, 0.5 to 0 in 1 second, go to 1 instantly, 1 to 0.5 in 1 second. It should therefore do a seamless transition that looks like the view is appearing and disappearing but it looks like the animation gets stuck on alpha=0.5 for a few time.
Theoretically, the effect should be the same as if using this key frame animation:
[UIView animateKeyframesWithDuration:2 delay:0 options:UIViewKeyframeAnimationOptionCalculationModeLinear | UIViewKeyframeAnimationOptionRepeat animations:^{
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:0 animations:^{
_animatedView.alpha = 1;
}];
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:1 animations:^{
_animatedView.alpha = 0;
}];
} completion:nil];
In case you want to animate N views in the same way:
CGFloat count = [self.animatedViews count];
CGFloat period = 1.0f / count;
__weak NSArray *weakViews = self.animatedViews;
[UIView animateKeyframesWithDuration:2.0f delay:0 options:UIViewKeyframeAnimationOptionCalculationModeLinear | UIViewKeyframeAnimationOptionRepeat animations:^{
for (NSUInteger index = 0; index < count; ++index) {
UIView *animatedView = weakViews[index];
CGFloat startDelay = period * index;
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:0 animations:^{
animatedView.alpha = startDelay;
}];
[UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:startDelay animations:^{
animatedView.alpha = 0.0f;
}];
[UIView addKeyframeWithRelativeStartTime:startDelay relativeDuration:0.0f animations:^{
animatedView.alpha = 1.0f;
}];
[UIView addKeyframeWithRelativeStartTime:startDelay relativeDuration:(1.0f - startDelay) animations:^{
animatedView.alpha = startDelay;
}];
}
} completion:nil];

Moving UIView OFF the screen from left to right and bring it IN again from left

How can I Move an UIView OFF the screen from left to right and then bring it IN the screen again from left side ?!!
Thanks in advance,
All you have to do is animate the x origin position of the view and use the devices screen with to determine how much. So for off screen to the right:
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_yourView.frame = CGRectMake(screenRect.size.width, _yourView.frame.origin.y, _yourView.frame.size.width, _yourView.frame.size.height);
}
completion:^(BOOL finished) {
//position screen left after animation
_yourView.frame = CGRectMake(-screenRect.size.width, _yourView.frame.origin.y, _yourView.frame.size.width, _yourView.frame.size.height);
}];
And for on screen go back to 0.0 x-position or whatever your starting point was:
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_yourView.frame = CGRectMake(0.0, _yourView.frame.origin.y, _yourView.frame.size.width, _yourView.frame.size.height);
}
completion:^(BOOL finished) {
//do something after animation
}];
To move OFF the screen:
CGRect frame = viewToMove.frame;
frame.origin.x = self.view.frame.size.width;
viewToMove.frame = frame;
To move the view back into the screen:
CGRect frame = viewToMove.frame;
frame.origin.x = 0;
viewToMove.frame = frame;
//Store your view's original rect
UIRect originRect = yourView.frame;
[UIView animateWithDuration:0.25 animations:^{
//create new rect and set its X coord to phone's width
CGrect rect = originRect;
rect.origin.x = self.view.frame.size.width;
yourView.frame = rect;
} completion:^(BOOL finished){
// when animation finished animating start new animation
//to bring your view to its original position
[UIView animateWithDuration:0.25 animation:^{
yourView.frame = originRect;
}];
}];
This code will move it to right in 0.3 seconds and then back from left to it's current position again in the next 0.3 seconds.
CGRect backToOriginal = Yourview.frame;
CGRect frameOnRight = CGRectMake ( screenWidth,backToOriginal.origin.y,backToOriginal.frame.size.width,backToOriginal.frame.size.height);
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{ Yourview.frame = frameOnRight; } completion:^(BOOL finished)[UIView setFrame: screenLeft;] [UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{ Yourview.frame = backToOriginal; }{ }];
There must be errors in this code in terms of syntax since I am not sitting on a MAC right now.
//To move view to left
[UIView animateWithDuration:0.5
delay:1.0
options: UIViewAnimationCurveEaseOut
animations:^{
self.yourView.frame = CGRectMake(distanceToLeft, distanceFromTop, yourView.width, yourView.height);
}
completion:^(BOOL finished){
NSLog(#"Moved to left!");
}];
// To move back to original position
[UIView animateWithDuration:0.5
delay:1.0
options: UIViewAnimationCurveEaseOut
animations:^{
self.yourView.frame = CGRectMake(originalX, OriginalY, yourView.Width, yourView.height);
}
completion:^(BOOL finished){
NSLog(#"Back to normal!");
}];

When creating an animation to cause a UIImageView to go up and down (almost float) infinitely, how do I stop a pause at the end of the animation?

Here's the code I'm using to take a UIImageView and make it float up and down.
[UIView animateKeyframesWithDuration:2.0 delay:0.0 options:UIViewKeyframeAnimationOptionRepeat animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.25 animations:^{
self.slider.transform = CGAffineTransformMakeTranslation(0, -5.0);
}];
[UIView addKeyframeWithRelativeStartTime:0.25 relativeDuration:0.5 animations:^{
self.slider.transform = CGAffineTransformMakeTranslation(0, 5.0);
}];
[UIView addKeyframeWithRelativeStartTime:0.75 relativeDuration:0.25 animations:^{
self.slider.transform = CGAffineTransformMakeTranslation(0, 0.0);
}];
} completion:^(BOOL finished) {
}];
However, it comes out looking like this with this delay after the animation ends and before it restarts.
How do I make it fluid?
I'm not sure what effect you're going for exactly, but I think this gives you something like your code does without the delay. I usually do this by animating a constraint, rather than using transforms. In this example, I've made an IBOutlet (topCon) to the constraint to the top of the view:
-(IBAction)floatView:(id)sender {
static int i= 1;
static float duration = .25;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.topCon.constant = self.topCon.constant - (5 * i);
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
i = (i == 1 || i == 2)? -2 : 2;
duration = 0.5;
[self floatView:self];
}];
}

UIView animation: simulating velocity

I am trying to use a UIView animation to simply move a view onto the screen:
[UIView animateWithDuration:.10 delay:0 options: nil animations:^
{
self.menusView.frame = endingMenuViewFrame;;
}
completion:^(BOOL finished)
{
}];
I'm wanting to add an animation so that UIView floats a little when it reaches the top before it comes down, i.e. akin to if someone jumps in the air - when they first jump, they shoot up quickly, but then gravity gradually slows them down as they reach the top of their jump, and eventually it pushes them back to earth. Does anyone know how to accomplish this?
Try with this code. This will make your view bounce three times with each time your height reduced by half. You may add more bounces.
CGFloat offset = 200.0;
CGRect originalFrame = self.menusView.frame;
[UIView animateWithDuration:1.0 animations:^{
CGRect frame = self.runnerAlertView.frame;
frame.origin.y = frame.origin.y - offset;
self.menusView.frame = frame;
} completion:^(BOOL finished){
[UIView animateWithDuration:1.0 animations:^{
self.menusView.frame = originalFrame;
} completion:^(BOOL finished) {
[UIView animateWithDuration:1.0 animations:^{
CGRect frame = self.menusView.frame;
frame.origin.y = frame.origin.y - 0.5 *offset;
self.menusView.frame = frame;
} completion:^(BOOL finished){
[UIView animateWithDuration:1.0 animations:^{
self.menusView.frame = originalFrame;
} completion:^(BOOL finished) {
[UIView animateWithDuration:1.0 animations:^{
CGRect frame = self.runnerAlertView.frame;
frame.origin.y = frame.origin.y - 0.25 * offset;
self.menusView.frame = frame;
} completion:^(BOOL finished){
[UIView animateWithDuration:1.0 animations:^{
self.menusView.frame = originalFrame;
}];
}];
}];
}];
}];
}];

Fade in and fade out a UIView while it is moving

It's easy enough to animate the view:
[UIView animateWithDuration:1.0
animations:^{theView.center = newCenter; theView.alpha = 0;}
completion:^(BOOL finished){
[theView removeFromSuperview];
}];
The problem is that when I add it as a subview, I want it to fade in and already look like it is moving. Right now it appears immediately, then moves and fades out.
So, I need to set it's initial alpha to zero, fade it quickly while it is moving, then fade it out. Is this possible with UIView animations? I can't have two competing animation blocks working on the same object right?
All you need to do is apply 2 animations back-to-back. Something like this ::
theView.alpha = 0;
[UIView animateWithDuration:1.0
animations:^{
theView.center = midCenter;
theView.alpha = 1;
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0
animations:^{
theView.center = endCenter;
theView.alpha = 0;
}
completion:^(BOOL finished){
[theView removeFromSuperview];
}];
}];
So in the 1st second it will appear while moving and then in the next second it will fade out
Hope this helps
Put the initial alpha=0 outside animation block.
theView.alpha = 0;
[UIView animateWithDuration:1.0
animations:^{
theView.center = newCenter;
theView.alpha = 1;
}
completion:^(BOOL finished){
// Do other things
}];

Resources