How to disable CALayer implicit animations?
Asked Answered
L

12

65

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?

Lavonnelaw answered 29/4, 2011 at 14:38 Comment(0)
T
66

Swift

CATransaction.begin()
CATransaction.setDisableActions(true)

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

CATransaction.commit()
Tachymetry answered 22/1, 2016 at 8:0 Comment(0)
I
46

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];
Inert answered 1/5, 2011 at 20:59 Comment(0)
A
21

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.       
Amenra answered 3/11, 2011 at 6:29 Comment(2)
This is unquestionably as good an answer as Oliver's. Better, imho.Procurator
For someone (like me) looking for this kind of answer, but in Swift, the format is: layer.actions = ["sublayers":NSNull()]Ulphia
A
18

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
}
Actinology answered 22/2, 2015 at 18:34 Comment(0)
F
18

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
    }
Fanchie answered 20/2, 2019 at 10:24 Comment(1)
Can you show an example with "something you don't want to animate"?Milburn
S
13

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
Stivers answered 23/10, 2019 at 8:6 Comment(0)
M
4

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
    }
}
Marylouisemaryly answered 12/2, 2019 at 18:44 Comment(0)
L
1

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() }
Lota answered 11/10, 2016 at 15:12 Comment(0)
S
1

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)

Sequin answered 22/4, 2017 at 14:37 Comment(1)
This answer is great if you want to animate a few things. However it's not suitable in the longer run. Id advice looking into: Setting layer?.actions In instead. This disables all Default animation. See InteractiveView in my project on github: github.com/eonist/swift-utilsSequin
G
1

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"]];
}
Gouveia answered 1/10, 2018 at 6:24 Comment(0)
R
0

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)
}
Rightly answered 11/7, 2019 at 0:51 Comment(0)
R
0

In 2011 it made sense to use CATransaction, but since iOS 7 (2013) you should use +[UIView performWithoutAnimation:]. That makes sure you are not left with an uncommitted CATransaction in case a return statement slips in the code between begin and commit.

For some system views, some changes only happen on layout (like when setting the title of a UIButton). Call layoutIfNeeded on those views.

Objective C:

[UIView performWithoutAnimation:^{
    // Your code goes here
    // Optionally layout your view
    [view layoutIfNeeded];
}];

Swift:

UIView.performWithoutAnimation {
    // Your code goes here
    // Optionally layout your view
    view.layoutIfNeeded()
}
Rebecarebecca answered 9/9, 2022 at 7:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.