CocoaConf Raleigh Recap: Matrix Transformations

by Christopher Mann

Introduction

Animation. Such a powerful word when used in the context of UI programming. The tools provided by the Cocoa Touch frameworks allow developers to add dynamic, fluid animations to their app, often with only a few lines of code. Matrix transformations serve as one such tool, allowing you to scale, translate, and rotate views in any way imaginable (provided you know the math to make it possible). These transformations can be used to add that needed polish to the UI of your app by animating view transitions or providing feedback to user interaction.

I recently attended a technical session at CocoaConf in Raleigh, NC, presented by Mark Pospesel, titled "Enter The Matrix: Reloaded". In this session, Mark demonstrated the application of various 2D and 3D matrix transformations and ways they could be combined to create complex animations. In this post I will examine some of these, focusing mainly on Core Animation.

iOS provides functions for creating matrix transforms in both 2D and 3D spaces using Core Graphics and Core Animation respectively. Using Core Graphics, views can be transformed either by modifying the Current Transformation Matrix (CTM) using drawRect or by CGAffineTransform’s, which provide a set of functions for manipulating matrices in the x, y plane. Almost entirely contained in the Quartz Framework, Core Animation provides a similar set of functions that can be applied to a view’s sublayer(s) via the CALayer (Core Animation Layer) class to help ease the pain of creating complex, 3D animations. While simple transformations can be quite easy, the task of applying multiple transformations simultaneously or in sequence can be challenging.

CGAffineTransform

If only a 2D transformation is required and you’re not concerned with animating other properties of a view’s CALayer, identifying the solution is generally as simple as answering one question: Does your view require use ofdrawRect? If drawing is required then the CTM transform functions provided by the Core Graphics framework are well suited to the task (See the Transforms section of the Quartz 2D Programming Guide). If not, then theCGAffineTransform is likely your weapon of choice.

Once your view layout has been defined, applying a simple 2D transformation is as easy as it gets. For example, if you want one view to slide off the screen in response to a tap or gesture, it only takes one line of code as shown below.

currentView.transform = CGAffineTransformTranslate(currentView.transform,
                                           -currentView.frame.size.width,
                                                                      0);

The transform function above accepts three parameters: the current state or Identity Transform of the view being transformed, an x translation value, and a y translation value. Of course if this is all you’re trying to accomplish there are even simpler ways to perform the task (i.e. adjusting the view’s frame). Let’s instead consider that you’d like to rotate the view 180 degrees along the both the x- and y-axes.

currentView.transform = CGAffineTransformRotate(currentView.transform,
                                              DEGREES_TO_RADIANS(180), 
                                              DEGREES_TO_RADIANS(180));

This effectively inverts the view both horizontally and vertically. As all matrix functions measure rotation in radians and not degrees, I find it easier to use a helper function to handle the conversion, which can be obtained with a simple equation: radians = degrees × π / 180

While CGAffineTransform can be used to perform complex matrix transformations to views in 2D space, Core Animation provides powerful tools for manipulating 3D matrices as well as simply animating view properties by exposing a view’s underlying CALayer.

CATransform3D

One important factor in producing realistic 3D animations using CATransform3D functions is the effect created by changing the m34 coefficient of the CATransform3D matrix structure. This property can be set and changed to affect the skew factor of a given transformation to alter the perspective.

CATransform3D rotationTransform = currentView.layer.transform;
currentTransform.m34 = 1.0 / -zDistance;

For a "normal" effect, this value should be proportional to the size of the view being transformed, with a greater skew effect the larger the zDistance value. While this modification is optional, using the default value may leave your animation looking somewhat flat. All values of the CATransform3D data structure can be modified directly in a similar fashion to create a completely custom transformation matrix.

To draw comparison to CGAffineTransform's let’s start by recreating our last rotation example, this time using Core Animation to rotate about the z-axis which we weren’t able to do using Quartz 2D. For completeness we’ll place the transform inside an animation block.

// define 180 degree rotation across z axis
CATransform3D rotationTransform = 
     CATransform3DRotate(currentView.layer.transform,
                  (CGFloat) DEGREES_TO_RADIANS(-180),
                                                0.0f,
                                                0.0f,
                                               -1.0f);
 
// define perspective proportional to width of frame
rotationTransform.m34 = 1.0 / -currentView.frame.size.width;
 
