How to wait for an animator to finish? - objective-c

This is probably a simple question but I can't seem to figure out how to do it. Basically all I want to do is fade a window before closing it:
[[window animator] setAlphaValue:0.0];
[window close];
This works fine without the [window close], but when that is included the window seems to close it before the animation finishes (which is obviously not what I want); the same seems to happen for orderOut:, performClose:, etc. Is there any way to avoid this?

[[window animator] setAlphaValue:0.0];
[window performSelector:#selector(performClose:) withObject:self afterDelay:[[NSAnimationContext currentContext] duration]];

This is an old (but still popular) question with obsolete answer.
The right way to wait for animator finished is using special NSAnimationContext's class method with completionHanler like this:
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
// Start some animations here.
[[window animator] setAlphaValue:0.0];
} completionHandler:^{
// This block will be invoked when all of the animations started above have completed or been cancelled.
NSLog(#"All done!");
}];

Implicit animations triggered through the animator proxy run on wall time. Get the duration from the current NSAnimationContext and perform delay your cleanup/post-animation operations using that interval.

Related

layoutIfNeeded happens after a noticeable delay

After changing the constraints on some of my view components, I'm calling layoutIfNeeded to perform the changes. The problem is that sometimes there's a very evident delay between the method call and the actual visual change. What am I doing wrong?
[self.view layoutIfNeeded];
I just realised you're supposed to call layoutIfNeeded while on the main queue:
dispatch_async(dispatch_get_main_queue(), ^{
self.loginFormConstraintX.constant = -1024;
[self.view layoutIfNeeded];
});

no animation when using modal segue

I have a view controller and have a few objects that I have some simply animations hooked up to.
[UIView animateWithDuration:0.25
delay: 10.1
options: UIViewAnimationOptionCurveEaseIn
animations:^{
addNameViewConstraint.constant = 10;
addNameView.alpha = 1.0;
[self.view layoutIfNeeded]; // move
}
completion:nil];
[UIView animateWithDuration:0.25
delay: 0.15
options: UIViewAnimationOptionCurveEaseIn
animations:^{
addEmailViewConstraint.constant = 10;
addEmailView.alpha = 1.0;
[self.view layoutIfNeeded]; // move
}
completion:nil];
When I was using the push segue the animation works fine. But when I switched to using a modal segue the animation stopped working. I increased the delay in the first one to 10.2 seconds thinking that maybe it was animating before I got to see it. I am calling this animation in the viewWillAppear method. Again works if I'm doing a push segue.. but not for modal. Any ideas?
I am calling this animation in the viewWillAppear method
This is a common mistake. viewWillAppear: is too soon to start the animation. The view has not yet appeared so there is nothing to animate. Move the animateWithDuration:... code to viewDidAppear: and all will be well.
This is, however, as you say in your comment, insufficiently satisfying. What you are after is that the modal transition should be happening and your extra animations within the new view should already be happening as the modal view is in the process of appearing. Instead, with viewDidAppear:, the modal view finishes appearing, it settles into place, and then your other animations start, which is not as cool.
One solution might be to move the animations again, this time to viewWillLayoutSubviews. This is trickier because this method gets called a lot. You will need to use a BOOL instance variable as a flag to ensure that your animations run only once. Thus, this should work (I tried it and it seemed fine):
-(void)viewWillLayoutSubviews {
if (!self->didLayout) {
self->didLayout = YES;
[UIView animateWithDuration:0.25 animations:^{
// mess with constraint constants here
[self.subv layoutIfNeeded];
}];
}
}

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

Why is [[NSWindow animator] setFrame...] very laggy sometimes?

So, I have the following code to show my NSWindow:
[_window makeKeyAndOrderFront:self];
[NSAnimationContext beginGrouping];
[[_window animator] setAlphaValue:1.0];
[[_window animator] setFrame:NSMakeRect([[NSApp currentEvent] window].frame.origin.x - 102, [[NSApp currentEvent] window].frame.origin.y - 238, _window.frame.size.width, _window.frame.size.height) display:YES];
[NSAnimationContext endGrouping];
This code is called right after the user has clicked on the app's status bar icon, that's why I use the [[NSApp currentEvent] window].frame.origin.y/x to get the location of the status bar icon.
This code runs perfectly but, sometimes, it's very laggy and "jumpy" and I don't know why.
Any ideas about this and how to fix it?
The NSWindow animator uses NSAnimation, which means that it rapidly fires a timer to animate the frame of the window. At each frame of the animation, every view inside the window is redrawn. If you have large views with somewhat complex view hierarchies, the performance is quite bad and there's no real way to work around it.
I would recommend JNWAnimatableWindow as a substitute for the default NSWindow animator, as it uses a Core Animation CALayer to perform animations on and therefore is much smoother.

Checking When animation stopped

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];