Drawviewhierarchyinrect:Afterscreenupdates: Delays Other Animations

drawViewHierarchyInRect:afterScreenUpdates: delays other animations

I used one of my Apple developer support tickets to ask Apple about my issue.

It turns out it is a confirmed bug (radar number 17851775). Their hypothesis for what is happening is below:

The method drawViewHierarchyInRect:afterScreenUpdates: performs its operations on the GPU as much as possible, and much of this work will probably happen outside of your app’s address space in another process. Passing YES as the afterScreenUpdates: parameter to drawViewHierarchyInRect:afterScreenUpdates: will cause a Core Animation to flush all of its buffers in your task and in the rendering task. As you may imagine, there’s a lot of other internal stuff that goes on in these cases too. Engineering theorizes that it may very well be a bug in this machinery related to the effect you are seeing.

In comparison, the method renderInContext: performs its operations inside of your app’s address space and does not use the GPU based process for performing the work. For the most part, this is a different code path and if it is working for you, then that is a suitable workaround. This route is not as efficient as it does not use the GPU based task. Also, it is not as accurate for screen captures as it may exclude blurs and other Core Animation features that are managed by the GPU task.

And they also provided a workaround. They suggested that instead of:

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
[self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:YES];
UIImage *im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
/* Use im */

I should do this

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
/* Use im */

Hopefully this is helpful for someone!

snapshotView of UIView render/draw in context with nothing

If you need a snapshot UIImage in the first place just use the method in your second code block. Create a UIView's category like

@implementation UIView (takeSnapshot)

- (UIImage *)takeASnapshot {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);

[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}

@end

That's enough. You don't need snapshot a view and convert it to a image. In your case this process might cause the problem

Sharing an Image between two viewControllers during a transition animation

It's probably two different views and an animated snapshot view. In fact, this is exactly why snapshot views were invented.

That's how I do it in my app. Watch the movement of the red rectangle as the presented view slides up and down:

Sample Image

It looks like the red view is leaving the first view controller and entering the second view controller, but it's just an illusion. If you have a custom transition animation, you can add extra views during the transition. So I create a snapshot view that looks just like the first view, hide the real first view, and move the snapshot view — and then remove the snapshot view and show the real second view.

Same thing here (such a good trick that I use it in a lot of apps): it looks like the title has come loose from the first view controller table view cell and slid up to into the second view controller, but it's just a snapshot view:

Sample Image

Simultaneous animations block each other

Here's some useful code:

// We schedule a timer for a desired 30fps animation rate.
// In performAnimation: we determine exactly
// how much time has elapsed and animate accordingly.
timer = [[NSTimer scheduledTimerWithTimeInterval:(1.0/30.0) target:self selector:@selector(performAnimation:) userInfo:nil repeats:YES] retain];

// The next two lines make sure that animation will continue to occur
// while modal panels are displayed and while event tracking is taking
// place (for example, while a slider is being dragged).
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSModalPanelRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode];

NSTimer blocks other animations

This is a common symptom of having auto layout turned on but are probably trying to slide it across the screen by adjusting the frame or center. Auto layout is a iOS 6+ feature that controls the location and size of various UIView elements. Unfortunately, when you have auto layout on, every time you change a label's value, it will reapply the constraints that dictate where the label should be positioned, defeating your attempts to animate it.

Two solutions:

  1. Turn off auto layout by opening up your storyboard or NIB, click on the first "document inspector" tab on the rightmost panel, and then uncheck "Use Autolayout".

    show autolayout

  2. If you want to use autolayout, animate the moving of the control by changing constraints rather than changing frame or center. See here for an example of how you can create an IBOutlet for a constraint and then change that constraint programmatically in an animateWithDuration block.

References:

  • Cocoa Auto Layout Guide

  • Introduction to Auto Layout for iOS and OS X

  • Best Practices for Mastering Auto Layout

  • Auto Layout by Example

Layer animations removed when switching apps

When switching apps or view controllers, the system will let the animations on the layer stop (complete), so you can set removedOnCompletion to NO (the default is YES), and it will work.

How to use performSelector: afterDelay: after doing animations?

From what I can gather, you're trying to perform a series of animations, each with a delay before they begin. So, instead of using performSelector:afterDelay: to call the animations, you can nest calls to animateWithDuration:delay:options:animations:completion: like this:

[UIView animateWithDuration:1.0f delay:2.0f options:UIViewAnimationOptionCurveEaseIn animations:^{

//your first animations

} completion:^(BOOL finished) {

[UIView animateWithDuration:1.0f delay:2.0f options:UIViewAnimationOptionCurveEaseIn animations:^{

//your second animations

} completion:^(BOOL finished) {

}];
}];

This method accepts an extra delay parameter which sounds like what you're wanting. And since you're second animation is in the first's completion block, the animations will happen in sequence.



Related Topics



Leave a reply



Submit