-[CALayer setNeedsDisplayInRect:]で暗黙的なアニメーションを無効にする


137

-drawInContext:メソッドにいくつかの複雑な描画コードを含むレイヤーがあります。必要な描画の量を最小限に抑えようとしているので、変更された部分だけを更新するために-setNeedsDisplayInRect:を使用しています。これは見事に機能しています。ただし、グラフィックスシステムがレイヤーを更新すると、クロスフェードを使用して古い画像から新しい画像に移行します。瞬時に切り替えたいのですが。

CATransactionを使用してアクションをオフにし、期間をゼロに設定してみましたが、どちらも機能しません。これが私が使っているコードです:

[CATransaction begin];
[CATransaction setDisableActions: YES];
[self setNeedsDisplayInRect: rect];
[CATransaction commit];

代わりに使用する必要があるCATransactionに別のメソッドがありますか(kCATransactionDisableActionsで-setValue:forKey:も試しましたが、同じ結果です)。


次回の実行ループでそれを行うことができます: dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });
ハシェムAboonajmi

1
以下の答えがたくさんありました。また Appleのレイヤーのデフォルト動作変更に関するドキュメントも役立ちます。このドキュメントでは、暗黙のアクション決定プロセスについて詳しく説明しています。
ɲeuroburɳ

これはこの質問と重複する質問です。stackoverflow.com
Ryan Francesconi

回答:


172

これを行うには、レイヤーのアクションディクショナリを[NSNull null]適切なキーのアニメーションとして返すように設定します。たとえば、私は

NSDictionary *newActions = @{
    @"onOrderIn": [NSNull null],
    @"onOrderOut": [NSNull null],
    @"sublayers": [NSNull null],
    @"contents": [NSNull null],
    @"bounds": [NSNull null]
};

layer.actions = newActions;

レイヤーの1つにサブレイヤーを挿入または変更したときのフェードイン/アウトアニメーション、およびレイヤーのサイズとコンテンツの変更を無効にします。contents更新された図面でクロスフェードを防ぐには、キーがあなたが探しているものだと思います。


Swiftバージョン:

let newActions = [
        "onOrderIn": NSNull(),
        "onOrderOut": NSNull(),
        "sublayers": NSNull(),
        "contents": NSNull(),
        "bounds": NSNull(),
    ]

24
フレームを変更するときに動きを防ぐには、@"position"キーを使用します。
mxcl 2011年

11
また@"hidden"、レイヤーの可視性をそのように切り替えて、不透明度のアニメーションを無効にする場合は、アクションディクショナリにもプロパティを追加してください。
Andrew

1
私は(私はオーバーライド苦労いくつかの後に思い付いた同じ考えです@BradLarson actionForKey:発見、代わりに)fontSizecontentsonLayoutboundssetValue:forKey:メソッドで使用できる任意のキーを指定でき、実際にはのような複雑なキーパスを指定できるようbounds.sizeです。
pqnet 2012

11
:ここではよく文書プロパティを表すないこれらの「特別な」文字列(@「onOrderOut」用などkCAOnOrderOut)の定数、実際にありますdeveloper.apple.com/library/mac/#documentation/Cocoa/Conceptual/...
パトリックPijnappel

1
@Benjohn対応するプロパティを持たないキーのみに定数が定義されています。ところで、リンクが死んでいるようですが、ここで新しいURLです:developer.apple.com/library/mac/documentation/Cocoa/Conceptual/...
パトリックPijnappel

89

また:

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

//foo

[CATransaction commit];

3
に置き換え//foo[self setNeedsDisplayInRect: rect]; [self displayIfNeeded];元の質問に答えることができます。
Karoy Lorentey

1
ありがとう!これにより、カスタムビューにもアニメーションフラグを設定できます。テーブルビューセル内で使用すると便利です(セルを再利用すると、スクロール中に奇妙なアニメーションが発生する可能性があります)。
Joe D'Andrea

3
私にとってパフォーマンスの問題に
つながり

26
省略形:[CATransaction setDisableActions:YES]
チタンデコイ

