ioszoomingscalecgaffinetransformcgaffinetransformscale

CGAffineTransformScale not working with zero scale


Updating to iOS 8 and I've noticed an interesting one. I use CGAffineTransformScale to scale an image view to zero, the idea being that it disappears from the user's view neatly. It's work up to iOS8 but now it's not at all. Instantly the view is gone; blank!

So originally I used:

CGAffineTransform zoom = CGAffineTransformScale(CGAffineTransformIdentity, 0.0f, 0.0f);
myImageView.transform = zoom;

Why would this which worked up to now not work and when the functions haven't been depreciated?


Solution

  • A CGAffineTransform is a 3x3 matrix, with its rightmost column permanently set to (0, 0, 1)T. So it actually stores six values, which it calls a, b, c, d, tx, and ty. The first four control rotation, scaling, and skewing. The last two control translation.

    You might think that animating from transform T to transform U is a simple matter of component-wise interpolation: T.a to U.a, T.b to U.b, etc. (I'll call this “simple interpolation”.)

    You would be wrong.

    OK, stick with me here. This explanation is going to seem circuitous but I promise by the end you'll understand why the zero scale breaks animation.

    Here's a demo app I whipped up. It interpolates simply between the identity transform and a 160˚ rotation (not 180˚).

    simple interpolation

    Notice two things: the image shrinks and then expands, and it doesn't have uniform angular velocity. The rotation starts slow, speeds up, and then slows down again. That's not my jerky hand motion; it really behaves that way using simple interpolation.

    So what's going on here? Let's look at the transform halfway through (at 80˚):

    80 degrees

    In text:
    [0.030153692, 0.17101011, -0.17101011, 0.030153692, 0, 0]

    What should a rotation transform for 80˚ look like? Like this:
    [0.17364822, 0.98480773, -0.98480773, 0.17364822, 0, 0]

    As you might have noticed, those matrices are rather different. A transform that represents only a rotation has several properties: a*a + b*b == 1, a == d, and b == -c. The true rotation matrix has all of these properties, but the simply-interpolated matrix doesn't. Specifically, its a*a + b*b == .030153695, which is pretty far from 1. This means it represents not just rotation, but scaling.

    This means that, in general, you can't just interpolate simply between two transforms and get decent results. So the system does something more complicated.

    Core Animation decomposes the transform into four parts: a rotation angle, a scale factor, a shear factor, and a translation. It interpolates these parts, and uses the interpolated values to compute the intermediate transforms of the animation.

    “OK”, you're saying, “but what does this have to do with a zero-scale matrix breaking the animation?”

    You can find the math for decomposing the matrix in this mathoverflow Q&A. Notice that to compute the rotation angle and the shear factor, you need to divide by at least one component of the matrix (A11 or A21 in that answer, a or b in CGAffineTransform). But when you scale by a factor of zero, those components become zero. You can't divide by zero.

    So when the system tries to decompose your transform, it fails, and it gives up.

    I'm not saying this is good behavior, but it's the behavior we live with.