私のアプリでは、ビューのぼやけた画像を取得するためにdrawViewHierarchyInRect:afterScreenUpdates:
を使用しています(AppleのUIImage
カテゴリ IImageEffects を使用)。
私のコードは次のようになります:
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
[self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:YES];
UIImage *im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
/* Use im */
開発中に、アプリを少し使用した後、アニメーションの多くが遅れていることに気付きました。つまり、アプリの新規起動と比較して、目立った(ただし、約1秒未満)休止後にビューがアニメーションを開始していました。
デバッグを行った後、画面の更新をYES
に設定してdrawViewHierarchyInRect:afterScreenUpdates:
を使用するだけでこの遅延が発生していることに気付きました。使用セッション中にこのメッセージが送信されなかった場合、遅延は発生しませんでした。画面の更新パラメーターにNO
を使用すると、遅延もなくなりました。
奇妙なことに、このぼかしコードは遅延アニメーションに完全に無関係(私が知る限り)です。問題のアニメーションは使用しませんdrawViewHierarchyInRect:afterScreenUpdates:
、それらはCAKeyframeAnimation
アニメーションです。このメッセージを送信するだけの動作(画面の更新をYES
に設定)は、アプリのアニメーションにグローバルな影響を与えたようです。
どうしたの?
(効果を示すビデオを作成しました: with および without アニメーションの遅延。ナビゲーションバーの「Check!」吹き出しの表示の遅延に注意してください。)
この潜在的なバグを説明するサンプルプロジェクトを作成しました。 https://github.com/timarnold/AnimationBugExample
Appleからの応答を受け取りました。これがバグであることを確認しました。回答 下記 を参照してください。
私はApple開発者サポートチケットの1つを使用して、私の問題についてApple=と尋ねました。
確認されたバグ(レーダー番号17851775)であることが判明しました。何が起こっているかの仮説は以下のとおりです。
メソッドdrawViewHierarchyInRect:afterScreenUpdates:は、可能な限りGPUで操作を実行します。この作業の多くは、おそらく別のプロセスでアプリのアドレス空間の外で行われます。 YESをafterScreenUpdates:パラメータとしてdrawViewHierarchyInRect:afterScreenUpdates:に渡すと、コアアニメーションがタスクとレンダリングタスクのすべてのバッファをフラッシュします。ご想像のとおり、これらのケースでも他にも多くの内部的なことが行われています。エンジニアリングは、あなたが見ている効果に関連するこの機構のバグである可能性が非常に高いと理論化しています。
これに対して、メソッドrenderInContext:は、アプリのアドレススペース内で操作を実行し、GPUベースのプロセスを使用して作業を実行しません。ほとんどの場合、これは別のコードパスであり、機能する場合は適切な回避策です。このルートは、GPUベースのタスクを使用しないほど効率的ではありません。また、GPUタスクによって管理されるぼかしやその他のコアアニメーション機能が除外される可能性があるため、画面キャプチャの精度は低くなります。
また、回避策も提供しました。彼らは次の代わりにそれを提案しました:
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
});
});
(サンプルアプリから)なぜこの行があるのですか?
_animation.beginTime = CACurrentMediaTime();
_
それを削除するだけで、すべてが希望どおりになります。
アニメーション時間を明示的にCACurrentMediaTime()
に設定すると、レイヤーツリーに存在する可能性のある時間変換を無視できます。まったく設定しないか(デフォルトではアニメーションがnow
で開始されます)、時間変換メソッドを使用します。
_animation.beginTime = [view.layer convertTime:CACurrentMediaTime() fromLayer:nil];
_
UIKitは、_afterScreenUpdates:YES
_を呼び出すときに時間変換をレイヤーツリーに追加して、進行中のアニメーションのジャンプを防ぎます。 (now
ではなく)特定の時間にアニメーションを開始する場合は、上記の時間変換方法を使用します。
そして、その間、_-[UIView snapshotViewAfterScreenUpdates:]
_ファミリではなく_-[UIView drawViewHierarchyInRect:]
_および友人を使用することを強くお勧めします(NO
部分にafterScreenUpdates
を指定することが推奨されます)。ほとんどの場合、永続的なイメージは必要なく、実際に必要なのはビューのスナップショットです。レンダリングされた画像の代わりにビュースナップショットを使用すると、次の利点があります。
drawViewHierarchyInRect
はそれらを黒または白にします)。afterScreenUpdates
パラメータがYESに設定されている場合、システムは、ビューをレンダリングする前に、保留中のすべての画面更新が発生するまで待機する必要があります。
アニメーションを同時に開始する場合、レンダリングとアニメーションが同時に発生しようとしているため、遅延が発生している可能性があります。
これを防ぐために、少し後でアニメーションを開始することを試してみる価値があります。明らかにそうではありませんtoo後でオブジェクトを無効にするためですが、小さなdispatch_after間隔は試す価値があります。