7
@titaniumdecoyコメントにコメントを追加すると、(私のような)誰かが混乱した場合に備えて[CATransaction setDisableActions:YES][CATransaction setValue:forKey:]行の省略形になります。あなたはまだbeginand commit行が必要です。
Hlung、2014年

31

レイヤーのプロパティを変更すると、CAは通常、暗黙的なトランザクションオブジェクトを作成して、変更をアニメーション化します。変更をアニメーション化しない場合は、明示的なトランザクションを作成し、そのkCATransactionDisableActionsプロパティを次のように設定することで、暗黙的なアニメーションを無効にできます trueにます

Objective-C

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
// change properties here without animation
[CATransaction commit];

迅速

CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
// change properties here without animation
CATransaction.commit()

6
setDisableActions:同じことをします。
ベンシンクレア

3
これは私がSwiftで作業する最も簡単な方法でした!
ジャンバマン、2015

@Andyのコメントは、これを行うための最善かつ最も簡単な方法です。
Aᴄʜᴇʀᴏɴғᴀɪʟ

23

Brad Larsonの回答に加えて:(自分で作成した)カスタムレイヤーの場合は、レイヤーのactions辞書を変更する代わりに委任を使用できます。このアプローチはより動的であり、パフォーマンスが向上する可能性があります。また、アニメート可能なキーをすべてリストしなくても、すべての暗黙的なアニメーションを無効にすることができます。

残念ながら、UIViewsをカスタムレイヤーデリゲートとして使用することは不可能です。それぞれUIViewが既に独自のレイヤーのデリゲートになっているためです。ただし、次のような単純なヘルパークラスを使用できます。

@interface MyLayerDelegate : NSObject
    @property (nonatomic, assign) BOOL disableImplicitAnimations;
@end

@implementation MyLayerDelegate

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
    if (self.disableImplicitAnimations)
         return (id)[NSNull null]; // disable all implicit animations
    else return nil; // allow implicit animations

    // you can also test specific key names; for example, to disable bounds animation:
    // if ([event isEqualToString:@"bounds"]) return (id)[NSNull null];
}

@end

使用法(ビュー内):

MyLayerDelegate *delegate = [[MyLayerDelegate alloc] init];

// assign to a strong property, because CALayer's "delegate" property is weak
self.myLayerDelegate = delegate;

self.myLayer = [CALayer layer];
self.myLayer.delegate = delegate;

// ...

self.myLayerDelegate.disableImplicitAnimations = YES;
self.myLayer.position = (CGPoint){.x = 10, .y = 42}; // will not animate

// ...

self.myLayerDelegate.disableImplicitAnimations = NO;
self.myLayer.position = (CGPoint){.x = 0, .y = 0}; // will animate

ビューのコントローラーをビューのカスタムサブレイヤーのデリゲートとして持つと便利な場合があります。この場合、ヘルパークラスは必要ありません。実装できます。actionForLayer:forKey:。コントローラ内でメソッド。

重要な注意:のデリゲートを変更しようとしないでください UIViewの基になるレイヤーの(たとえば、暗黙的なアニメーションを有効にするなど)—悪いことが起こります:)

注:レイヤーの再描画をアニメーション化する(アニメーションを無効にしない)場合、実際の再描画が後で発生する可能性があるため(おそらくそうなる可能性があるため)、[CALayer setNeedsDisplayInRect:]呼び出しを内に配置しても意味がCATransactionありません。この回答で説明さているよう、カスタムプロパティを使用することをお勧めします。


これではうまくいきません。こちらをご覧ください。
aleclarson 2014

うーん。このアプローチで問題が発生したことはありません。リンクされた質問のコードは問題ないように見え、おそらく問題は他のいくつかのコードが原因です。
スコジン2014年

ああ、あなたはすでにあなたがすでに機能することCALayerを妨げたのが間違っていたことを整理したようnoImplicitAnimationsです。たぶん、あなたは自分の答えを正しいものとしてマークし、そのレイヤーの何が問題であったかを説明する必要がありますか?
スコジン2014年

私は単に間違ったCALayerインスタンスでテストしていました(一度に2つ持っていました)。
aleclarson 2014年