// animate the transformation
[UIView animateWithDuration:1.0 animations:^{
 
     // apply transformation
     currentView.layer.transform = rotationTransform;
 
} completion:NULL];

Simple enough but what if your app requires multiple transformations at once? Core Animation provides a function suited just for this purpose: CATransform3DConcat.

CATransform3DConcat

To perform multiple transformations at once, each transform must first be defined and then combined using the CATransform3DConcat function. CATransform3DConcat performs matrix multiplication on two matrices and because this operation is not commutative, order is important.

The following code snippet applies rotation along the z-axis combined with translation on the x- and y-axes. The resulting effect is the current view will appear to spin off the screen towards the window’s origin.

// define visible page transform
CATransform3D rotationTransform = currentView.layer.transform;
 
// define perspective proportional to width of frame
rotationTransform.m34 = 1.0 / - currentView.frame.size.width;
 
// define 180 degree rotation along z axis
rotationTransform = CATransform3DRotate(rotationTransform,
                       (CGFloat) DEGREES_TO_RADIANS(-180), 
                                                     0.0f,
                                                     0.0f,
                                                    -1.0f);
 
// define translation of current view
CATransform3D translateTransform = 
     CATransform3DTranslate(currentView.layer.transform,
                          -currentView.frame.size.width,
                         -currentView.frame.size.height,
                                                      0);
 
translateTransform.m34 = 1.0 / - currentView.frame.size.width;
 
// animate the transformation
[UIView animateWithDuration:1.0 animations:^{
 
     // apply transformations and fade out current view
     currentView.alpha = 0.0;
     currentView.layer.transform = 
          CATransform3DConcat(rotationTransform,translateTransform);
 
} completion:NULL];

Embedded Animations

Another approach for creating complex animations would be to enclose a second transformation within the completion block of the first transformation. This allows you additional freedom to customize the timing of each transformation. The following code snippet demonstrates multiple transforms applied to two views using embedded animation blocks.

-(void)animationViewTransitions {
 
     // define visible page transform
     CATransform3D rotationTransform = currentView.layer.transform;
 
     // define perspective proportional to width of frame
     rotationTransform.m34 = 1.0 / -self.view.frame.size.width;
     // define 90 degree rotation
     rotationTransform = CATransform3DRotate(rotationTransform,
                             (CGFloat) DEGREES_TO_RADIANS(-15),
                                                          0.0f,
                                                          0.0f);
 
     // scale and center next frame
     nextView.frame = CGRectMake(self.view.frame.size.width,
                                self.view.frame.size.height,
                           self.view.frame.size.width * .25,
                          self.view.frame.size.height * .25);
 
     nextView.center = self.view.center;
     nextView.alpha = 0;
 
     [self.view addSubview:nextView];
 
     // define scale transformation of next view
     CATransform3D scaleTransform = 
          CATransform3DScale(nextView.layer.transform, 1/.25, 1/.25, 0);
 
     // animate transformations
     [UIView animateWithDuration:1.0 animations:^{
 
          // apply transformations and fade out current view
          currentView.alpha = 0.9;
          currentView.layer.transform = rotationTransform;
 
     } completion:^(BOOL finished) {
 
          // define translation of current view
          CATransform3D translateTransform = 
               CATransform3DTranslate(currentView.layer.transform,
                                      -self.view.frame.size.width,
                                                                0,
                                                               50);
 
          [UIView animateWithDuration:1.0 animations:^{
 
               // apply translate transform and fade out current view
               currentView.alpha = 0.0;
               currentView.layer.transform = translateTransform;
 
               // apply scale transform and fade in next view
               nextView.layer.transform = scaleTransform;
 
               nextView.alpha = 1.0;
               nextView.center = self.view.center;
 
          } completion:NULL];
 
     }];
}

Conclusion

iOS and Cocoa Touch provide developers with a vast array of tools to create custom animations. Using matrix transforms to manipulate views and their sublayers is just one of these tools. While the notion of matrix transformations may seem scary at first it may just end up being the missing piece to add a professional look to your app. For more information on Core Animation and CATransform3D, see Apple’s Core Animation Programming Guide. For a simple guide to matrix transformations see http://wally.cs.iupui.edu/n351/3D/matrix.html or reference the "Math Behind the Matrices" section of the Quartz 2D Programming Guide. If you have questions or comments, contact me at cmann@captechventures.com.