Checking When animation stopped - objective-c

I am working on a simple UIImageView animation. Basically I got a bunch of image numbers from 0-9 and I am animating them using this code.
myAnimation.animationImages = myArray;
myAnimation.animationDuration = 0.7;
myAnimation.animationRepeatCount = 12;
[myAnimation startAnimating];
Here myArray is an array of uiimages i.e. 0.png, 1.png etc.
All is well and its animating just fine. What I need to know is when do I know when the animation has stopped? I could use NSTimer but for that I need a stopwatch to check when the animation starts and stops. Is there a better approach, meaning is there a way I can find out when the UIImageView stopped animating?
I looked at this thread as reference.
UIImageView startAnimating: How do you get it to stop on the last image in the series?

Use the animationDidStopSelector. This will fire when the animation is done:
[UIView setAnimationDidStopSelector:#selector(someMethod:finished:context:)];
then implement the selector:
- (void)someMethod:(NSString*)animationID finished:(NSNumber*)finished context:(void*)context {
}

Yes, there is a much better approach than using NSTimers. If you're using iOS 4 or higher, it is better you start using block animations. try this
[UIView animateWithDuration:(your duration) delay:(your delay) options:UIViewAnimationCurveEaseInOut animations:^{
// here you write the animations you want
} completion:^(BOOL finished) {
//anything that should happen after the animations are over
}];
Edit: oops I think I read your question wrong. In the case of UIImageView animations, I can't think of a better way than using NSTimers or scheduled events

You can also try this:
[self performSelector:#selector(animationDone) withObject:nil afterDelay:2.0];

Related

Repeating NSTimer causes truncated animateWithDuration on first animation

I have a UIViewController that, depending on the frequency set by user, displays images in a animateWithDuration fade-in/fade-out every X seconds (say, 5 or 10). To manage the regularly timed calls to fade-in/out the images, I have a NSTimer that is set every time viewWillAppear is called.
Some function that does the animation, let's call it "showImageNow":
// on...
[UIView animateWithDuration:someInterval
delay:0
options:UIViewAnimationCurveEaseInOut
animations:
^{
// UI alpha = ... code here
}
// off...
completion:^(BOOL finished){
[UIView animateWithDuration:someOtherInterval
delay:yetAnotherValue
options:UIViewAnimationCurveEaseInOut
animations:
^{
// UI alpha = ... code here
}
completion:nil
];
}
];
In viewWillAppear:
if(myTimer != nil)
{
[myTimer invalidate]; // in case user changed the frequency in settings view
}
myTimer = [NSTimer scheduledTimerWithTimeInterval: [[NSUserDefaults standardUserDefaults] doubleForKey:#"userFrequency"]
target:self
selector: #selector(showImageNow:)
userInfo: nil
repeats: YES];
In viewDidAppear:
if(myTimer) { [myTimer fire]; }
While everything works as expected most of the time, the fade-out part of the first animation is cut off/stutters every time the UIViewController is re-appeared (from say, app went to background or app was in another view). The fade-in part of the animation works always, oddly enough. This is observed on a real device, not the simulator. So the fade-in/out works for every animation except the first one (the fade-out part doesn't work).
Notes:
Yes, I've tried [myTimer fire] in the viewWillAppear (instead of viewDidAppear) as well, but this causes other issues like the UIViewController's elements show up rather abruptly when user switches to that view from other views or from background mode.
The frequency is much longer than the animateWithDuration's animation values, so there shouldn't be any frame overlaps or whatever UI overlaps there may be.
I put debug code before every animateWithDuration call in the UIVIewController, so I know for certain that no other animateWithDuration is interrupting the very first image animateWithDuration call.
So this is perplexing. I've tried using CADisplayLink but apparently that's not the right way to do it. Any ideas how to resolve this issue?
I'd try enabling the UIViewAnimationOptionBeginFromCurrentState option in your animation code and see if that helps

UIView animation stop when UIView willDisappear

I am not much into iOS animation. What I am trying to achieve is a simple message view that slide vertical from bottom of screen to a given y, then after few instants the UIView rollback in vertical to go off screen.
[UIView animateWithDuration:0.5f
animations:^{
self.messageView.frame=CGRectMake(x, y -80, width, height);
}
completion:^(BOOL finished){
if (finished) {
[UIView animateWithDuration:0.5f delay:2.0f options:UIViewAnimationOptionCurveLinear
animations:^{
self.messageView.frame=CGRectMake(x, y + 80, width, height);
}
completion:^(BOOL finished){
// do something...
}
];
}
} ];
This is working fine, but I am having a problem using this mechanism in a iOS UITabBar application: when I change tab, the animation stop, I can infact see that "finished" completion is "false". Therefore the second block is not called, and the message view stays on.
Here are the questions:
my first concern is to understand if the code I have written is correct, regarding the nested animations.
I could solve by ignoring 'finished' and execute code anyway, but I don't feel it is a good idea
within the last completion block, I have put some programming logic, basically I am restoring few UIButtons state, and some other little UI change. At this point I don't know if it is a good idea, it seems not, but how can let the UI knows that the message view has disappeared. NSNotification and KVO seems a bad idea when fast responsive UI change are involved.
You can stop all animations for a layer by sending the layer a removeAllAnimations message.
[sel.view removeAllAnimations];

UIView animation to "rewind" a pan gesture: recursion?

I have a scenario where the user dragged a UIView somewhere; I want to return it to where it started, and I want it to return along the same track and at the same speed that the user dragged. I have come up with a recursive solution that looks like this:
#property (...) NSMutableArray *dragTrack; // recorded during pan gesture
- (void)goHome
{
[self backtrack: [self.dragTrack count]
withDelay: 1.0 / [self.dragTrack count]];
// interim version of delay; will implement accurate timing later
}
- (void)backtrack: (int)idx withDelay: (double)delay
{
if (idx > 0){
[UIView animateWithDuration:0 delay:delay options: 0
animations: ^{
self.center = [[self.dragTrack objectAtIndex:idx - 1] CGPointValue];
}
completion:^(BOOL finished){
[self backtrack: idx - 1 withDelay: delay];
}];
} else {
// do cleanup stuff
}
}
This works, and recursion depth doesn't appear to be an issue - my drag tracks are typically only a couple of hundred points long. (I assume the chances of the recursive call to backtrack getting tail call optimized are rather slim?). But I'm still wondering: is this a reasonable/normal/safe solution to what I'm trying to achieve? Or is there a simpler way to pass a collection of timestamps and states to an animation and say "play these"?
You are not causing recursion there. Since the completion block is called asynchronously, it is not getting deeper into function stack. You can see it yourself by setting a breakpoint there and check the stack trace.
You can imagine NSTimer there triggering theoretically infinite times and not causing recursion.
To your second question, you can make such animation. It is called CAKeyframeAnimation and it is part of QuartzCore framework. It would require you to work with layers instead of views, but it is not that difficult.
Just one note to your code: try to use option UIViewAnimationOptionCurveLinear for the animation to be more smooth.

Detecting if a Cocoa Animation is in Progress (Using BOOL) to prevent multiple simultaneous animations

In my app, I run numerous animations, moving objects and shapes etc.
Many of the animations lead on to the animation of other objects.
A problem with the application is that if an animation is running and you start another that involves one of the objects currently being animated, the screen goes white/we get unexpected results.
I worked up some sample code to show what we are trying to do:
UIButton *button;
BOOL currentlyShowingQuestion;
- (void)viewDidLoad
{
[self moveButton:button];
currentlyShowingQuestion = NO;
[NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:#selector(hasBoolChanged) userInfo:nil repeats:YES];
}
- (void)moveButton:(UIButton *)buttonToUse {
currentlyShowingQuestion = YES;
NSLog(#"moveButton Called! Yippee!!!!!!!");
[UIView animateWithDuration:0.5 delay:0.5 options:nil animations:^{
buttonToUse.frame = CGRectMake(10, 10, 300, 440);
} completion:^(BOOL finished){
currentlyShowingQuestion = NO;
}];
}
-(void)hasBoolChanged {
if (currentlyShowingQuestion == YES) {
NSLog(#"Yes");
}
else if (currentlyShowingQuestion == NO) {
NSLog(#"No");
}
}
What I end up actually seeing is like the currentlyShowingQuestion BOOL both get changed instantly when the method is run, so it changes to YES and instantly back to NO. Then the animation runs. Why is this? What's wrong with the order?
Any help appreciated.
Sorry about the code formatting - I couldn't figure out the syntax here.
Thanks in advance,
Sam
I don't really understand what you try to achieve. At the beginning you start a timer that checks whether the value of the boolean has changed. I think it is kind of an overkill for this task but thats ok.
Your animation is delayed 0.5s for some reason and then you have a 0.5s duration for an animation and that is really fast so it could seem that you instantly change the value.
I'm not too familiar with UIView, but according to the docs, the completion block may be called before the animation's finished. Try add a condition whether finished is YES before you change currentlyShowingQuestion to NO.

Consecutive animations using nested animation blocks

I'm looking for a way to implement consecutive animations using nested animation blocks.
Somewhat complicated by happening inside a UIScrollView, the size of three UIImageViews (there are many images, and as I scroll through them I constantly swapping out the images in the UIImageViews).
When a scroll is finished, I want to switch out the image in the (visible) middle UIImageView, three times, then back to the original view. I'm trying it thus:
- (void) doAnimation {
// get the animation frames, along with the current image
NSString *swap1 = #"first.png";
NSString *swap2 = #"second.png";
UIImage *original = currentPage.image;
UIViewAnimationOptions myOptions = UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction;
[UIView animateWithDuration:2.0 delay:2.0 options:myOptions
animations:^{ [currentPage setImage:[UIImage imageNamed:swap1]]; }
completion:^(BOOL finished) {
[UIView animateWithDuration:2.0 delay:2.0 options:myOptions
animations:^{ [currentPage setImage:[UIImage imageNamed:swap2]]; }
completion:^(BOOL finished) {
[UIView animateWithDuration:2.0 delay:2.0 options:myOptions
animations:^{ [currentPage setImage:[UIImage imageNamed:swap1]]; }
completion:^(BOOL finished) {
[currentPage setImage:original]; }]; }]; }];
}
When I run this, there is no duration, no delay, it all happens at once, almost too fast for the eye to see. Could this be because "currentPage" is a UIImageView? (Similar to this question?)
There's no delay because UIImageView.image isn't an animateable property. As such, the UIView animation machinery will have no animations to set up and will just call your completion block immediately.
What sort of animation did you expect? You can attach a CATransition object to the underlying layer to get a simple cross-fade, Just use [imageView.layer addAnimation:[CATransition animation] forKey:nil] to get the crossfade with the default values (you can customize the timing by modifying properties of the CATransition before attaching it to the layer). To achieve the subsequent animations, you can either use the delegate property of CAAnimation (CATransition's superclass) to learn when it's done and fire your second one, or you could just use -performSelector:withObject:afterDelay: to start your next animation step after a user-defined delay. The delegate method is going to be more accurate with regards to timing, but the performSelector method is a bit easier to write. Sadly, CAAnimation doesn't support a completion block.
Another approach for you to transition from one image view to another is by using the block animation function transitionFromView:toView:duration:options:completion as discussed in "Creating Animated Transitions Between Views". You would do this instead of animateWithDuration to change images.