Disabling implicit animations in -[CALayer setNeedsDisplayInRect:]
Solution 1
You can do this by setting the actions dictionary on the layer to return [NSNull null]
as an animation for the appropriate key. For example, I use
NSDictionary *newActions = @{
@"onOrderIn": [NSNull null],
@"onOrderOut": [NSNull null],
@"sublayers": [NSNull null],
@"contents": [NSNull null],
@"bounds": [NSNull null]
};
layer.actions = newActions;
to disable fade in / out animations on insertion or change of sublayers within one of my layers, as well as changes in the size and contents of the layer. I believe the contents
key is the one you're looking for in order to prevent the crossfade on updated drawing.
Swift version:
let newActions = [
"onOrderIn": NSNull(),
"onOrderOut": NSNull(),
"sublayers": NSNull(),
"contents": NSNull(),
"bounds": NSNull(),
]
Solution 2
Also:
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
//foo
[CATransaction commit];
Solution 3
When you change the property of a layer, CA usually creates an implicit transaction object to animate the change. If you do not want to animate the change, you can disable implicit animations by creating an explicit transaction and setting its kCATransactionDisableActions property to 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()
Solution 4
In addition to Brad Larson's answer: for custom layers (that are created by you) you can use delegation instead of modifying layer's actions
dictionary. This approach is more dynamic and may be more performant. And it allows disabling all implicit animations without having to list all animatable keys.
Unfortunately, it's impossible to use UIView
s as custom layer delegates, because each UIView
is already a delegate of its own layer. But you can use a simple helper class like this:
@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
Usage (inside the view):
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
Sometimes it's convenient to have view's controller as a delegate for view's custom sublayers; in this case there is no need for a helper class, you can implement actionForLayer:forKey:
method right inside the controller.
Important note: don't try to modify the delegate of UIView
's underlying layer (e.g. to enable implicit animations) — bad things will happen :)
Note: if you want to animate (not disable animation for) layer redraws, it is useless to put [CALayer setNeedsDisplayInRect:]
call inside a CATransaction
, because actual redrawing may (and probably will) happen sometimes later. The good approach is to use custom properties, as described in this answer.
Solution 5
Here's a more efficient solution, similar to accepted answer but for Swift. For some cases it will be better than creating a transaction every time you modify the value which is a performance concern as others have mentioned e.g. common use-case of dragging the layer position around at 60fps.
// Disable implicit position animation.
layer.actions = ["position": NSNull()]
See apple's docs for how layer actions are resolved. Implementing the delegate would skip one more level in the cascade but in my case that was too messy due to the caveat about the delegate needing to be set to the associated UIView.
Edit: Updated thanks to the commenter pointing out that NSNull
conforms to CAAction
.
Ben Gottlieb
Newton, Palm, Mac, now iPhone. Love this industry!
Updated on September 30, 2020Comments
-
Ben Gottlieb over 3 years
I've got a layer with some complex drawing code in its -drawInContext: method. I'm trying to minimize the amount of drawing I need to do, so I'm using -setNeedsDisplayInRect: to update just the changed parts. This is working splendidly. However, when the graphics system updates my layer, it's transitioning from the old to the new image using a cross-fade. I'd like it to switch over instantly.
I've tried using CATransaction to turn off actions and set the duration to zero, and neither work. Here's the code I'm using:
[CATransaction begin]; [CATransaction setDisableActions: YES]; [self setNeedsDisplayInRect: rect]; [CATransaction commit];
Is there a different method on CATransaction I should use instead (I also tried -setValue:forKey: with kCATransactionDisableActions, same result).
-
mxcl about 13 yearsTo prevent movement when changing the frame use the
@"position"
key. -
Karoy Lorentey almost 13 yearsYou can replace
//foo
with[self setNeedsDisplayInRect: rect]; [self displayIfNeeded];
to answer the original question. -
Joe D'Andrea over 12 yearsThanks! This lets me set an animated flag on my custom view as well. Handy for use within a table view cell (where cell reuse can lead to some trippy animations while scrolling).
-
Andrew over 12 yearsAlso be sure to add the
@"hidden"
property in the action dictionary too if you are toggling the visibility of a layer that way and wish to disable the opacity animation. -
Ser Pounce over 12 years@brad larson - do you know how you'd use this to disable the navigation animation in the navigation bar (ie when a view controller gets pushed on the stack), ie what key would you use?
-
Brad Larson over 12 years@CoDEFRo - That's totally unrelated to these actions. This is just for Core Animation's implicit animations on layers. What you describe is something internal to UIKit, so it's not controllable via what I show here.
-
Pascalius about 12 yearsLeads to performance issues for me, setting actions is more performant
-
pqnet about 12 yearsis there a place where all these string constants are documented? I can't seem to find it on apple docs
-
Brad Larson about 12 years@pqnet - Some are simply animatable properties. The more subtle ones can be discovered by overriding
-animationForKey:
and seeing which keys are animated in response to an action. -
pqnet about 12 years@BradLarson that's the same idea i came up with after some struggling (i overrode
actionForKey:
instead), discoveringfontSize
,contents
,onLayout
andbounds
. It seems like you can specify any key you could use insetValue:forKey:
method, actually specifying complex key paths likebounds.size
. -
Patrick Pijnappel over 11 yearsThere are actually constants for these 'special' strings not representing a property (e.g. kCAOnOrderOut for @"onOrderOut") well-documented here: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…
-
titaniumdecoy over 11 yearsShorthand:
[CATransaction setDisableActions:YES]
-
Geek about 10 years@BradLarson Not working for me. stackoverflow.com/questions/21574661/…
-
pronebird over 9 yearssetDisableActions: does the same.
-
aleclarson over 9 yearsThis isn't working for me. See here.
-
skozin over 9 yearsHmmm. I have never had any issues with this approach. The code in the linked question looks ok and probably the issue is caused by some other code.
-
skozin over 9 yearsAh, I see that you have already sorted out that it was wrong
CALayer
that preventednoImplicitAnimations
from working. Maybe you should mark your own answer as correct and explain what was wrong with that layer? -
aleclarson over 9 yearsI was simply testing with the wrong
CALayer
instance (I had two at the time). -
Hlung over 9 yearsAdding to @titaniumdecoy comment, just in case anyone got confused (like me),
[CATransaction setDisableActions:YES]
is a shorthand for just the[CATransaction setValue:forKey:]
line. You still need thebegin
andcommit
lines. -
Benjohn over 9 years@Patrick !That's amazingly useful, many thanks! My solution had been
NSStringFromSelector(@selector(contents))
, which at least has some compile time checking. Yours is much better. Could the Brad update the answer to include this? I'm happy to edit, but large improvements like this are often rejected in moderation. -
Benjohn over 9 years@patrick on looking at this, unfortunately only three of the properties seem to have keys defined for them:
kCAOnOrderIn
,kCAOnOrderOut
&kCATransition
. So, Brad can probably leave the answer as it is. -
Patrick Pijnappel over 9 years@Benjohn Only the keys that don't have a corresponding property have constants defined. BTW, the link seems to be dead, here's the new URL: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
-
Sam Soffes about 9 yearsThis doesn't work for me. Can't undo my upvote. See mxcl's answer below. That works.
-
user5649358 over 8 yearsNo need to create a
NullAction
for Swift,NSNull
conforms toCAAction
already so you can do the same you do in objective C: layer.actions = [ "position" : NSNull() ] -
Jambaman over 8 yearsThis one was the way simplest solution I got working in Swift!
-
Benjohn about 8 yearsI do not believe that this method blocks CALayer animations.
-
Warpling about 8 years@Benjohn Ah I think you're right. Didn't know as much in August. Should I delete this answer?
-
Benjohn about 8 years:-) I'm never sure either, sorry! The comments communicate the uncertainty anyway, so it's probably okay.
-
Erik Zivkovic about 8 yearsI combined your answer with this one to fix my animating CATextLayer stackoverflow.com/a/5144221/816017
-
Mecki almost 8 yearsNice solution... but
NSNull
does not implement theCAAction
protocol and this is no protocol that only has optional methods. This code as well crash and you can't even translate that to swift. Better solution: Make your object conform to theCAAction
protocol (with an emptyrunActionForKey:object:arguments:
method that does nothing) and returnself
instead of[NSNull null]
. Same effect but safe (will not crash for sure) and also works in Swift. -
skozin almost 8 years@Mecki, this is incorrect. NSNull is allowed as the return value. See actionForKey: method reference (which
CALayerDelegate
'sactionForLayer:forKey:
documentation redirects to). -
skozin almost 8 years@Mecki Specifically, it says: "The delegate must do one of the following: 1) Return the action object for the given key. 2) Return the NSNull object if it does not handle the action." Also,
NSNull
does conform toCAAction
, see the list of protocols it conforms to in its reference (currently,CAAction
is the first protocol in the list). -
skozin almost 8 yearsYour suggestion of implementing
CAAction
by some object you have control of will work, though, but I don't think it's necessary, even in Swift. I can assure you that I had no single crash caused by returningNSNull
from this method. Also, I doubt Apple documentation and examples would suggest something that could crash your app. -
skozin almost 8 yearsAh, forgot to add: to create
NSNull
object in Swift, simply useNSNull()
. -
Mecki almost 8 yearsSorry, I have no idea what page you are seeing but your link takes me to a page where
NSNull
definitely does NOT conform to theCAAction
protocol. When I try to returnNSNull()
in Swift, I get a compile time error telling me exactly that. And when I read the delegate documentation, it nowhere says that you may returnNSNull
. See also here s33.postimg.org/si135uetr/… (this is where your link takes me) and here s33.postimg.org/j5a5ciegv/… -
skozin almost 8 yearsHmm, that's very strange. That's what I see: NSNull and actionForKey:. And what are you seeing when you follow the link to CA programming guide in the answer ("you can use delegation")? Do you see this?
-
skozin almost 8 yearsThanks for the info, I'll try to use this method in Swift and update the answer accordingly.
-
PlateReverb about 7 yearsThis was a great fix for my problem of needed to bypass the "animation" delay when changing the color of CALayer lines in my project. Thanks!!
-
Sentry.co almost 7 yearsThis was the only perma solution that worked for me in swift. I tried the other solutions on this page in many possible combinations.
-
Sentry.co almost 7 yearsNO luck for me in swift with this answer. Tried @Mecki's suggestion as well. What worked in the end was setting
layer?.actions = ["sublayers":NSNull(),"content":NSNull(),"onOrderOut":NSNull(),"bounds":NSNull(),"hidden":NSNull(),"position":NSNull()]//avoids implicit animation
-
m1h4 over 6 yearsPlease never do this ffs
-
Martin CR over 6 years@m1h4 thanks for that - please explain why this is a bad idea
-
m1h4 over 6 yearsBecause if one needs to turn off implicit animations there is a mechanism for doing that (either a ca transaction with temporarily disabled actions or explicitly setting empty actions onto a layer). Just setting the animation speed to something hopefully high enough to make it seem instant causes loads of unnecessary performance overhead (which the original author mentions is relevant for him) and potential for various race-conditions (the drawing is still done into a seperate buffer to be animated into the display at a later point - to be precise, for your case above, at 0.25/999 sec later).
-
Aᴄʜᴇʀᴏɴғᴀɪʟ almost 6 yearsThe comment by @Andy is by far the best and easiest way to do this!
-
Austin over 5 yearsThanks for this answer. I first tried using
disableActions()
as it sounds like it does the same thing, but it's actually to get the current value. I think it's marked@discardable
too, making this harder to spot. Source: developer.apple.com/documentation/quartzcore/catransaction/… -
tcurdt over 4 yearsIt's really is a shame that
view.layer?.actions = [:]
doesn't really work. Setting the speed is ugly but works. -
David H over 4 yearsShort and sweet! Great solution!
-
eonil over 3 yearsThis worked for me. Don't forget to subclass
CALayer
to override the method. -
Fyodor Volchyok over 2 yearsSeems like the most recent link to properties is this: developer.apple.com/library/archive/documentation/Cocoa/…