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.
Related
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 a subView that I want to toggle between hidden and not hidden by a button. How do I fade in the subview and fade it out? For now it just appears immediately and disappear immediately when I toggle the button.
I am wondering what is the easiest way to do this animation.
Thanks
On iOS 4.0+ Apple recommends you use their new, block-based animation methods. Using these, the code would look something like this:
[UIView animateWithDuration:2.0
animations:^{myView.alpha = 0.0;}];
The properties you are animating go inside the block (the ^{...} part). Blocks are sort of like functions, so you can put multiple lines of code inside of them, if you want to animate multiple properties. For example:
[UIView animateWithDuration:0.2
animations:^{
view.alpha = 0.0;
view.backgroundColor = [UIColor redColor];
}];
If you need to perform an action after the animation is complete, use the +animateWithDuration:animations:completion: method (which also uses blocks), for example:
[UIView animateWithDuration:0.2
animations:^{view.alpha = 0.0;}
completion:^(BOOL finished){ [view removeFromSuperview]; }];
For more info, check out the UIView Class Reference 'Animations' section and 'Animating Views with Blocks' section.
This is the old pre-4.0 way:
http://objcolumnist.com/2009/07/18/simple-uiview-based-animations-on-the-iphone/
... which has the advantage of being conceptually simple and easy to implement.
float alpha = 1.0; // or 0.0 if it's already visible and you want to fade out
[UIView beginAnimations:#"" context:NULL];
[UIView setAnimationDuration:2.0]; // seconds, not ms. guess how i know?
[mySubView setAlpha:alpha];
[UIView commitAnimations];
It appears to me these two class methods are not interchangeable. I have a subview of UIView with the following code in the touchesBegan method:
if (!highlightView) {
UIImageView *tempImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Highlight"]];
self.highlightView = tempImageView;
[tempImageView release];
[self addSubview:highlightView];
}
highlightView.alpha = 0.0;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
highlightView.alpha = 1.0;
[UIView commitAnimations];
When I touch the Button, the highlight fades in, like you would expect. When I touch up immediately (before the animation is finished), my touchesEnded gets called. This is the behavior I want.
But now, I've become a big fan of blocks and try to use them wherever possible. So I replaced the UIView animation code with this:
[UIView animateWithDuration:0.2 animations:^{
highlightView.alpha = 1.0;
}];
Results: the highlight still fades in as expected, but if I touch up before the animation is finished, my touchesEnded does not get called. If I touch up after the animation is finished, my touchesEnded does get called. What's going on here?
The new animation blocks in iOS 4 by default disable user interaction. You can pass in an option to allow views to respond to touches during animation using bit flags in conjunction with the animateWithDuration:delay:options:animations:completion method of UIView as such:
UIViewAnimationOptions options = UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction;
[UIView animateWithDuration:0.2 delay:0.0 options:options animations:^
{
highlightView.alpha = 1.0;
} completion:nil];
Documentation
One more thing is that Appple doesn't recommend to use [UIView beginAnimations:context:], you can find it in beginAnimations docs
Use of this method is discouraged in iOS 4.0 and later. You should use
the block-based animation methods to specify your animations instead.
Probably Apple can mark old methods as deprecated in the future releases and won't support them, so using block-based methods is really more preferable way for performing animation.
I wan't to perform an animation before quitting a view. The problem is that if I write this code:
[self animationMethod];
[self removeFromSuperview];
the animation is not presented because the removeFromSuperview instruction is immediatly executed, i suppose.
There's a way to specify thath the removeFromSuperview method must be executed after a a specified time? thanks.
Does animationMethod use a [UIView beginAnimations: context:] section? If that's the case, you should use the animation delegate. Specifically:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(removeFromSuperview)];
//Your animation stuff
[UIView commitAnimations];
Otherwise, if you're doing something else that doesn't have a callback, you can call the method after a delay using:
[self performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:0.25f];
where 0.25f is the delay you want to use. I chose 0.25 because that is the default animation length for animation blocks (as shown above).
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.