I referred to the DOC and it said:
completion
... This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called. ...
But I find that no matter you use the bool parameter or not, the completion: block will always execute after animations: block. Just like the two simple block-based animation code snippets shown below, both of them are doing the same.
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationCurveEaseInOut
animations:^{
[myView setAlpha:0.0f];
}
completion:^(BOOL finished) {
[myView removeFromSuperview];
}];
and
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationCurveEaseInOut
animations:^{
[myView setAlpha:0.0f];
}
completion:^(BOOL finished) {
if (finished) [myView removeFromSuperview];
}];
And I find that most people(including me) use the first one(even the apple's official doc example). So,
what's the finished parameter used for here exactly?
or what's the situation will be used?
The finished parameter will be NO when the animation was cancelled: typically, when you have interrupted the animation to start another one (e.g. you have begun a new animation, before the current one has ended, with the parameter to begin from the current state) or you have directly cancelled the animation.
In effect this cancels the current animation, but the completion block is still called. If you were chaining a sequence of animations you would want that chain to stop, so you would only continue the chain of the previous animation had finished.
As an example, imagine you had a game where a bomb was flying across the screen. If the user doesn't tap the bomb, it explodes when it reaches the edge. So you'd have one animation to move the bomb, and your completion block would have another animation to show the explosion, and maybe a call to some method to decrease a score or something.
If the user taps the bomb, you'd cancel the moving animation and have the bomb fly harmlessly away. Your original completion block would still be executed, so you'd need to know if the animation had finished on its own, or been cancelled.
Related
I have a UIView animation block which uses the UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoReverse option so it keeps going, but at some points I need to stop the animation but more importantly stop it and have the view return to its original position.
I have tried [dot1.layer removeAllAnimations]; but that stops it in its tracks without returning it to its original position, it stops mid animation.
I have tried stopping it then just running a second animation to return it to its correct frame, but that can be a bit jerky as I don't know how far it needs to go with what duration etc.
So is it possible to simply have the animation stop after it's completed its last cycle rather than mid way?
You should try this:
[UIView animateWithDuration:2 delay:0 options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction animations:^
{
view.frame = CGRectMake(0, 100, 200, 200);
} completion:^(BOOL finished)
{
if(! finished) return;
}];
And to stop the animation use this:
[view.layer removeAllAnimations];
I've tried the accepted solution and it didn't work for me. The animations just stopped after one run. Digging around on the net leads me to believe that [self.view.layer removeAllAnimations] is the recommended way to stop animations (another method can be seen in the following link as the asker's comment to the accepted solution
Stop an auto-reverse / infinite-repeat UIView animation with a BOOL / completion block).
The way that worked for me was the one given by BooRanger (even jrturton accepted it). I set a boolean property called stopAnimations in the class and initialise its value to NO. Whenever I want to stop the animations, I set stopAnimations to YES and then call [self.view.layer removeAllAnimations] in the next line. Then in the completion block, I do the following.
if(self.stopAnimations){
self.finishAnimations = NO;
return;
}
Hope this helps the someone. :)
I'm wandering what is the correct way to do "long" actions in response to user events. For example, I have this slide to cancel block that animates itself off screen over 0.5 seconds. The [self coreDataAction] may take about 0.3 seconds itself.
I want to ensure that the action completes once the user sees the end of the animation ( I do not want the user to accidentally navigate to a different controller or close the app thinking that the action is done).
Where should I put the [self coreDataAction]; in this case? Above the block, within the block or in the completion block?
//should I put it here?
CGPoint slideToCancelCenter = slideToCancel.view.center;
[UIView animateWithDuration:0.5 animations:^{
self.goToSleepButton.center = slideToCancelCenter;
[UIView setAnimationDuration:0.5];
CGPoint sliderCenter = slideToCancel.view.center;
sliderCenter.y += slideToCancel.view.bounds.size.height;
slideToCancel.view.center = sliderCenter;
//should I put it here?
// [self coreDataAction];
} completion:^(BOOL finished) {
//should I put it here?
} ];
A better way to handle this might be to animate the view on-screen then start the coreDataAction in the completion handler. Once the coreDataAction method execution is complete you can call a method to animate the slide to cancel view off-screen.
Assuming [self coreDataAction] executes on the main thread, I would say you should put it on the first line to ensure that the method is complete by the time the animation is done.
I'm trying to animate a label embedded in a UIView.
this is the code :
-(void)displayText:(NSString*)text {
[label setText:text];
[UIView animateWithDuration:5.0
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[labelView setAlpha:1.0];
}
completion:nil
];
[UIView animateWithDuration:5.8
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[labelView setAlpha:0.0];
}
completion:nil
];
}
To verify, the method is called, i set a breakpoint.
The calls return immediatly but only the end of animations is displayed.
I wired the UIView to the controller.
Pls help, I'm stuck.
Thanks in advance !
Patrick
Correct,
When you animate views like this the animation doesn't actually happen on screen until the next pass of the runloop (i.e. once your method returns).
UIView will coalesce animations that are programmed sequentially.
Use the completion block to fade back out. The code looks a bit odd but it works great!
[UIView animateWithDuration:5.0
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
[labelView setAlpha:1.0];
}
completion:^(BOOL completed){
[UIView animateWithDuration:5.8
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{[labelView setAlpha:0.0];}
completion:nil];
}];
In response to your comments:
The animations won't start until the next run of the runloop. They won't start until your app finishes what its doing. If you wait in the loop you will have the same problem and also freeze up your interface. Consider using individual labels for each letter, and add a progressively bigger delay for each animation. All these animation instructions will be queued up at once and then played out over the course of the next however many seconds. Imagine you are like a movie director, you tell each actor what to do in the next scene. Then, once everyone knows what to do you sit back and yell "action" and watch it all play out.
I have connected the two methods below to separate buttons in my UI but have noticed that after pressing the "VERSION 1" button that I could not press the button again until the animation duration within the method had ended. My understanding was that the animation uses its own thread so as not to block the main application.
// VERSION 1
-(IBAction)fadeUsingBlock {
NSLog(#"V1: Clicked ...");
[myLabel setAlpha:1.0];
[UIView animateWithDuration:1.5 animations:^{
[myLabel setAlpha:0.0];
}];
}
The older style version (below) does allow the button to be repressed before the animation timer ends, simply resetting the timer to start again. Should these both work the same, am I missing something or has there been a change in operation between 3.2 and 4?
// VERSION 2
-(IBAction)fadeUsingOld {
NSLog(#"V2: Clicked ...");
[myLabel setAlpha:1.0];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.5];
[myLabel setAlpha:0.0];
[UIView commitAnimations];
}
Cheers gary
Animating with blocks doesn't block the main thread. I think the behavior you're seeing is because, by default, user interaction is disabled duration animation with the new block calls. You can override this by passing UIViewAnimationOptionAllowUserInteraction (calling animationWithDuration:delay:options:animations:completion), like this:
-(IBAction) fadeUsingBlock {
NSLog(#"V1: Clicked ...");
[myLabel setAlpha:1.0];
[UIView animateWithDuration:1.5
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
[myLabel setAlpha:0.0];
}
completion:nil];
}
For animateWithDuration:, the class reference doesn't say anything about threading, so I am not sure.
For beginAnimations:context: and commitAnimation:, yeah, they run in a separate thread
UIView class Reference.
Some of the property changes to view objects can be animated—for example, setting the frame, bounds, center, and transform properties. If you change these properties in an animation block, the changes from the current state to the new state are animated. Invoke the beginAnimations:context: class method to begin an animation block, set the properties you want animated, and then invoke the commitAnimations class method to end an animation block. The animations are run in a separate thread and begin when the application returns to the run loop. Other animation class methods allow you to control the start time, duration, delay, and curve of the animations within the block.
In an iPhone application, I try to catch animation endings using setAnimationDidStopSelector. I try to suspend code execution until animation ends. I have tried this; set a global BOOL variable, set it to TRUE before commiting animation and after commiting animations waited using a while loop. In the setAnimationDidStopSelector, set the BOOL variable to FALSE and hope while loop to break. But unluckily this did not work, the code did not even fall into setAnimationDidStopSelector (I check that with some trace outputs). EDIT: If that BOOL variable handling is not added, code runs into the handler method.
The code where animation takes place is below:
self.AnimationEnded=FALSE;
[UIView beginAnimations:NULL context:NULL];
[UIView setAnimationDuration:2];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
// do sth.
[UIView commitAnimations];
while(![self AnimationEnded]);
Also this is the code of handler:
- (void)animationDidStop:(NSString*)animationID finished:(NSNumber*)finished context:(void*)context {
printf("abc\n"); fflush(stdout);
self.AnimationEnded=true;
}
What do you suggest?
In iOS 4, you can set a completion block instead of using an animation delegate and handlers. This is a simpler way of taking action when your animation has ended. I recommend using it if you aren't supporting pre-iOS 4 devices.
Your example changes to:
self.animationEnded = NO;
[UIView animateWithDuration:2
animations:^{ /* Do something here */ }
completion:^(BOOL finished){
printf("abc\n");
fflush(stdout);
self.animationEnded = YES;
}];
See +UIView animateWithDuration:animations:completion: at the iOS developer site for more.
You have to call setAnimationDelegate:to designate the object that you want the selector called upon when the animation stops. Assuming that the method that sets your flag to FALSE is in the same class as the one where you are creating the animation this will be self. For details see the UIView class reference.
Try this:
__block BOOL done = NO;
[UIView animateWithDuration:0.3 animations:^{
// do something
} completion:^(BOOL finished) {
done = YES;
}];
// wait for animation to finish
while (done == NO)
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
// animation is finished, ok to proceed
The animation will not start until this loop finishes. This loop will not finish until the animation starts.
while(![self AnimationEnded]);
Whatever you want to do after the animation needs to go in the animationDidStop method.