CADisplayLink と CAAnimation を組み合わせて、一部のレイヤーを CAAnimation でアニメーション化し、アニメーション レイヤーの変化するプレゼンテーション レイヤーに基づいてリアルタイムで他のレイヤーを調整しようとしています。CADisplayLink コールバックがプロセス内にある間に、CAAnimations がプロセス外で駆動されていることを認識しているため、同期が完全にタイトになることはありません。ただし、私が見ているものよりも優れていることを望んでいました (おそらく最大 4 フレームのラグが見られます)。
簡単なサンプル コードに要約すると、多数のレイヤーのペアが作成されます。半分はアウトラインで、残りの半分は塗りつぶされた正方形です。すべてのアイテムは同じサイズです。目標は、これらの四角形のペアをアニメートして、それらの輪郭と四角形が常に一緒 (つまり、同じフレーム) になるようにすることです。
サイクルごとに、ソリッド レイヤーの新しい位置を設定し、CADisplayLink コールバックで、補完するソリッド レイヤーのプレゼンテーション レイヤーの位置に基づいてアウトライン レイヤーを設定します。シミュレーターでは、これはほとんどの場合、少なくとも少数のレイヤーでは機能するようです。最大 24 ペアのレイヤーを取得すると、一部のアウトラインが遅れ始めます。デバイス (iPad Air と iPhone 6) では、わずか 1 つの正方形でもアウトラインが遅れます。すべてのテストを iOS 8 で行っています。
以下は私のViewControllerの実装です。
@implementation ViewController
{
CADisplayLink *_displayLink;
NSUInteger _startingLayerIndex;
}
#define NUMLAYERS 1
- (void)viewDidLoad {
[super viewDidLoad];
_startingLayerIndex = self.view.layer.sublayers.count;
for (int i=0; i<NUMLAYERS; ++i) {
CALayer *outlineLayer = [[CALayer alloc] init];
outlineLayer.borderColor = [[UIColor blackColor] CGColor];
outlineLayer.borderWidth = 1.0;
CALayer *fillLayer = [[CALayer alloc] init];
fillLayer.backgroundColor = [[UIColor redColor] CGColor];
{
CGRect r = CGRectMake(self.view.center.x, self.view.center.y, 0, 0);
r = CGRectInset(r, -22, -22);
outlineLayer.frame = r;
fillLayer.frame = r;
}
[self.view.layer addSublayer:fillLayer];
[self.view.layer addSublayer:outlineLayer];
}
UITapGestureRecognizer *gr = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
[self.view addGestureRecognizer:gr];
// WARNING - will create an ARC cycle. I don't care though, throwaway code
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(refreshDisplay:)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
_displayLink.paused = true;
}
-(void) refreshDisplay:(CADisplayLink *) displayLink {
[CATransaction begin];
[CATransaction setDisableActions:true];
bool keepGoing = false;
for (int i=0; i<NUMLAYERS; ++i) {
CALayer *animatedLayer = [self.view.layer.sublayers objectAtIndex:(2*i)+_startingLayerIndex];
if (animatedLayer.animationKeys.count) {
keepGoing = true;
}
CALayer *trackingLayer = [self.view.layer.sublayers objectAtIndex:(2*i)+_startingLayerIndex+1];
trackingLayer.position = [animatedLayer.presentationLayer position];
}
_displayLink.paused = !keepGoing;
[CATransaction commit];
}
CGFloat randInRange(CGFloat r) {
return (rand()%((int)r*2))/2.0;
}
-(void) onTap:(UITapGestureRecognizer *) gr {
_displayLink.paused = false;
[CATransaction begin];
[CATransaction setAnimationDuration:2.0];
//[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
for (int i=0; i<NUMLAYERS; ++i) {
CGSize sz = self.view.frame.size;
CGPoint p = CGPointMake(randInRange(sz.width), randInRange(sz.height));
[[self.view.layer.sublayers objectAtIndex:(2*i)+_startingLayerIndex] setPosition:p];
}
[CATransaction commit];
}
@end
3 つの異なるタイミング機能を試しましたが、同じ結果が得られました。また、インストルメントを使用してフレームレートを確認したところ、58 ~ 60 fps であり、まだ目に見える分離が見られます。
誰かがこれを行うためのより良い方法を知っているかどうか疑問に思っています。すべてのアニメーションを CAAnimation または CADisplayLink によって駆動されるように変換できることは承知しています。しかし、私の場合、どちらも魅力的ではありません。画面には何百ものレイヤーがあり、それらはすべて相互接続されています (常に応答性が高く、完全にアニメーション化されたビューを構成しています) - 時には階層的に。Core Animation は生活をより管理しやすくします (そして、ラグの根本的な原因はその「効率」かもしれませんが、より効率的だと思います)。すべてのアニメーションをパラメータ化する必要があることに興奮していません。多数の他のレイヤーのフレームに依存する多数のレイヤーがあります。作業の大部分でアニメーションを実行できるようになり、「手動で」いくつかのコーナーケースを実行するだけです
また、UIKit Dynamics は、アニメーション化されたレスポンシブ ビューの優れた基盤を提供するように構築されていることも認識しています。ただし、レイヤーのサイズを調整していて、画面上のレイヤーの位置には制約とアタッチメントが必要です。特にCPUがすでに制約されている場合は、それを数百、さらには数千のレイヤーにスケーリングすることは問題があるようです.