drawViewHierarchyInRect:afterScreenUpdates:延迟其他animation

在我的应用程序中,我使用drawViewHierarchyInRect:afterScreenUpdates:为了获得我的看法(使用苹果的UIImage类别UIImageEffects )的模糊图像。

我的代码如下所示:

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

我在开发过程中注意到,在使用我的应用程序一段时间后,我的许多animation被延迟了,也就是说,与刚刚启动的应用程序相比,我的视图在明显(但不到一秒)的停顿之后开始animation。

经过一些debugging,我发现仅仅使用drawViewHierarchyInRect:afterScreenUpdates:将屏幕更新设置为YES导致延迟。 如果这个消息在使用过程中从未被发送过,则延迟从未出现过。 屏幕更新参数使用NO也使延迟消失。

奇怪的是,这个模糊的代码是完全不相关的 (据我所知)的延迟animation。 有问题的animation不使用 drawViewHierarchyInRect:afterScreenUpdates:它们是CAKeyframeAnimationanimation。 发送此消息的单纯行为(将屏幕更新设置为YES )似乎已在全球范围内影响我的应用中的animation。

这是怎么回事?

(我已经创build了video来说明效果: 有和没有animation延迟,请注意导航栏中“检查!”对话框出现的延迟。)

UPDATE

我已经创build了一个示例项目来说明这个潜在的错误。 https://github.com/timarnold/AnimationBugExample

更新第2号

我收到苹果公司的回复,validation这是一个错误。 见下面的答案。

我用我的一个苹果开发者支持票据问苹果关于我的问题。

事实certificate,这是一个确认的错误(雷达编号17851775)。 他们对正在发生的事情的假设如下:

方法drawViewHierarchyInRect:afterScreenUpdates:尽可能在GPU上执行它的操作,而这些工作的大部分可能会发生在另一个进程的应用程序地址空间之外。 传递YES作为afterScreenUpdates:参数来绘制ViewHierarchyInRect:afterScreenUpdates:将导致核心animation刷新您的任务和渲染任务中的所有缓冲区。 正如你可以想象的那样,在这些情况下还有很多其他内部的东西。 工程学理论认为,这很可能是与你所看到的效果有关的机器中的一个错误。

相比之下,renderInContext方法:在您的应用程序的地址空间内执行其操作,并不使用基于GPU的进程来执行工作。 在大多数情况下,这是一个不同的代码path,如果它为你工作,那么这是一个合适的解决方法。 这条路线不像使用基于GPU的任务那样高效。 此外,屏幕截图的准确性也不尽如人意,因为它可能会排除由GPU任务pipe理的模糊和其他Core Animationfunction。

他们还提供了一个解决方法。 他们build议,而不是:

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

我应该这样做

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

希望这对某人有帮助!

你有没有尝试在后台线程上运行你的代码? 下面是一个使用gcd的例子:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ //background thread UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0); [self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:YES]; UIImage *im = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); dispatch_sync(dispatch_get_main_queue(), ^(void) { //update ui on main thread }); }); 

afterScreenUpdates参数设置为YES时,系统必须等到所有待处理屏幕更新发生之后才能呈现视图。

如果你在同一时间踢开animation,那么渲染和animation可能会一起发生,这会造成延迟。

为了防止这种情况发生,可能需要稍后尝试开始animation。 显然不会太晚 ,因为这会击败对象,但一个小dispatch_after间隔将是值得尝试的。

为什么你有这样的线(从您的示例应用程序):

 animation.beginTime = CACurrentMediaTime(); 

只要删除它,一切都将如你所愿。

通过将animation时间显式设置为CACurrentMediaTime()可以忽略可能存在于图层树中的时间转换。 要么根本不设置(默认情况下animation将立即开始)或使用时间转换方法:

 animation.beginTime = [view.layer convert​Time:CACurrentMediaTime() from​Layer:nil]; 

当您调用afterScreenUpdates:YES来防止正在进行的animation跳转时,UIKit会将时间转换添加到图层树,否则会导致中间CoreAnimation落实。 如果你想在特定的时间(不是now )开始animation,使用上面提到的时间转换方法。