1
素晴らしい解決策...しかしNSNullCAActionプロトコルを実装していません。これは、オプションのメソッドのみを持つプロトコルではありません。このコードもクラッシュし、迅速に変換することもできません。より良い解決策:オブジェクトをCAActionプロトコルに準拠させ(runActionForKey:object:arguments:何もしない空のメソッドを使用)、selfではなくを返します[NSNull null]。同じ効果ですが安全です(確実にクラッシュしません)。また、Swiftでも機能します。
Mecki

9

受け入れられた回答に似ていますが、Swiftのより効率的なソリューションがあります。他の人が言及しているように、パフォーマンスの問題である値を変更するたびにトランザクションを作成するよりも良い場合もあります。たとえば、レイヤー位置を60 fpsでドラッグする一般的なユースケースです。

// Disable implicit position animation.
layer.actions = ["position": NSNull()]      

レイヤーアクションの解決方法については、アップルのドキュメントをご覧ください。デリゲートを実装すると、カスケードでもう1つのレベルがスキップされますが、私の場合は、デリゲートを関連するUIViewに設定する必要があるため、警告が多すぎて面倒でした。

編集:にNSNull準拠していると指摘したコメンターのおかげで更新されましたCAAction


NullActionSwift用のを作成する必要はなく、すでにNSNull準拠しCAActionているため、目的Cで行うのと同じことができます。layer.actions= ["position":NSNull()]
user5649358

私はあなたの答えをこれと組み合わせて、私のアニメーションCATextLayerを修正しましたstackoverflow.com/a/5144221/816017
Erik

これは、プロジェクトでCALayer線の色を変更するときに「アニメーション」の遅延を回避する必要があるという私の問題に対する優れた修正でした。ありがとう!!
PlateReverb 2017

短くて甘い!素晴らしい解決策!
David H

7

サムの答えとサイモンの困難に基づいて... CSShapeLayerを作成した後にデリゲート参照を追加します。

CAShapeLayer *myLayer = [CAShapeLayer layer];
myLayer.delegate = self; // <- set delegate here, it's magic.

... "m"ファイルの他の場所...

基本的にSamと同じですが、カスタムの「disableImplicitAnimations」変数配置を介して切り替える機能はありません。「ハードワイヤー」アプローチの詳細。

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {

    // disable all implicit animations
    return (id)[NSNull null];

    // allow implicit animations
    // return nil;

    // you can also test specific key names; for example, to disable bounds animation:
    // if ([event isEqualToString:@"bounds"]) return (id)[NSNull null];

}

7

実際、正しい答えは見つかりませんでした。私のために問題を解決する方法はこれでした:

- (id<CAAction>)actionForKey:(NSString *)event {   
    return nil;   
}

次に、特定のアニメーションを無効にするために、ロジックを何でもできますが、それらをすべて削除したかったので、nilを返しました。


5

Swiftで暗黙的なレイヤーアニメーションを無効にするには

CATransaction.setDisableActions(true)

この回答をありがとう。同じことをするようdisableActions()聞こえるので、私は最初に使用してみましたが、実際には現在の値を取得することです。それ@discardableもマークされていると思うので、これを見つけるのは難しくなります。出典:developer.apple.com/documentation/quartzcore/catransaction/...
オースティン

5

内部の無効化アクションへの簡単な方法を発見しCATransaction、内部で呼び出し、そのsetValue:forKey:ためkCATransactionDisableActionsのキー:

[CATransaction setDisableActions:YES];

迅速:

CATransaction.setDisableActions(true)

2

これを、-drawRect()メソッドを実装するカスタムクラスに追加します。自分のニーズに合わせてコードを変更してください。私にとって「不透明度」は、クロスフェードアニメーションを停止するためのトリックでした。

-(id<CAAction>) actionForLayer:(CALayer *)layer forKey:(NSString *)key
{
    NSLog(@"key: %@", key);
    if([key isEqualToString:@"opacity"])
    {
        return (id<CAAction>)[NSNull null];
    }

    return [super actionForLayer:layer forKey:key];
}

1

非常に迅速な(しかし確かにハッキーな)修正が必要な場合は、それを行うだけの価値があります(Swift):

let layer = CALayer()

// set other properties
// ...

