web-dev-qa-db-ja.com

drawViewHierarchyInRect:afterScreenUpdates:他のアニメーションを遅らせます

私のアプリでは、ビューのぼやけた画像を取得するために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

アップデートNo. 2

Appleからの応答を受け取りました。これがバグであることを確認しました。回答 下記 を参照してください。

28
Tim Camber

私は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 */

うまくいけば、これは誰かのために役立ちます!

68
Tim Camber

バックグラウンドスレッドでコードを実行してみましたか? 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
    });
});
1
Sam

(サンプルアプリから)なぜこの行があるのですか?

_animation.beginTime = CACurrentMediaTime();
_

それを削除するだけで、すべてが希望どおりになります。

アニメーション時間を明示的にCACurrentMediaTime()に設定すると、レイヤーツリーに存在する可能性のある時間変換を無視できます。まったく設定しないか(デフォルトではアニメーションがnowで開始されます)、時間変換メソッドを使用します。

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

UIKitは、_afterScreenUpdates:YES_を呼び出すときに時間変換をレイヤーツリーに追加して、進行中のアニメーションのジャンプを防ぎます。 (nowではなく)特定の時間にアニメーションを開始する場合は、上記の時間変換方法を使用します。

そして、その間、_-[UIView snapshotViewAfterScreenUpdates:]_ファミリではなく_-[UIView drawViewHierarchyInRect:]_および友人を使用することを強くお勧めします(NO部分にafterScreenUpdatesを指定することが推奨されます)。ほとんどの場合、永続的なイメージは必要なく、実際に必要なのはビューのスナップショットです。レンダリングされた画像の代わりにビュースナップショットを使用すると、次の利点があります。

  • 2倍から10倍高速
  • 2x-3x少ないメモリを使用します
  • 常に正しい色空間とバッファー形式を使用します(ワイドスクリーンのデバイスなど)
  • 正しい縮尺と向きを使用するため、見栄えを良くするために画像の配置方法を考える必要はありません。
  • アクセシビリティ機能との連携性が高い(例:スマート反転色)
  • ビューのスナップショットは、アウトプロセスビューとセキュアビューも正しくキャプチャします(drawViewHierarchyInRectはそれらを黒または白にします)。
1
wonder.mice

afterScreenUpdatesパラメータがYESに設定されている場合、システムは、ビューをレンダリングする前に、保留中のすべての画面更新が発生するまで待機する必要があります。

アニメーションを同時に開始する場合、レンダリングとアニメーションが同時に発生しようとしているため、遅延が発生している可能性があります。

これを防ぐために、少し後でアニメーションを開始することを試してみる価値があります。明らかにそうではありませんtoo後でオブジェクトを無効にするためですが、小さなdispatch_after間隔は試す価値があります。

1
jrturton