Second animateWithDuration call disables animation - objective-c

I have what I thought was a very simple case of animation. There is a view that sits at 0 alpha unless it becomes 1 until a future animation with one type of event, or becomes 1 for a few seconds with another type of event. My problem is that when those two events are called consecutively, so, in effect the following code is run:
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
self->bkgView.alpha = 1.0;
} completion:^(BOOL finished){}];
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
self->bkgView.alpha = 1.0;
} completion:^(BOOL finished){
NSLog(#"completion finished: %d", finished);
if (finished)
[UIView animateWithDuration:0.5 delay:3 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
self->bkgView.alpha = 0;
} completion:^(BOOL finished){}];
}];
I would think that the second call just cancels the first call and takes over from wherever that one was, so I get the flashing to alpha 1 for a few seconds. What actually happens is no animation at all, the completion block is called immediately with finished as true - so no indication it was cancelled. I thought I'd ask instead of banging my head on this seemingly trivial case, what am I doing wrong?
Edit: I meant it when I said that the code is trivial, but here is the entire viewcontroller you can put on a new single view project if you want to try it:
#import "ViewController.h"
#interface ViewController () {
UIView *bkgView;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGFloat x = 20;;
for (NSString *str in #[#"show", #"flash", #"both"]) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:NSSelectorFromString(str) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:str forState:UIControlStateNormal];
button.backgroundColor = [UIColor grayColor];
button.frame = CGRectMake(x, 100, 80, 40);
[self.view addSubview:button];
x += 100;
}
bkgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 100)];
bkgView.backgroundColor = [UIColor blackColor];
bkgView.alpha = 0;
[self.view addSubview:bkgView];
}
-(void)both {
[self show];
[self flash];
}
-(void)show {
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
self->bkgView.alpha = 1.0;
} completion:^(BOOL finished){}];
}
-(void)flash {
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
self->bkgView.alpha = 1.0;
} completion:^(BOOL finished){
[UIView animateWithDuration:0.5 delay:3 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
self->bkgView.alpha = 0;
} completion:^(BOOL finished){}];
}];
}
It loads three buttons. If you click "show", it fades in a rectangle. If you click "flash" it fades in a rectangle for 3 seconds then fades it out. My problem is if you click "both" which might not make much sense in this trivial example, but simulates calling the show and flash actions consecutively which can happen in my original code. I expected it to behave the same as "flash" (as it should just take over from the "show" function immediately), but it does nothing, the rectangle is not shown.

