How to disable CALayer implicit animations?
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?Swift
CATransaction.begin()
CATransaction.setDisableActions(true)
// change layer properties that you don't want to animate
CATransaction.commit()
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];
Another way:
You should disable default animation of your sheet.layer, which is called implicitly when adding sublayer.
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.
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
}
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
}
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
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
}
}
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() }
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)
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"]];
}
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)
}
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()
}
精彩评论