Home > Blogs > Ask Big Nerd Ranch: Rotating an iPhone View Around a Point

Ask Big Nerd Ranch: Rotating an iPhone View Around a Point

By  May 20, 2010

Topics: Programming

Brian Hardy answers a question about view rotation.

Sample code for this article is available in the Big Nerd Ranch github repository. The sample application demonstrates several techniques illustrated here, and works on iPhone or iPad.

Q. On iPhone OS, how can I rotate a view around an arbitrary point?

A. By default, views in Cocoa Touch (and Cocoa) are configured to rotate around their center point. While this is commonly useful (think of a UIActivityIndicatorView), often you will want to use a point other than the center. There are (at least) two ways of doing this. You can change the anchorPoint property of the view's layer. Alternatively, you can wrap the view in a superview, with the superview's center located at the point you want to rotate around. In either case, the mechanism for rotation is the same. Both techniques are discussed here.

A UIView Rotation Primer

Before we discuss the details of rotating a view around a point, let's make sure we have a clear understanding of rotation in general. First, the obvious: rotation is a type of animation. In Cocoa (Touch) there are two general methods for animation: modifying properties of a UIView inside an animation block, and modifying properties of a view's layer property (an instance of CALayer). The former technique allows you to do simple animations on entire views. The latter technique delves into Core Animation, which is much more flexible and potentially more complicated.

Regardless of the technique you use for animation, rotations are achieved by modifying the transform property of the view or layer to rotate. The mechanics of this are slightly different for views and layers, and below is an example that applies the same rotation to a view and its layer (you would typically only do one or the other).

Simple Rotation of UIView and CALayer

// assume "view" is an instance of UIView.
// UIView's transform property is a CGAffineTransform.
[UIView beginAnimations:nil context:NULL]; // arguments are optional
view.transform = CGAffineTransformMakeRotation(M_PI / 2.0);
[UIView commitAnimations];

// CALayer's transform property is a CATransform3D.
// rotate around a vector (x, y, z) = (0, 0, 1) where positive Z points
// out of the device's screen.
view.layer.transform = CATransform3DMakeRotation(M_PI / 2.0, 0, 0, 1);

The above example is about the simplest way to rotate a view or a layer, and in both cases it relies on a lot of defaults. Specifically, the layer rotation uses what Apple calls "implicit" animation. Implicit animation changes the currently displayed value of a property to the new value you set over a default period of time. Sometimes, this may be all you want to do. More often the situation is more complex.

With Core Animation, the alternative to implicit animation is "explicit" animation, which is nowhere near as racy as it sounds. Explicit animation requires that you create an animation object (an instance of CAAnimation or one of its subclasses) and configure that object to animate as you desire. Once you have created the animation object, you can add it to a layer, and the animation will be performed as you requested. This technique allows you to specify not only the property to animate, but also the "from" and "to" values, the duration, and many other options. Here is a basic example using the same rotation above:

Explicit Rotation Animation

CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
rotate.fromValue = [NSNumber numberWithFloat:0];
rotate.toValue = [NSNumber numberWithFloat:M_PI / 2.0];
rotate.duration = 2.0; // seconds
[view.layer addAnimation:rotate forKey:nil]; // "key" is optional

The rotation above will rotate the view's layer one quarter turn over the course of two seconds, starting at zero and moving to M_PI / 2.0, which is, in radians, equal to 90 degrees.

Rotation Around an Arbitrary Point

As discussed in the introduction, rotations are applied, by default, around the center of the view. In this case, we want to rotate around some point other than the center. Let's examine two ways of accomplishing this.

Rotate the Superview

Perhaps the more straightforward way of rotating a view around an arbitrary point is to wrap that view in a superview whose center point is the point you want to rotate around. You can the apply the rotation animation to the superview, and it will appear as though the view you want rotated is moving around the specified point.

For this technique, it often makes sense to make the superview transparent so that it does not affect the appearance of the scene. Here is an example that demonstrates this technique:

// assume we'll rotate around the point (100, 100)
CGRect aFrame = CGRectMake(0, 0, 200, 200);
// this is the view we'll rotate
UIView *superview = [[[UIView alloc] initWithFrame:aFrame] autorelease];
superview.backgroundColor = [UIColor clearColor];

aFrame = CGRectMake(0, 0, 50, 50); // relative to superview
// this is the view that will appear to rotate around (100, 100)
UIView *otherView = [[[UIView alloc] initWithFrame:aFrame] autorelease];
// give it a distinctive look
otherView.backgroundColor = [UIColor blueColor];
[superview addSubview:otherView];

// add superview to some view hierarchy, e.g. a view controller's view

// apply rotation to superview by method of your choice

Using a transparent superview in this way can be a convenient shortcut to achieving this effect. Keep in mind that the superview doesn't necessarily have to fit fully within the available screen space, so you can do tricks like rotating a view on or off screen. You should be aware that very large views can require a lot of memory; keep an eye on your memory usage in Instruments to be sure you aren't introducing a performance issue.

Change the Anchor Point

The other way of rotating around a point is to change the anchorPoint property of its root layer. The anchorPoint of a layer is the point around which rotations are applied, and it is also the point that the layer's position is considered relative to. Because of this dual role, any time you change the anchorPoint, the layer's on-screen location will update to make the position relative to the new anchorPoint value.

Another interesting feature of the anchorPoint property is that its coordinates are normalized to a range of zero to one, with zero representing one edge of the layer and one representing the opposite edge. This can make calculations somewhat tricky if you want to set the anchor point to a specific point on the screen, as you will have to convert the screen coordinates to the normalized coordinate system of the anchor point.

In order to compensate for the location shift that occurs when changing the anchor point, you can adjust the layer's position property. The position is specified in standard coordinates, so you don't have to do any conversions as you do with anchorPoint.

You can also specify an anchor point that falls outside of the zero-to-one coordinate range. Although the behavior is undocumented by Apple, in practice it seems to work well enough. Specifying negative values for either coordinate will place the anchor point outside the view to the left or top, depending on the axis. Positive values greater than 1 will place the anchor point outside to the right or bottom.

The ViewRotationTest Sample Application

You can download a sample application that demonstrates each of the techniques described in this article. It allows you to rotate either a view or its superview, and you can change the view's layer's anchor point to another value. There is a switch control that toggles automatic correction of the layer's position.

To see the position correction code, look at the correctLayerPosition method in ViewRotationTestViewController.m. Note that the calculation depends on the current value of anchorPoint.

Become an InformIT Member

Take advantage of special member promotions, everyday discounts, quick access to saved content, and more! Join Today.