This question already has answers here:
How to make UIView animation sequence repeat and autoreverse
(3 answers)
Closed 9 years ago.
I've written the following code to make my UIView constantly fade in and out. (FadeAlphaValue is a BOOL)...
-(void) fade {
[UIView animateWithDuration:1.0
animations:^{
fadeView.alpha = (int)fadeAlphaValue;
}
completion:^(BOOL finished){
fadeAlphaValue=!fadeAlphaValue;
[self fade];
}];
}
It works but I have a feeling it's going to cause some weird crash if I let it run forever... I'm not to familiar with [..^{..} completion^{...}]; that notation. And I feel like since I'm calling the "fade" function during completion it won't actually complete until the "fade" function completes, the problem is the fade function will call itself again before it completes and so on and so on, it seems like an infinite loop... Is this going to cause some kind of weird multi-threading freeze after a few hundred iterations?
A better approach to this is to use the UIViewAnimationOptionRepeat option which will repeat an animation indefinitely.
[UIView animateWithDuration:1.0f
delay:0.0f
options:UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
animations:^{
// your animation code here
}
completion:nil];//NOTE - this REPEAT animation STOPS if you exit the app (or viewcontroller)... so you must call it again (and reset all animation variables (e.g. alpha) in the UIApplicationDidBecomeActiveNotification function as well (or when the viewcontroller becomes active again)!
Related
I'm doing two animations on the same UIImageView, using blocks. Animations are not quite back to back, but almost; there is some logic inbetween.
animate UIImageView from one location on the view to another.
execute logic that determines whether image is allowed to stay there
if not allowed, undo the animation (UIImageView springs back to original location)
If I implement this as above, only the second animation shows (this is normal behavior from what I understand). If I nest the logic and the second animation block inside the completion block of the first, I see both animations, but there's a fair amount of code to jam into that completion block and it just seems ugly and out of place.
In the non-nested configuration, why does iOS want to cut short the previous animations and execute only the final one, and how can I force it to wait on the first one before going to the next? I don't think it needs to block the main thread or "sit and spin" in a completion block; I just want all animations to be shown. Tried adding delay to second animation to no avail.
Is this a job for CAKeyframeAnimation?
// first animation
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationCurveLinear
animations:^{movingColor.center = newCenter;
movingColor.transform = scaleTransform;}
completion:^(BOOL finished){ NSLog(#"forward animation done");}];
if (/* ...color is allowed at that location */) {
// do some stuff
} else {
// undo the animation
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationCurveLinear
animations:^{movingColor.center = origCenter;
movingColor.transform = scaleTransform;}
completion:^(BOOL finished){ NSLog(#"back animation done");}];
}
The second animation should be done conditionally inside the first's completion block. Putting it into a function will make it more readable.
- (void)moveColor:(UIView *)view to:(CGPoint)center delay:(NSTimeInterval)delay completion:(void (^)(BOOL finished))complete {
[UIView animateWithDuration:0.5 delay:delay options:UIViewAnimationCurveLinear animations:^{
view.center = center;
view.transform = scaleTransform; // probably don't need this
} completion:completion];
}
This way your logic can be separate from the animation code
- (void)someMethod {
[self moveColor:movingColor to:newCenter delay:0.0 completion:^(BOOL finished) {
if (/*it can stay*/) {
// stuff
} else {
[self moveColor:movingColor to:origCenter delay:2.0 completion:^(BOOL finished) {}];
}
}];
}
The correct way to do it is as you said to set the second one in the completition of the first one that is the correct way,
You can also adda delay on the start of the other, this may or may not work it depends on alot of variables
Your second animation will have a delay of .5 (time for first animation to complete )
[UIView animateWithDuration:0.5
delay:0.5
options: UIViewAnimationCurveLinear
animations:^{movingColor.center = origCenter;
movingColor.transform = scaleTransform;}
completion:^(BOOL finished){ NSLog(#"back animation done");}];
The animateWithDuration is executed asynchronously, and returns right away. That is, the next line of code is executed without waiting for the animation to finish. You either put your code in the completion block if you want it to be executed after the animation is finished, or you accept that the second animation is started (thus canceling the first) immediately.
If you want to indicate to the user that something was wrong by starting the animation, but not completing it, you could execute the second animation with a delay. Remember to also set the the UIViewAnimationOptionBeginFromCurrentState option if you delay the second animation with less than the duration of the first:
[UIView animateWithDuration:0.5
delay:0.25 //or some number
options: (UIViewAnimationCurveLinear | UIViewAnimationOptionBeginFromCurrentState)
animations:^{movingColor.center = origCenter;
movingColor.transform = scaleTransform;}
completion:^(BOOL finished){ NSLog(#"back animation done");}];
Why does the following code log 'Done' as soon as it is fired?
[UIView animateWithDuration:0.3
animations:^{
NSLog(#"Start");
}
completion:^(BOOL finished){
NSLog(#"done");
}
];
Because you're not actually animating anything (a NSLog cannot be animated). You need to animate an object or else the completion block will be called straight away. The 0.3 second duration will be ignored if there is nothing being animated.
Because you aren't animating anything. If you change the value of some UIView in your animation block, you will correctly see "done" outputted after the 0.3 second delay.
I tested with a 5 second delay using exactly your code, only adding in something to animate to confirm.
I'm trying to implement multistage animation using UIViewAnimationOptionBeginFromCurrentState to allow the user to cancel the animation at will. The animation is a view that continually and cyclically animates between two sizes. When the user touches the view to cancel the animation, I want the view to quickly revert back to its original, small size, whether it was growing or shrinking at the time.
I'm implementing the multistaging aspect by having two separate animations, one for growing the view and one for shrinking it. Each calls the other routine in its completion block, thus cycling forever unless the abort flag has been set.
I get the expected behaviour if abort is called during the grow-the-view animation: the animation quickly and immediately returns the view to its original, small size and stops. Good!
However, if abort is called during the shrink-the-view animation cycle, the view continues to shrink at the same speed (and then stops as expected), as if the UIViewAnimationOptionBeginFromCurrentState option was never invoked.
Code will hopefully make this clearer and hopefully somebody can see what I can't.
- (void)stopAnimating {
abort = YES;
[UIView animateWithDuration:.2 // some small interval
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{ self.frame = minRect;}
completion:^(BOOL done){}
];
}
- (void)animateSmall {
[UIView animateWithDuration:4
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{self.frame = minRect;}
completion:^(BOOL done){if (!abort)[self animateBig];}
];
}
- (void)animateBig {
[UIView animateWithDuration:4
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{self.frame = maxRect;}
completion:^(BOOL done){if (!abort)[self animateSmall];}
];
}
Just a guess here, because your code looks exactly as I would have done it. But I think what's going on is that the abort animation is setting the same attribute to the same value as the animation it's interrupting, and this gets treated as equivalent and not in need of change (even though the duration changes).
A test of this theory - and a fix to the problem - would be to make your oscillating minRect just a little bit different in size than your steady state minRect.
Hope this works. Good luck.
I'm afraid that the title is correct english expression but,
I have an animation on iOS with 4 scenes.
I used setAnimationDidStopSelector: method.
Here's the question: How can I make stopping function for the animation?
In iOS 4 and later you should use block based animation. Using these newer methods you can very easily specify some code to run once the animation has finished. For example:
[UIView animateWithDuration:1.0
animations:^{
// This code will be animated for 1 second.
[anObject setAlpha:0.0];
}
completion:^(BOOL finished) {
// This code will be executed once the animation has completed.
[anObject removeFromSuperview];
}];
A little ugly, but pretty lazy:
add a flag to know when it should animate, and when not. Put your animation under if block (or something same) and just switch flag when you need.
I have the following block of code to fade out an introView(UIView)
// Hide intro view after 5 seconds
[UIView animateWithDuration: 1.0
delay: 5.0
options: (UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveLinear)
animations: ^{
introView.alpha = 0;
}
completion: ^(BOOL finished) {
[introView removeFromSuperview];
}];
I have a skip button inside the introVew but there is no interaction whatsoever, am I missing something? I have to add this is a Universal app targeting 3.2 and I'm using XCode 4.2
Pretty sure this is impossible pre-4.0:
UIView userInteractionEnabled Docs
During an animation, user interactions are temporarily disabled for
all views involved in the animation, regardless of the value in this
property. You can disable this behavior by specifying the
UIViewAnimationOptionAllowUserInteraction option when configuring the
animation.
There seems little point in targeting 3.2 in an app you haven’t released yet.
Are you setting your button alpha to 0?
If yes here is an interesting thing about animation.
What you see on the screen during the animation is not what the application sees.
The moment you set your alpha to 0, the alpha is 0 for that view, even if you are still seeing it on the screen.
Also, a view that has an alpha lower that 0.05 (don't recall the exact number) won't get touch event.
What you can do is to implement the - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event of that view's superview. or the touchesEnded... as you like.
(Assuming that your not setting it's alpha to 0.)
So you can test for touche that occur where the button is, or just remove that button and let any touch on the screen cancel your animation.
You may also be interested in this post:
Core Animation, unexpected animated position and hitTest values
I found another circumstance which could cause this. I haven't seen this answer anywhere else. It does not deal with alpha at all.
If you use a delay in the call to UIView.animate(), then even if you specify the .allowUserInteraction option, the view does NOT receive touches during the delay period. I have no idea why, but I could help it by moving the code block to another function, and using a performSelector after the same delay seconds, and in the block I run the code without delay.
I had the same problem with a button that I animated with changing the alpha. Cueing off VinceBurn's answer...
What you see on the screen during the animation is not what the application sees. The moment >you set your alpha to 0, the alpha is 0 for that view, even if you are still seeing it on the >screen.
AND view that have an alpha lower that 0.05 (don't recall the exact number) won't get touch >event.
… the simple solution of just making the minimum alpha 0.1 instead of 0.0 worked for me:
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionAllowUserInteraction
animations:^{
self.myButton.alpha = 0.1f;
}
completion:^(BOOL finished){
}]
Button registered the touchUpInside all the time with no additional method needed, and there was virtually no difference in appearance from taking the alpha to zero.
This won't work in iOS 3.2 since Blocks are only available in iOS4
you will have to use the standard animation techniques, in a separate thread so that you don't block the interface
[UIView beginAnimations:nil context:nil];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:view cache:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:1.0];
[view1 setHidden:TRUE];
[UIView commitAnimations];