How to disable CALayer implicit animations?

IosAnimationCalayer

Ios Problem Overview


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?

Ios Solutions


Solution 1 - Ios

Swift

CATransaction.begin()
CATransaction.setDisableActions(true)

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

CATransaction.commit()

Solution 2 - Ios

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];

Solution 3 - Ios

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
}

Solution 4 - Ios

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.       

Solution 5 - Ios

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
    }

Solution 6 - Ios

Layer extension:

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

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

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

Usage:

anyLayer.areAnimationsEnabled = false

Solution 7 - Ios

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

Solution 8 - Ios

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

Solution 9 - Ios

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)

Solution 10 - Ios

The following solution avoids temporarily use of +(CATransaction) before and after layers and sets the needed behaviour (no animation for specific properties of CALayers) permanently unless you create actions on purpose. This way you end up with cleaner source clearly expressing what the approach is and still have the full potential power of CATransaction.

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"]];
}

Solution 11 - Ios

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEsepherView Question on Stackoverflow
Solution 1 - IosSuragchView Answer on Stackoverflow
Solution 2 - IosOlivier TaboneView Answer on Stackoverflow
Solution 3 - IoszneakView Answer on Stackoverflow
Solution 4 - IosholodnyalexView Answer on Stackoverflow
Solution 5 - IosSkaalView Answer on Stackoverflow
Solution 6 - IosOleg BarinovView Answer on Stackoverflow
Solution 7 - IosRyan FrancesconiView Answer on Stackoverflow
Solution 8 - Iosuser4806509View Answer on Stackoverflow
Solution 9 - IosSentry.coView Answer on Stackoverflow
Solution 10 - IosOl SenView Answer on Stackoverflow
Solution 11 - IosGayleDDSView Answer on Stackoverflow