# Transforms

When I discussed how to resize `UIPickerViews` last year, I touched upon the fact that a `UIView's` `transform` property is rather unusual; according to the documentation:

The origin of the transform is the value of the center property, or the layer’s anchorPoint property if it was changed. (Use the layer property to get the underlying Core Animation layer object.)

This turns out to be something of a hassle when trying to reason about transformations. Fortunately, a little math can sort things out.

### Math

It’s probably worth pointing out that the affine transformations used in computer graphics aren’t arbitrary collections of numbers; they are matrices which are applied to vectors which represent points in a coordinate system. The results of these computations represent points in a new coordinate system. For instance, points specified in the coordinate system of a `UIView` are transformed into the coordinate system of its superview, and its superview’s superview, and so on, ultimately being brought into the coordinate system of the physical screen.

The Mac universe seems to consistently use row vectors, so, given the definition of a `CGAffineTransform`, a point is transformed like this:

``````                         [a  b  0]
[x' y' 1]  = [x  y  1] * [c  d  0]
[tx ty 1]``````

that is:

``````x' = x*a + y*c + tx
y' = x*b + y*d + ty``````

### The Problem

Unfortunately, if you just grab the `transform` of a `UIView` and invoke `CGPointApplyAffineTransform()` to transform a point, you’re very likely to get the wrong answer. More precisely, if you were to execute:

``CGPoint np0 = [aView.superview convertPoint:op fromView:aView];``

and:

`` CGPoint  np1 = CGPointApplyAffineTransform(op, aView.transform);``

for an arbitrary point `op`, you would likely get two different results for `np0` and `np1`.

This happens because the `transform` property is a big fat lie. More specifically, that business about the “origin of the transform [being] the value of the center property” is nonsense; the origin of any transform is the origin of the initial coordinate system. What’s really happening is that prefix and suffix transforms are being concatenated with the matrix stored in the `transform` property before it is applied to any points. If you want to reason closely about the transforms being done by the system, you have to take these “hidden” transforms into account.

### Wrappers

When converting points from a `UIView's` coordinate system to that of its parent, the system effectively moves the point at the center of the `bounds` rectangle to the origin of the initial coordinate system, then applies the `UIView's` `transform`, then moves the point at the origin of the transformed coordinate system to the `UIView's` `center` property. (Remember that `center` is a point in the superview’s coordinate space.)

In code, it looks something like this:

``````CGAffineTransform MakeEffectiveTransform(UIView* subView)
{
CGRect b = subView.bounds;
CGAffineTransform t0 = CGAffineTransformMakeTranslation(-b.origin.x-b.size.width/2, -b.origin.y-b.size.height/2);
CGAffineTransform t1 = CGAffineTransformMakeTranslation(subView.center.x, subView.center.y);
return CGAffineTransformConcat(t0, CGAffineTransformConcat(subView.transform, t1));
}

CGPoint ConvertPointToSuperview(CGPoint p, UIView* subview)
{
return CGPointApplyAffineTransform(p, MakeEffectiveTransform(subview));
}``````

### Lying

This analysis lets us derive a simple function that will convert “proper” transformations (i.e. matrices that give us the right answers when multiplied with row vectors) into matrices suitable for assignment to `transform` properties. We just need to apply the inverses of the prefix and suffix transforms, s.t. the prefix and suffix will be canceled out when applied. Something like this would work:

``````CGAffineTransform MakeAssignableTransform(CGAffineTransform t, UIView* subView)
{
CGRect b = subView.bounds;
CGAffineTransform t0 = CGAffineTransformMakeTranslation(b.origin.x+b.size.width/2, b.origin.y+b.size.height/2);
CGAffineTransform t1 = CGAffineTransformMakeTranslation(-subView.center.x, -subView.center.y);
return CGAffineTransformConcat(t0, CGAffineTransformConcat(t, t1));
}``````

### Why Should You Care?

The truth is, most of this stuff will make absolutely no difference to you most of the time. But, one day, you may find yourself trying to debug some bizarre animation problem, and digging into the underlying `transforms` on that day, it will be helpful to bear in mind that you can’t just run points through them and expect to have the results make any sense.

I’m still trying to decide whether I think this was a clever move on AAPL’s (NeXT’s?) part. I guess you need something like this if you want to have a distinct `center` property … I don’t much like it, though.

Share and Enjoy:
This entry was posted in iPhone. Bookmark the permalink.