CGAffineTransformScale not working with zero scale

守給你的承諾、 提交于 2019-11-29 08:10:53
rob mayoff

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˚).

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˚):

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.

So what I've done is to change the zoom to scale from zero to small:

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

And this works quite nicely. Back to normal.

I'm happy with the solution, and I'm mainly interested in presenting this as a solution for anybody else who needs it, but I'd also ask, why is this?

(Please be nice - just trying to help anybody who comes across this one too.)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!