In my app, I use drawViewHierarchyInRect:afterScreenUpdates:
in order to obtain a blurred image of my view (using Apple’s UIImage
category UIImageEffects).
My code looks like this:
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
[self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:YES];
UIImage *im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
/* Use im */
I noticed during development that many of my animations were delayed after using my app for a bit, i.e., my views were beginning their animations after a noticeable (but less than about a second) pause compared to a fresh launch of the app.
After some debugging, I noticed that the mere act of using drawViewHierarchyInRect:afterScreenUpdates:
with screen updates set to YES
caused this delay. If this message was never sent during a session of usage, the delay never appeared. Using NO
for the screen updates parameter also made the delay disappear.
The strange thing is that this blurring code is completely unrelated (as far as I can tell) to the delayed animations. The animations in question do not use drawViewHierarchyInRect:afterScreenUpdates:
, they are CAKeyframeAnimation
animations. The mere act of sending this message (with screen updates set to YES
) seems to have globally affected animations in my app.
What’s going on?
(I have created videos illustrating the effect: with and without an animation delay. Note the delay in the appearance of the "Check!" speech bubble in the navigation bar.)
UPDATE
I have created an example project to illustrate this potential bug. https://github.com/timarnold/AnimationBugExample
UPDATE No. 2
I received a response from Apple verifying that this is a bug. See answer below.
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:
And they also provided a workaround. They suggested that instead of:
I should do this
Hopefully this is helpful for someone!
Have you tried running your code on a background thread? Heres an example using gcd:
When the
afterScreenUpdates
parameter is set to YES, the system has to wait until all pending screen updates have happened before it can render the view.If you're kicking off animations at the same time then perhaps the rendering and the animations are trying to happen together and this is causing a delay.
It may be worth experimenting with kicking off your animations slightly later to prevent this. Obviously not too much later because that would defeat the object, but a small dispatch_after interval would be worth trying.
Why do you have this line (from your sample app):
Just remove it, and everything will be as you want it to be.
By setting animation time explicitly to
CACurrentMediaTime()
you ignore possible time transformations that can be present in layer tree. Either don't set it at all (by default animations will startnow
) or use time conversion method:UIKit adds time transformations to layer tree when you call
afterScreenUpdates:YES
to prevent jumps in ongoing animation, that would be caused otherwise by intermediate CoreAnimation commits. If you want to start animation at specific time (notnow
), use time conversion method mentioned above.And while at it, strongly prefer using
-[UIView snapshotViewAfterScreenUpdates:]
and friends instead of-[UIView drawViewHierarchyInRect:]
family (preferably specifyingNO
forafterScreenUpdates
part). In most of the cases you don't really need a persistent image and view snapshot is what you actually want. Using view snapshot instead of rendered image has following benefits:drawViewHierarchyInRect
will render them black or white).