OK - couple things are going on...
Try this code - modified slightly from your example. I've changed the "fade in" duration to 1.5 seconds and the "fade out" duration to 2.5 seconds (can be changed at the end of viewDidLoad) so we can see a bit more of what's going on. I also added some NSLog() statements to follow the flow and print the timing information:
#import "SequentialAnimViewController.h"
#interface SequentialAnimViewController () {
UIView *bkgView;
double dur1;
double dur2;
NSDate *start;
}
#end
#implementation SequentialAnimViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGFloat x = 20;;
for (NSString *str in #[#"show", #"flash", #"both"]) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:NSSelectorFromString(str) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:str forState:UIControlStateNormal];
button.backgroundColor = [UIColor grayColor];
button.frame = CGRectMake(x, 100, 80, 40);
[self.view addSubview:button];
x += 100;
}
bkgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 100)];
bkgView.backgroundColor = [UIColor blackColor];
bkgView.alpha = 0;
[self.view addSubview:bkgView];
dur1 = 1.5; //0.3;
dur2 = 2.5; //0.5;
start = [NSDate date];
}
-(void)both {
[self show];
[self flash];
}
-(void)show {
[UIView animateWithDuration:dur1 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
NSLog(#"anim A started.... \t %f", [[NSDate date] timeIntervalSinceDate:self->start]);
self->bkgView.alpha = 1.0;
} completion:^(BOOL finished){
NSLog(#"anim A finished: %d \t %f", finished, [[NSDate date] timeIntervalSinceDate:self->start]);
}];
}
-(void)flash {
[UIView animateWithDuration:dur1 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
NSLog(#"anim B started.... \t %f", [[NSDate date] timeIntervalSinceDate:self->start]);
self->bkgView.alpha = 1.0;
} completion:^(BOOL finished){
NSLog(#"anim B finished: %d \t %f", finished, [[NSDate date] timeIntervalSinceDate:self->start]);
[UIView animateWithDuration:self->dur2 delay:3 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
NSLog(#"anim C started.... \t %f", [[NSDate date] timeIntervalSinceDate:self->start]);
self->bkgView.alpha = 0;
} completion:^(BOOL finished){
NSLog(#"anim C finished: %d \t %f", finished, [[NSDate date] timeIntervalSinceDate:self->start]);
}];
}];
}
#end
First thing to try is tapping "Flash". You should see what you expect to see, with debug console output along these lines:
anim B started.... 2.780529
anim B finished: 1 4.282427
anim C started.... 4.282540
anim C finished: 1 9.784028
"anim B started" is inconsequential, because it's the elapsed time between viewDidLoad and when you tap "Flash"
"anim B finished: 1" shows completion == true and the elapsed time is about 1.5 seconds from "B started"
"anim C started" will occur almost instantaneously after B finishes
Now, the elapsed time for "anim C finished: 1" (completion == true) may surprise you, as it's 5.5 seconds for a 2.5 second animation. That's because you get into the anim block before the delay happens. So,
3-second delay + 2.5-second anim == 5.5
This is our first clue why your "Both" doesn't do what you think it should.
Next thing to try is - re-launch the app for clarity - and tap "Show" ... the debug output will be:
anim A started.... 2.039016
anim A finished: 1 3.541033
Again, "started" is elapsed time between viewDidLoad and when you tap "Show", and "finished: 1" (completion == true), with an elapsed time of 1.5 seconds.
Now, though, while the black view is still showing, tap "Show" again:
anim A started.... 2.039016
anim A finished: 1 3.541033
anim A started.... 7.357023
anim A finished: 1 7.357585
Wait... 1.5 second anim only took 0.000562 seconds???
That's because black view's alpha was already == 1 ... so animating from 1.0 to 1.0 doesn't take any time.
A key point to note here: the animation does not take into account the condition of the property being animated. It evaluates the property itself.
So, what's happening when we tap "Both"? Restart the app and tap the button.
Here's my debug output:
anim A started.... 1.744193
anim B started.... 1.744692
anim B finished: 1 1.745148
anim C started.... 1.745233
anim A finished: 0 1.745463
anim C finished: 1 7.246692
Again, "started" is elapsed time between viewDidLoad and when you tap "Both"...
Then we have elapsed times of:
0.0004989
0.000456
0.000085
0.000229
5.501229
Here's what happened:
on tap, start animating from alpha == 0 to alpha == 1
immediately start animating from alpha == 1 to alpha == 1 ... no time required, and no actual animation occurs
finish that animation
immediately start animating from alpha == 1 to alpha == 0 (with 3-second delay)
first anim finishes NOT complete
final animation runs, but we don't see animation because the 0-to-1 animation never finished
What you need to remember is that the property gets set immediately... it doesn't wait for the visual animation to complete.
Hope that's not too much information to blur the understanding of what's going on.
Edit
As to UIViewAnimationOptionBeginFromCurrentState, try this...
Change the duration to 5 seconds:
dur1 = 5.0; //0.3;
and tap "Show" ... it will be a very slow "fade in" ... after 2 seconds, tap "Show" again. It will continue the animation.
Change your show method to:
-(void)show {
double f = bkgView.alpha == 0.0 ? 1.0 : 0.0;
[UIView animateWithDuration:dur1 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){
NSLog(#"anim A started.... \t %f", [[NSDate date] timeIntervalSinceDate:self->start]);
self->bkgView.alpha = f;
} completion:^(BOOL finished){
NSLog(#"anim A finished: %d \t %f", finished, [[NSDate date] timeIntervalSinceDate:self->start]);
}];
}
And you can tap show, watch it slowly fade-in, tap show again in the middle of the fade, and watch it slowly fade-out from the point it was at.
The difference is (mainly) due to your "Both" approach where you immediately call the next animation.

Related

iOS - UIView animateWithDuration not working properly

I'm trying to do some animation to my UIProgressView when i finished upload a photo to my server. When i set the duration to 5.0 or 10.0 seconds, it it not different and the completition is called immediately.
[UIView animateWithDuration:5.0 animations:^{
self.progressBar.progress = 1.0;
} completion:^(BOOL finished) {
NSLog(#"kykny udah");
if (finished) {
NSLog(#"finished");
Post *post = [mappingResult.dictionary objectForKey:#""];
[self.posts insertObject:post atIndex:0];
NSLog(#"caption: %# , items: %#",post.caption, post.items);
CGRect frame = self.progressView.frame;
frame.size.height = 0;
self.progressView.frame = frame;
[self.tableView reloadData];
}
}];
how come this issue happens ? It is just like a normal sequence code in reality
Try adding the following line after updating your frame changes like
self.progressBar.progress = 1.0;
[self.view layoutIfNeeded];
This will make your animation effect for the time you specified in animation duration.
If you want to show just the progress for 10sec then
// PROGRESS VIEW IS JUST A VIEW WITH SOME BACKGROUND COLOR WITH FRAME CGRectMake(0,0,0,10)
[UIView animateWithDuration:5.0 animations:^{
self.progressView.frame = CGRectMake(0,0,100,10); // adjust the frame to get the linear animation view
} completion:^(BOOL finished) {
NSLog(#"kykny udah");
if (finished) {
.....
self.progressView.frame = CGRectMake(0,0,100,0);
.....
}
}];

how to spin an image continuously

I have an wheel image in .png format, I want to know how can i animate so that it rotates continuously, I searched on stackoverflow and found certain snippets of code which helped me rotate my image but it wouldn't rotate continuously, it would just rotate for a few seconds and stop, the code as follows
the code in viewdidload
UIImageView *imageToMove =
[[UIImageView alloc] initWithImage:[UIImageimageNamed:#"horo_circle.png"]];
[self.view addSubview:imageToMove];
[self rotateImage:imageToMove duration:5.0
curve:UIViewAnimationCurveEaseIn degrees:180];
and the animation
- (void)rotateImage:(UIImageView *)image duration:(NSTimeInterval)duration
curve:(int)curve degrees:(CGFloat)degrees
{
// Setup the animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve:curve];
[UIView setAnimationBeginsFromCurrentState:YES];
// The transform matrix
CGAffineTransform transform =
CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(degrees));
image.transform = transform;
// Commit the changes
[UIView commitAnimations];
}
and the following lines after the import
#define M_PI 3.14159265358979323846264338327950288 /* pi */
#define DEGREES_TO_RADIANS(angle) (angle / 180.0 * M_PI)
You are better of doing this with a CABasicAnimation:
if ([self.spinnerOverlay animationForKey:#"SpinAnimation"] == nil) {
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat: 2*M_PI];
animation.duration = 10.0f;
animation.repeatCount = INFINITY;
[self.spinnerOverlay.layer addAnimation:animation forKey:#"SpinAnimation"];
}
In this code I check whether the animation is all ready set, not need to set it again.
The spinnerOverlay is in your case the UIImageView you want to rotate.
To stop the animation:
[self.spinnerOverlay.layer removeAnimationForKey:#"SpinAnimation"];
The code bellow will spin a UIImageView called iconView continiously until rotate == NO.
The image will always return to its original position.
- (void)performRotationAnimated
{
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
self.iconView.transform = CGAffineTransformMakeRotation(M_PI);
}
completion:^(BOOL finished){
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
self.iconView.transform = CGAffineTransformMakeRotation(0);
}
completion:^(BOOL finished){
if (_rotate) {
[self performRotationAnimated];
}
}];
}];
}
This is a variation on the same theme.
(Total duration: 2 * 0.5 = 1 sec for a spin)
Works like magic!
Here is another way to do it with a timer.
In your viewController.h file, you'll need to declare your timer and image:
#interface ViewController : UIViewController
{
IBOutlet UIImageView *spinningImage;
NSTimer *spinTimer;
}
Next, in your ViewController.m, you add this code and can make the spin go faster by lowering the time interval or by increasing the transform interval.
-(void) viewDidLoad {
spinTimer = [NSTimer scheduledTimerWithTimeInterval:.01 target: self selector:#selector(spinVoid) userInfo:nil repeats:YES];
}
-(void) spinVoid {
spinningImage.transform = CGAffineTransformRotate(spinningImage.transform, 0.001);
}
Hope this helps!
Now that we're at iOS6, please consider switching to block based animations (that are available since iOS 4.0) rather than the classical method. Try this:
[UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionRepeat animations:^{
image.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(degrees));
} completion:nil];
Using the method you've chosen for animations :
[UIView setAnimationRepeatCount:10000000];
or, you can specify the UIViewAnimationOptionRepeat flag in the blocks method animateWithDuration:delay:options:animations:completion:

animateWithDuration not working

I'm trying to animate a view with some buttons and controllers for my main view into the screen from the right side of the screen. The problem is it doesn't animate, it just goes directly to the finished state.
This is my code:
UIStoryboard* storyBoard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
options = [storyBoard instantiateViewControllerWithIdentifier:#"OptionsView"];
options.delegate = self;
[self.view addSubview:options.view];
options.view.center = CGPointMake(floor(total_width+options.view.frame.size.width/2)+10, floor(total_height/2));
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
options.view.center = CGPointMake(floor(total_width-options.view.frame.size.width/2)+10, floor(total_height/2));
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
EDIT
To make things more strange, if I put this animation inside the completion block of another animation then this one works, but not the new one.
UIButton* boton = (UIButton*) sender;
[UIView animateWithDuration:5.0
delay:0.0
options: UIViewAnimationOptionCurveLinear
animations:^{
boton.alpha = 0.0;
//Jumps directly to 0, animation doesn't work
}
completion:^(BOOL finished){
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
options.view.center = CGPointMake(floor(total_width-options.view.frame.size.width/2)+10, floor(total_height/2));
//It works now, but it doesn't take 5 seconds to start, it starts right away
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}];
The line before the animateWithDuration:... call directly assigns the center to the same value that you're animating to. Either remove that or assign the initial center value there. An animation that has the same start and end values obviously has no effect.

IOS - animation not responding

I want to create an animated menu.
first it's hidden outside screen
when user taps the screen, it will slide in
user can tap it and it do something
then after 3s it will slide outside screen
But problem is it not response when I tap it. Why?
example code: All code is in my UIView class.
showing = NO;
box = [[UIView alloc] initWithFrame:CGRectMake(500,-200,200,200)];
box.backgroundColor = [UIColor redColor];
UITapGestureRecognizer *tapBox = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapMenuHandler:)] autorelease];
[box addGestureRecognizer:tapBox];
[self addSubView:box];
and:
-(void) tapMenuHandler: (UIView *)obj {
//Do something
NSLog(#"tap box");
}
and:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (!showing) {
box.frame = CGRectMake(500, -200, 200, 200);
[UIView animateWithDuration:0.5f delay:0.3f options:(UIViewAnimationOptionAllowUserInteraction) animations:^{
box.frame = CGRectMake(500, 200, 200, 200);
} completion:^(BOOL finished) {
showing = YES;
[UIView animateWithDuration:0.5f delay:3.0f options:(UIViewAnimationOptionAllowUserInteraction) animations:^{
box.frame = CGRectMake(500, -200, 200,200);
} completion:^(BOOL finished) {
showing = NO;
}];
}];
}
}
Thank for your help and sorry for my English :)
Edit: more detail
I tried set start my box's origin in screen (ex. 500,600), It still always response when tap at start point (500,600) even though my box move to another position.
Update:
I changed my way to move box outside screen by use NSTimer then it's work!
[UIView animateWithDuration:0.5f delay:0.3f options:UIViewAnimationOptionAllowUserInteraction animations:^{
box.frame = CGRectMake(500,200,200,200);
} completion:^(BOOL finished) {
isShowMenubar = YES;
//use NSTimer instead of delay
[NSTimer scheduledTimerWithTimeInterval:3.0f target:self selector:#selector(hideBox) userInfo:nil repeats:NO];
}];
and
-(void)hideBox {
[UIView animateWithDuration:0.5f delay:0.0f options:UIViewAnimationOptionAllowUserInteraction animations:^{
box.frame = CGRectMake(500,-200,200,200);} completion:^(BOOL finished) {
isShowMenubar = NO;
}];
}
The reason why the box is not receiving touches has to do with how animation works. Your box is actually offscreen, but visually, it's going through the animation.
Even though you set the delay as 3.0, it still moves the box beforehand (due to the animation method still being called). So in your current code, if you tapped on the box at (500, -200) then it will receive the touch.
To fix this, either use an NSTimer or Grand Central Dispatch to delay.
NSTimer:
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self action:#selector(dismissBox) userInfo:nil repeats:NO];
The problem with NSTimer is that you'll have to put that animation code in a separate method.
Grand Central Dispatch:
double delayInSeconds = 3.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[UIView animateWithDuration:0.5f animations:^{
box.frame = CGRectMake(box.frame.origin.x, -200, 200,200);
}completion:^(BOOL finished) {
showing = NO;
}];
});
I ran the code and didn't see the red view either. Then playing around for a bit, I got that your view is animating but is still out of bounds so you never see it. try changing the animation block to something like (the origin of the frame is at the left bottom corner)
box.frame = CGRectMake(100, 100, 200, 200);
and hopefully it will come animating.
Also, assuming that you run these in your view controller, the correct method should be
[self.view addSubview:box];
hope this helps

I want to flash an UIImageview on the iPad, how do I do that?

I want to let a UIImageView flash several times.
Currently I don't know how to do that.
Actual code:
-(void)arrowsAnimate{
[UIView animateWithDuration:1.0 animations:^{
arrow1.alpha = 1.0;
arrow2.alpha = 1.0;
arrow3.alpha = 1.0;
NSLog(#"alpha 1");
} completion:^(BOOL finished){
[self arrowsAnimate2];
}];
}
-(void)arrowsAnimate2{
[UIView animateWithDuration:1.0 animations:^{
arrow1.alpha = 0.0;
arrow2.alpha = 0.0;
arrow3.alpha = 0.0;
NSLog(#"alpha 0");
} completion:^(BOOL finished){;}];
}
later on I call it like this:
for (int i = 0;i < 10; i++){
[self arrowsAnimate]; }
This gives me 10x alpha 1, and then 10x alpha 0. In the middle we see only one animation.
Any suggestions?
Thanks.
There is a simpler way to achieve a flashing animation using only 1 animation block:
aview.alpha = 1.0f;
[UIView animateWithDuration:0.5f
delay:0.0f
options:UIViewAnimationOptionAutoreverse
animations:^ {
[UIView setAnimationRepeatCount:10.0f/2.0f];
aview.alpha = 0.0f;
} completion:^(BOOL finished) {
[aview removeFromSuperview];
}];
The trick is to use [UIView setAnimationRepeatCount:NTIMES/2]; *_inside_* your animation block.
No need for extra functions or extra loops.
Use
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
and pass the UIViewAnimationOptionRepeat and probably UIViewAnimationOptionAutoreverse in your options. You shouldn't need to provide a completion block and only perform the first animation.
Edit: here is some sample code for an image that fades in and out indefinitely.
[UIView animateWithDuration:1.0
delay:0.0
options:(UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse)
animations:^{
self.myImageView.alpha = 1.0;
}
completion:NULL];
Edit 2: I see you actually need to flash it 10 times only. I wasn't able to do that with blocks actually. When the completion block executed, the animation seemed to complete instantly the remaining 9 times. I was however able to do this with just the old-style animations quite easily.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0];
[UIView setAnimationRepeatCount:10.0];
[UIView setAnimationRepeatAutoreverses:YES];
self.myImageView.alpha = 1.0;
[UIView commitAnimations];
Edit 3: I found a way to do this with blocks.
- (void)animate
{
if (self.animationCount < 10)
{
[UIView animateWithDuration:1.0
animations:^{
self.myImageView.alpha = 1.0;
}
completion:^(BOOL finished){
[self animateBack];
}];
}
}
- (void)animateBack
{
[UIView animateWithDuration:1.0
animations:^{
self.myImageView.alpha = 0.0;
}
completion:^(BOOL finished){
self.animationCount++;
[self animate];
}];
}
Above blinking may not work when your app go background and foreground at time of blinking.
Instead of these you can take a transparent image and your actual image and animate your ImageView
UIImageView *imageview=[UIImageView new];
imageview.animationDuration=1;
imageview.animationImages = your array of images;
[imageview startAnimating];
You need to wait for an animation to complete before launching a new one. You could chain your completion block in animate2 to go back to animate, and stop based on a counter property, implementing your loop in the animate/completion blocks instead of a separate loop.