layer.speed = 999

3
これを絶対にしないでください
m1h4

@ m1h4ありがとうございます。これが悪い考えである理由を説明してください
Martin CR

3
暗黙的なアニメーションをオフにする必要がある場合、それを行うためのメカニズム(一時的に無効にされたアクションを伴うcaトランザクション、または空のアクションをレイヤーに明示的に設定する)があるためです。アニメーションの速度をすぐに見えるように十分に高く設定すると、不要なパフォーマンスオーバーヘッド(元の作者が言及したもの)が発生し、さまざまな競合状態が発生する可能性があります(描画は別のバッファーで行われます)。後でディスプレイにアニメーション表示されます-正確には、上記のケースでは、0.25 / 999秒後です)。
m1h4 2017

それview.layer?.actions = [:]は本当に機能しないのは本当に残念です。速度の設定は醜いですが、動作します。
tcurdt

1

MacOSではなくiOSで1つの暗黙的なプロパティアニメーションのみを迅速かつ無効にするために更新

// Disable the implicit animation for changes to position
override open class func defaultAction(forKey event: String) -> CAAction? {
    if event == #keyPath(position) {
        return NSNull()
    }
    return super.defaultAction(forKey: event)
}

別の例として、この場合は2つの暗黙的なアニメーションを削除しています。

class RepairedGradientLayer: CAGradientLayer {

    // Totally ELIMINATE idiotic implicit animations, in this example when
    // we hide or move the gradient layer

    override open class func defaultAction(forKey event: String) -> CAAction? {
        if event == #keyPath(position) {
            return NSNull()
        }
        if event == #keyPath(isHidden) {
            return NSNull()
        }
        return super.defaultAction(forKey: event)
    }
}

0

iOS 7以降、これを行う便利なメソッドがあります。

[UIView performWithoutAnimation:^{
    // apply changes
}];

1
この方法でCALayerアニメーションがブロックされるとは思いません。
ベンジョン2016

1
@ベンジョンああ、私はあなたが正しいと思います。8月はあまり知りませんでした。この回答を削除する必要がありますか?
2016

:-)申し訳ありませんが、どちらかわかりません。コメントはとにかく不確実性を伝えているので、おそらく大丈夫です。
ベンジョン

0

CATextLayerの文字列プロパティを変更するときに、煩わしい(ぼやけた)アニメーションを無効にするには、次のようにします。

class CANullAction: CAAction {
    private static let CA_ANIMATION_CONTENTS = "contents"

    @objc
    func runActionForKey(event: String, object anObject: AnyObject, arguments dict: [NSObject : AnyObject]?) {
        // Do nothing.
    }
}

そして、それをそのように使用します(たとえば、正しいフォントなど、CATextLayerを適切に設定することを忘れないでください):

caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]

ここでCATextLayerの完全なセットアップを確認できます。

private let systemFont16 = UIFont.systemFontOfSize(16.0)

caTextLayer = CATextLayer()
caTextLayer.foregroundColor = UIColor.blackColor().CGColor
caTextLayer.font = CGFontCreateWithFontName(systemFont16.fontName)
caTextLayer.fontSize = systemFont16.pointSize
caTextLayer.alignmentMode = kCAAlignmentCenter
caTextLayer.drawsAsynchronously = false
caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]
caTextLayer.contentsScale = UIScreen.mainScreen().scale
caTextLayer.frame = CGRectMake(playbackTimeImage.layer.bounds.origin.x, ((playbackTimeImage.layer.bounds.height - playbackTimeLayer.fontSize) / 2), playbackTimeImage.layer.bounds.width, playbackTimeLayer.fontSize * 1.2)

uiImageTarget.layer.addSublayer(caTextLayer)
caTextLayer.string = "The text you want to display"

これで、caTextLayer.stringを必要なだけ更新できます=)

これこの答えに触発されました。


0

これを試して。

let layer = CALayer()
layer.delegate = hoo // Same lifecycle UIView instance.

警告

UITableViewインスタンスのデリゲートを設定すると、クラッシュが発生することがあります(おそらく、スクロールビューのヒットテストが再帰的に呼び出されます)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.