CATransaction Completionblock triggers immediately [duplicate] - ios

This question already has answers here:
CATransaction completion being called immediately
(4 answers)
Closed 8 years ago.
I'm trying to wait for an animation to finish before starting another task. I looked at different methods but using CATransactions seems to be the most used method to do this.
Somehow, my CATransaction Completionblock triggers immediately after the animation starts, not after it finishes.
Here's my code:
[CATransaction begin];
[CATransaction setCompletionBlock: ^{
NSLog(#"Animation ends");
}];
NSLog(#"Animation begins");
[tableView setEditing:NO animated:YES];
[CATransaction commit];
When looking at the console I get this:
2014-03-17 15:44:12.995 BarTap[89934:70b] Animation begins
2014-03-17 15:44:12.997 BarTap[89934:70b] Animation ends
So appearently the Completionblock starts 0.002 seconds after the animation begins, but the animation definitely takes longer than that.
Could anyone help me? Thanks!

Animation completion triggers immediately because there are no tasks for animation in your code.
CAAnimation effects to CALayer properties and it finishes immediately if there no changes in animatable properties.
Try this:
[UIView animateWithDuration:timeInterval animations:^{
[tableView setEditing:NO animated:NO];
} completion:^(BOOL finished) {
// Perform tasks after animation completion here
}];

Related

UIView AnimateWithDuration Never Reaches Completion Block

I have this bit of code that I use to switch the root view controller with a nice little animation, it had been working for months... but then randomly stopped working.
UIView snapshot = [self.window snapshotViewAfterScreenUpdates:YES];
[viewController.view addSubview:snapshot];
self.window.rootViewController = viewController;
NSLog(#"check point 1");
[UIView animateWithDuration:0.3 animations:^{
NSLog(#"check point 2");
snapshot.layer.opacity = 0;
NSLog(#"check point 3");
snapshot.layer.transform = CATransform3DMakeScale(1.5, 1.5, 1.5);
NSLog(#"check point 4");
} completion:^(BOOL finished) {
NSLog(#"check point 5");
[snapshot removeFromSuperview];
NSLog(#"check point 6");
}];
I put in these checkpoints, everything through checkpoint 4 triggers.. but 5 and 6 never trigger. So weird to me because even if it failed, the completion block should still trigger.
On the new root view controller that loads, the user's permission to gather his or her location is requested. So maybe when that pop up shows up, it wrecks this transition? It didn't used to though.
The completion block will not call if there is nothing to animate or the transition part already done by some another part of your source code and the code snippet runs on different thread(besides UI thread). Therefore just to illustrate, completion block of below code will never trigger,
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"check point 1");
[UIView animateWithDuration:10.3 animations:^{
NSLog(#"check point 4");
} completion:^(BOOL finished) {
NSLog(#"check point 5");
[snapshot removeFromSuperview];
NSLog(#"check point 6");
}];
});
To solve this, Enclose your code within this code,
dispatch_async(dispatch_get_main_queue(), ^{
//Your code, This runs on main thread
});

iOS: Animation Not working in observeValueForKeyPath

I used KVO to observe changes in a frame and set another frame accordingly
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
if ([keyPath isEqualToString:KeyPathForFrameBeingObserved]) {
[UIView animateWithDuration:1 animations:^{
CGRect frame = [self playWithFrame:viewBeingMonitored.frame];
self.viewToAnimate.frame = frame;
}];
}
}
Code in the animation block is executed but animation is not working, I suspected that repeated calls to animation method could cause this but using log messages I found that animation is not working even for a single call, can anyone explain that ?
iPad 2/(iOS8.4)
I have tried including the animation method call in dispatch_async(dispatch_get_main_queue(), ^{ ... }); but animation still not working.
I have tried pushing the observed changes in queue and delaying the animation method for a second using dispatch_after to run it only using the last element in the queue to avoid repeated calls but didn't work.
KVO is working perfectly (adding the observer is already handled), but the code in the animation block is executed without animation.
It seems like you're trying to observe the property of an UIKit object. Keep in mind that:
... the classes of the UIKit framework generally do not support KVO ...
Source: https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/KVO.html
So, you have to check if your observeValueForKeyPath:ofObject:change:context: is calling in fact.
This is an older question but I thought I would answer in case someone else is reading this. You will need to call setFrame to set the frame of the animation instead of the simple assignment.
CGRect frame = [self playWithFrame:viewBeingMonitored.frame];
[self.viewToAnimate setFrame:frame];

Stopping an UIView animation that was started but being delayed [duplicate]

This question already has answers here:
How to cancel UIView block-based animation?
(4 answers)
Closed 8 years ago.
[UIView animateWithDuration:1 delay:4 options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveEaseIn)
animations:^
{
// do something here (irrelevant)
}
completion:nil
];
How do I stop the animation within the time of the delay? For example, I want to stop this animation after I execute it but before the delay timer ends. I've tried [self.view.layer removeAllAnimations]; but to no avail.
EDIT: As I've stated. I've tried [self.view.layer removeAllAnimations]; but to no avail.
I didn't try it but you can stop all animations with [view.layer removeAllAnimations]; it should work.
You could use NSTimer and then just call [timer invalidate] if you no longer want the animation to run.
E.g.:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:4 target:self selector:#selector(animateView:) userInfo:nil repeats:NO];
// Then stop...
[timer invalidate];

UIScroll animation is interrupted by NSFetchedResultsControllerDelegate processing

When the keyboardWillHide, I scroll the tableView to a specified point. The code is below. This works well.
Now, I implement NSFetchedResultsControllerDelegate. I set it "on" by setting fetchedResultsController.delegate = self; The scroll animation is interrupted. The NSFetchedResultsControllerDelegate is calling [tableView beginUpdates], which I think is causing the interruption on the tableView animation.
How can I prevent the scroll animation from being interrupted and still implement NSFetchedResultsControllerDelegate?
- (void)keyboardWillHide:(NSNotification *)notification {
[screen setHidden:YES];
[suggestView setHidden:YES];
[_tableView setContentOffset:origin animated:YES];
}
I found out that you can't do animations in keyboardWillHide. You shouldn't do any animation in any "WILL" event.

NSTimer Thrashing - Do I Need to Handle It?

It's possible this question is already out there, but I couldn't find it. My question is essentially this. If I have a repeating NSTimer that executes something that takes longer than the timer interval, will there be some thrashing that will crash the app? Alternatively, does the new time event not start until the task being executed completes?
Since the NSTimer runs on the run loop it was created in, I think it can't ever re-enter the method it calls. This document on the run loops confirms this (see the "Timer Sources" section:
"Similarly, if a timer fires when the run loop is in the middle of
executing a handler routine, the timer waits until the next time
through the run loop to invoke its handler routine"
You can always just schedule an nstimer that only occurs once and then reschedule it when the function completes.
- (void)myFunction {
......stuff that your method does
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(myFunction) userInfo:nil repeats:NO];
}
A repeating timer always schedules itself based on the scheduled firing time, as opposed to the actual firing time. For example, if a timer is scheduled to fire at a particular time and every 5 seconds after that, the scheduled firing time will always fall on the original 5 second time intervals, even if the actual firing time gets delayed. If the firing time is delayed so far that it passes one or more of the scheduled firing times, the timer is fired only once for that time period; the timer is then rescheduled, after firing, for the next scheduled firing time in the future.
https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/Reference/NSTimer.html
As long as you avoid kicking off some asynchronous jobs, you'll be fine. If asynchronously dispatching tasks that routinely take longer than the interval between invocations of the timer, then that queue can get backed up. If doing animations, the timer will fire even though the animation may not be done.
Let me provide two examples. For both examples, let's imagine that we create a timer that fires once per second:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(handleTimer:)
userInfo:#"tick"
repeats:YES];
First example: Let's assume we have some serial queue:
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;
Furthermore, let's assume we have a NSTimer handler that does something like:
- (void)handleTimer:(NSTimer *)timer
{
NSLog(#"%s %#", __FUNCTION__, timer.userInfo);
[self.queue addOperationWithBlock:^{
NSLog(#"%s starting some slow process; has %d operations queued", __FUNCTION__, self.queue.operationCount);
// to simulate a slow process, let's just sleep for 10 seconds
sleep(10);
NSLog(#"%s done", __FUNCTION__);
}];
}
Because the timer is firing every second, and because the timer handler returns almost immediately (because all it's doing is queueing up background operations), by the time the first queued operation (which takes 10 seconds) finishes and the second one starts, there are already 10 operations sitting on that background queue. And by the time the second background operation finishes, when the third operation kicks off, there are 19 operations queued up. It only gets worse because the NSTimer handler will simply keep getting called, firing more quickly than the slower background operations are getting cleared out of their queue. Obviously, if the handler did everything synchronously in the current queue, though, everything is fine, and there's no backlogging, no "thrashing" by the NSTimer.
Second example: Another example of this problem is animation. Let's assume that the timer handler method is doing something like the following, that starts a 10 second animation that moves a UIImageView:
- (void)handleTimer:(NSTimer *)timer
{
NSLog(#"%s %#", __FUNCTION__, timer.userInfo);
[UIView animateWithDuration:10.0
animations:^{
self.imageView.frame = [self determineNewFrame];
}
completion:nil];
}
This won't work (or more accurately, you'll see the subsequent invocations of the timer call handleTimer even though the previous animation is not done). If you're going to do this, you have to keep track of whether the animation is done. You have to do something like:
- (void)handleTimer:(NSTimer *)timer
{
NSLog(#"%s %#", __FUNCTION__, timer.userInfo);
if (!self.animating)
{
NSLog(#"%s initiating another animation", __FUNCTION__);
[UIView animateWithDuration:10.0
animations:^{
self.animating = YES;
self.imageView.frame = [self determineNewFrame];
}
completion:^(BOOL finished){
self.animating = NO;
}];
}
}
You either have to do some state flag (like my boolean animation flag) to prevent additional animations before the first one is done, or just not use recurring timers and simply kick off another timer in the completion block of the UIView animation class method.

Resources