How to disable CALayer implicit animations?

2020-01-29 03:40发布

问题:

It's driving me crazy! I am working on a drawing application. Let's say I am working on a UIView called sheet.

I am adding some sublayers to this view ([sheet.layer addSublayer:...]) and then I want to draw into them. To do so I am creating a CGImageRef and putting it into the layer's contents. But it's animated and I don't want that.

I tried everything:

  • removeAnimationForKey:
  • removeAllAnimations
  • set the actions dictionary
  • using the actionlayer delegate
  • [CATransaction setDisableAnimations:YES]

It's seems correct. I don't understand why this layer is still animated ;_;
Am I doing something wrong? Is there a secret way?

回答1:

You have to explicitly disable animations by wrapping your code in a CATransaction

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
                 forKey:kCATransactionDisableActions];
layer.content = someImageRef;
[CATransaction commit];


回答2:

Swift

CATransaction.begin()
CATransaction.setDisableActions(true)

// change layer properties that you don't want to animate

CATransaction.commit()


回答3:

As of Mac OS X 10.6 and iOS 3, CATransaction also has a setDisableActions method that sets the value for key kCATransactionDisableActions.

[CATransaction begin];
[CATransaction setDisableActions:YES];

layer.content = someImageRef;

[CATransaction commit];

In Swift, I like to use this extension method:

extension CATransaction {
    class func withDisabledActions<T>(_ body: () throws -> T) rethrows -> T {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        defer {
            CATransaction.commit()
        }
        return try body()
    }
}

You can then use it like this:

CATransaction.withDisabledActions {
    // your stuff here
}


回答4:

Another way:

  1. You should disable default animation of your sheet.layer, which is called implicitly when adding sublayer.

  2. You should also content-animation of each sublayer. Of course, you can use "kCATransactionDisableActions" of CATransaction each time you set sublayer.content. But, you can disable this animation once, when you are creating your sublayer.


Here is code:

// disable animation of container
sheet.layer.actions = [NSDictionary dictionaryWithObject:[NSNull null] 
                                                  forKey:@"sublayers"];

// disable animation of each sublayer
sublayer.layer.actions = [NSDictionary dictionaryWithObject:[NSNull null] 
                                                     forKey:@"content"];

// maybe, you'll also have to disable "onOrderIn"-action of each sublayer.       


回答5:

Swift 4 extension :

extension CATransaction {

    static func disableAnimations(_ completion: () -> Void) {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        completion()
        CATransaction.commit()
    }

}

Usage :

    CATransaction.disableAnimations {
        // things you don't want to animate
    }


回答6:

Swift 2

I was able to disable all animations as follows, where myView is the view you are working with:

myView.layer.sublayers?.forEach { $0.removeAllAnimations() }

And as a side note, removing all layers:

myView.layer.sublayers?.forEach { $0.removeFromSuperlayer() }


回答7:

Reusable global code:

/**
 * Disable Implicit animation
 * EXAMPLE: disableAnim{view.layer?.position = 20}//Default animation is now disabled
 */
func disableAnim(_ closure:()->Void){
    CATransaction.begin()
    CATransaction.setDisableActions(true)
    closure()
    CATransaction.commit()
}

Add this code anywhere in your code (Globally scoped)



回答8:

This is an old question but the problem remains. Sometimes you don't want the animations that CALayer forces on you. I wasn't happy with the transaction based approach as I just wanted to turn these actions off. For good. Here's a Swift 4 solution to subclass CALayer to allow a choice whether to allow any action or globally disable them. You can also create CAShapeLayer, CATextLayer subclasses with the same contents:

public class ActionCALayer: CALayer {
    public var allowActions: Bool = false

    override public func action(forKey event: String) -> CAAction? {
        return allowActions ? super.action(forKey: event) : nil
    }
}


回答9:

before adding the layer to your view with i.e. [self.layer addSublayer:yourCALayer] and also after its already added you can disable specific animated propertys of your CALayer by overwriting the animation key. The key you set to NULL is named after the property, here shown like its done for the layer.position = CGPoint(x,y);

yourCALayer.actions = [NSDictionary dictionaryWithObject:[NSNull null] forKey:@"position"];

Because the actions property is an NSDictionary which does not allow storing of nil you set it explicit to an NULL object with [NSNull null], which is the same as (id)kCFNull You can do this for all sublayers by iterating thru all sublayers of the views layer with...

for (CALayer *iterationLayer in self.layer.sublayers ) {
    iterationLayer.actions = [NSDictionary dictionaryWithObject:[NSNull null] forKey:@"position"];
    //or for multiple keys at once
    NSNull *nop = [NSNull null];
    iterationLayer.actions = [NSDictionary dictionaryWithObjects:@[nop,nop] forKeys:@[@"position",@"contents"]];
}


回答10:

I completely agree with Ryan. His answer is for MacOS, for iOS you add the following to create an NSNull() action. This question Disabling implicit animations in -[CALayer setNeedsDisplayInRect:] and the documentation in the header lead me here

// 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)
}


回答11:

Layer extension:

extension CALayer {    
    var areAnimationsEnabled: Bool {
        set {
            delegate = newValue ? nil : CALayerAnimationsDelegate.shared
        }
        get {
            return delegate == nil
        }
    }
}

fileprivate class CALayerAnimationsDelegate: NSObject, CALayerDelegate {
    static let shared = CALayerAnimationsDelegate()
    private let null = NSNull()

    func action(for layer: CALayer, forKey event: String) -> CAAction? {
        return null
    }
}

Usage:

anyLayer.areAnimationsEnabled = false