web-dev-qa-db-ja.com

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

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

CATransactionを使用してアクションをオフにし、期間をゼロに設定しようとしましたが、どちらも機能しませんでした。私が使用しているコードは次のとおりです。

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

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

128
Ben Gottlieb

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

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

layer.actions = newActions;

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


Swiftバージョン:

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

また:

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

//foo

[CATransaction commit];
90
mxcl

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

Objective-C

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

Swift

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

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

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

@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内で呼び出します。これは、実際の再描画が後で行われる可能性があるため(おそらくそうなる可能性があるため)です。 この回答では のように、カスタムプロパティを使用することをお勧めします。

23
skozin

受け入れられた答えに似ていますが、Swiftに対するより効率的なソリューションがあります。場合によっては、値を変更するたびにトランザクションを作成するよりも優れている場合があります。 60fpsでレイヤーの位置をドラッグする一般的な使用例。

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

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

編集:NSNullCAActionに準拠していることを指摘しているコメント者のおかげで更新されました。

8
Jarrod Smith

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

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

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

7
Simon

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

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

...「m」ファイルの他の場所...

基本的に、カスタムの「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
bob

CATransactionキーに対してsetValue:forKey:を内部的に呼び出すkCATransactionDisableActions内のアクションを無効にする簡単なメソッドを見つけました。

[CATransaction setDisableActions:YES];

迅速:

CATransaction.setDisableActions(true)
5
rounak

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

CATransaction.setDisableActions(true)
3
pawpoise

-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];
}
2
Kamran Khan

非常に迅速な(しかし明らかにハッキングな)修正が必要な場合は、ただ行う価値があるかもしれません(Swift):

let layer = CALayer()

// set other properties
// ...

layer.speed = 999
1
Martin CR

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を必要なだけ更新できます=)

this および this answerに触発されました。

0
Erik Zivkovic

これを試して。

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

警告

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

0
Tueno

Swift用に更新され、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)
}
0
GayleDDS

iOS 7現在、これを行う便利なメソッドがあります:

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