Sunday, December 05, 2010

Learning the value of CATransaction

The iOS API is full of side effects. Although some developers might find these useful, when designing APIs as a product manager, I always insist on limiting side effects as much as possible -- it leads to developer confusion.

One side effect in particular perplexed me to no end this week, and required a lot of crafty Googling to narrow down. I'll post it here in hopes that spreading the information makes it easier to find for others.

The problem
I've got a CALayer, and I'm doing a variety of things including changing it's contents, hidden, and center properties. Changing the contents in particular is what got me -- there was a strange flicker from one image to the next whenever I did it. This made things look terrible, since I'm reusing these CALayers quite a bit in my engine.

The solution
It turns out that CALayer properties all include an implicit animation of 0.25 seconds whenever you set them. When you change the contents property from one image to another, for example, Core Animation actually crossfades between the two images. This is cool, but it really doesn't work for what I'm doing.

You can tell it to stop doing this, luckily, by manually opening a CATransaction before you make your batch of changes, like this:
[CATransaction begin];
[CATransaction setAnimationDuration:0];
When you've finished making changes to the CALayer, commit the transaction like so:
[CATransaction commit];
One gotcha -- if you forget to commit a transaction, the run loop appears to hang (without crashing), causing all sorts of fun problems. Never ever forget to commit your transaction if you open one.

Now let's say you want to nest animations. The outer transaction is set for a 0 second duration, while you want the inner transaction to take 0.5 seconds. Surround the inner transaction with some additional code like this:
[UIView beginAnimations:nil context:NULL];
[CATransaction begin];
[CATransaction setAnimationDuration:0.5];
// Perform animations by changing properties
[CATransaction commit];
[UIView commitAnimations];
Hope it helps!

1 comment:

Ivan Vučica said...

I'm aware that the post is from 2010, but for people who may in the future search for a way to disable implicit animations, here's a tip.

Instead of setting duration to zero, how about turning off "actions" - the specifications on how to generate the implicit animation?

You can disable them using transactions:

[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

Also, you can disable them per-layer, by subclassing CALayer and overriding -actionForKey: to return nil. Or, if you have a layer delegate, -actionForLayer:forKey: should return nil, too.

Apple docs:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreAnimation_guide/Articles/Actions.html

StackOverflow:
http://stackoverflow.com/questions/2244147/disabling-implicit-animations-in-calayer-setneedsdisplayinrect