Ipphones

  • Subscribe to our RSS feed.
  • Twitter
  • StumbleUpon
  • Reddit
  • Facebook
  • Digg

Tuesday, 28 October 2008

Demystifying CGAffineTransform

Posted on 09:58 by Unknown
One of the most common questions I get about iPhone programming is about how to rotate or scale or make various other changes to a view. I'm not talking about hardcore Core Animation kind of stuff, just simple, "How do I place a label at 45°" kind of questions. The expectation is that there should be a property called "angle" or "scale" that can be set, or that you should be able to hold down the option key in Interface Builder to rotate it there.

With Leopard, Apple gave us Core Animation,and it is very cool, but in order to achieve all that coolness, they had to add some concepts that make it not always intuitive. (BTW: If you do want to know more about Core Animation, I've been reading Bill Dudney's Core Animation for Mac OS X and the iPhone and it's very good).

Any change to a view's position, scale, or rotation can now be stored in a property of the view called transform. It's a CGAffineTransform struct, and it's a bit cryptic if you've never worked with transformation matrices before.

A transformation matrix nothing more than a two-dimensional array of numbers. Okay, perhaps I shouldn't say "just", as transformation matrices are quite powerful. They can store complex changes to the position and shape of an object, including rotating, scaling, moving, and skewing (or shearing) the view as it's drawn. This adds a little bit of programmer complexity if you want to do anything more than the absolute basics, but it opens up a world of possibilities.

The CGAffineTransform Data Structure


So, what does this data structure look like? This:


struct CGAffineTransform {
CGFloat a;
CGFloat b;
CGFloat c;
CGFloat d;
CGFloat tx;
CGFloat ty;
};

yeah, it doesn't look like a matrix, does it? It is; it'sit is a 3x3 matrix, it's just that certain values in the matrix are constant, they can't change, so they're not represented by a variable. Here's what the matrix would look like drawn out in human-friendly form:

| a b 0 |
| c d 0 |
| tx ty 1 |


These 9 numbers are used to store the rotation, scale, and position of an object using something called Matrix Multiplication.
Note: By the way, this exact same process is used to transform shapes in three-dimensional graphics. The matrices used for 3D are 4X4 instead of 3x3 to capture the additional z dimension.


So, if a view has a transform, how does Core Graphics figure out how and where to draw it?

Basically, it figures out where it would draw each point (x,y) without the transformation, and then does the following math to figure out the new, transformed point:
new x position = old x position * a + old y position * c + tx
new y position = old x position*b + old y position * d + ty

And that works? Yep, it does, amazingly enough. If you're interested in the math behind it all, there are many good sources. Just google "Matrix Transformation" to find some. If you're going to be doing complex transformations, it's a good idea to have a basic understanding of the underlying maths, but for basic usage, you can get away without it.

The Basic Transformations


The reason you can get away without understanding the intricacies of the math is because Apple has provided us with a number of functions to retrieve standard matrices and to standard transformations. In order to use any of these transformations, you will need to include the CoreGraphics framework in your project, and include the CoreGraphics header file:
#import <CoreGraphics/CoreGraphics.h>


The Identity Transformation


All views (and layers, but we're not talking about layers today) start out with their transform property set to the Identity Matrix. This matrix represents the object without any changes. It hasn't been rotated, scaled, sheared, or translated (moved). For a view that has the identity matrix for the transform property will be drawn based solely on the size and origin in the bounds property.

Note: Views have both a frame (coordinates in superview's coordinate system) and bounds (coordinates in own coordinate system) property, but if you transform a view, you should not use or rely on the frame property anymore. If you are using transformations, work with the bounds property only, not the frame property, as transformations are applied to the bounds, but aren't necessarily reflected accurately in frame


Any time you want to reset a view or layer to its original, untransformed state, you simply set its transform to the Identity Transformation using the constant value CGAffineTransformIdentity like so:
theView.transform = CGAffineTransformIdentity;


The Translate Transformation


Translation is just a fancy way of saying "moving". You can, of course, accomplish a move by changing the origin value of the view's frame property (which moves the view in relation to its superview), but since we can't use the frame property along with other transformations, the translate transformation is an important one. If you want to translate a view, you use a Core Graphics function called CGAffineTransformTranslate(). This method takes three paramters. The first is an existing CGAffineTransform value that the translation will be applied to. To translate a view from its current position, you would pass the view's transform property here. To translate the view from its original position, you would pass in CGAffineTransformIdentity. Here is an example that would move the view five points to the right and ten points down.
theView.transform = CGAffineTransformTranslate(theView.transform, 5.0, 10.0);

Note: "points" or "units" generally mean pixels, but as we're moving to resolution independence and starting to support the third dimension, it's no longer correct to say it's always and forever true that one point is one pixel. But, it usually is


Matrix multiplication is cumulative, so if you translate by five pixels, then translate again by five pixels in the same direction, you get a translation of ten pixels, assuming that there were no other transformations in between the two calls.

The Rotation Transformation


The next most common translation, and the first one we're discussing that can't be done without transformations, is rotation, which is handled by the function called CGAffineTransormRotate(). This function takes two parameters, the first being the existing transformation matrix, and the second being the angle of rotation expressed in radians.

Radians? Who the heck thinks in radians? There's a convenience conversion macro in Core Graphics for Mac OS X called degreesToRadian(), but right now, it's inexplicably absent from Core Graphics for iPhone. It's an easy enough conversion, though - just add this line of code to your header file:
#define degreesToRadians(x) (M_PI * x / 180.0)

then you can rotate a view like this:
theView.transform = CGAffineTransformRotate(theView.transform, degreesToRadians(45));


The Scale Transformation


The final of the basic transformation is the scale transformation, which allows you to resize your view without touching its bounds property. To scale a view to double its original size, we use CGAffineTransformScale(), like so:
theView.transform = CGAffineTransformScale(theView.transform, 2.0, 2.0);


Note: If you are transforming the Identity Matrix, you can use the "Make" version of these functions which do not take a CGAffineTransform as a parameter, and just assume the Identity Matrix. So, for example, calling:
theView.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, 2.0, 2.0);

You could call
theView.transform = CGAffineTransformMakeTranslation(2.0, 2.0);

Those two lines of code are functionally identical.


Some Things to Be Aware Of


Here are a few things that you should make note of as you start working with transformations.

The Center of the World


When you are scaling or rotating, the object will get scaled or rotated from the center of the object. In Core Animation, you can set an Anchor Point that will change that behavior, but if you are not using Core Animation and don't want to deal with layers, then in order to scale or rotate from, for example, you would have to also have to manually do translation transformations to reposition the view after the scale or rotation. Which brings another thing to mind:

Order Matters


When you apply success transformations, the order matter. Rotating and then translating will give you a different result then translating and then rotating. This can bite you if you're not careful.

Stepping Back


As I said earlier, you can always get back to the starting point by setting the view's transform to the Identity Matrix. But, what if you just want to back out one transformation. Say, you rotated, then scaled, then translated, and you just want to "untranslate" it. This can be useful if, for example, you want to do complex animations, such as having a view move one way, and then return to its original position. There is another transformation to look at called the Inverse transformation. The inverse of a transformation is the translation that will negate that translation when applied after it. You can get the inverse transformation for any CGAffineTransform by using the function CGAffineTransformInvert(), like so:
CGAffineTransform inverse = CGAffineTransformInvert(CGAffineTransformMakeTranslation(5.0, 5.0));

The value of inverse in the code above is the same as creating a translation of (-5.0, -5.0), but you don't have to know what the transformation is to get the inverse using this method.

Conclusion


Okay, we've only scratched the surface of what is a very complex part of the iPhone, but I hope this helps make the concept of "transforms" and "transformations" more approachable and will help some people make more sense of the documentation. As always, if you have questions, feel free to ping me via e-mail or IM, or send a tweet my way. I use the same username every where, which is jeff underscore lamarche, except for gmail, where I'm jeff dot lamarche.

Here's a sample Xcode project that shows how to use some transformations on views created in Interface Builder.
Email ThisBlogThis!Share to XShare to Facebook
Posted in CGAffineTransform, Cocoa Touch, iPhone SDK | No comments
Newer Post Older Post Home

0 comments:

Post a Comment

Subscribe to: Post Comments (Atom)

Popular Posts

  • Making OpenGL ES Screenshot
    The Bit-101 Blog has an entry that shows how to take a screenshot when using OpenGL ES . I tested this in my much-delayed particle-generato...
  • Adding CLANG to Your Build Process
    Frasier Spiers has a nifty piece this morning on using Git pre-commit hooks to automatically run the CLANG Static Analyzer. I'm not a G...
  • CLANG Static Analyzer
    If you aren't using the LLVM/Clang Static Analyzer , you really should be. The Clang Project is an attempt to write a front end for the...
  • A Little Help
    I'm having a problem with OpenGL ES, and it's keeping me from finishing my particle engine post. I was hoping someone here could see...
  • WWDC Accommodations
    Staying downtown in San Francisco is very expensive in the summertime. Bu, if you're going to WWDC, you really want to stay downtown. Yo...
  • Xcode File Templates and a Mystery
    One of the things that confuses many newcomers to Xcode is how to set it up so that your company name gets automatically filled in when you ...
  • Brain Surgery?
    Craig Hockenberry has an interesting post on his blog today about the iPhone background processing issue. Craig speaks from personal experi...
  • Book's Almost Done
    I just finished Chapter 16. I'll give it another read-over in the morning then it will go off to my writing partner for his review, then...
  • iPhone Alley
    Looks like Dave and I are going to make an appearance on the iPhone Alley Podcast next week. We're recording on Sunday night, so I woul...
  • Shuffling Arrays
    Ever want to randomize an array of items? It's a task that, for some reason, I've had to do a lot in recent programs. So, I wrote a ...

Categories

  • 3D Models
  • Ad Hoc Distribution
  • ADC
  • Address Book
  • Amazon
  • Anaglyphs
  • App Store
  • Apple
  • Apple DTS
  • Apple Store
  • Application Store
  • articles
  • Award
  • Background Processing
  • Barcodes
  • Beta
  • Blog
  • Blogger
  • Blogging
  • Blogs
  • Blogspot
  • Book project
  • Bug Reporting
  • Captain Obvious
  • Categories
  • Censorship
  • CFFoundation
  • CGAffineTransform
  • Clang Static Analyzer
  • Cocoa
  • Cocoa Touch
  • Code Reuse
  • Code Signing
  • Computer
  • conferences
  • Controller Classes
  • Core Animation
  • Daring Fireball
  • Database
  • Debugging
  • Defect
  • Delegates
  • Design Awards
  • Developer Certifications
  • Discussion Forums
  • Edit Mode
  • employment opportunities
  • Encryption
  • Enterprise
  • Errata
  • free code
  • Free software
  • Full Screen
  • Game Programming
  • Gestures
  • Getting Started
  • goof
  • Google Code
  • Google Maps
  • Gotcha
  • Help
  • HIG
  • HTTP PUT
  • Idiots
  • Idle Timer
  • Images
  • Instruments
  • Interface Builder
  • iPHone
  • iPhone Applications
  • iPhone Dev Center
  • iPhone Developers
  • iPhone OS 3.0
  • iPhone SDK
  • iPhone SDK PNG
  • iPhone Simulator
  • iPhoneSDK
  • iPod
  • Job Opportunities.
  • k
  • Key Value Observing
  • Keynote
  • KVO
  • Landscape Mode
  • Learn Cocoa
  • Learn Cocoa on the Mac
  • libxml
  • Licensing
  • Mac Developers
  • Mac OS X
  • Macworld Expo
  • Microsoft
  • NDA
  • NeHe
  • New Category
  • New Release
  • NSFileHandle
  • NSMutableArray
  • NSMutableURLRequest
  • NSXML
  • Object-Oriented Design
  • Objective-C
  • Open Source
  • OpenGL ES
  • Optimizations
  • Other blogs
  • Paired Arrays
  • Parsing
  • Particle Engine
  • Party
  • PeopleSoft
  • Performance
  • Persistence
  • Pink Screen of Death
  • Piracy
  • Pixar
  • Podcasts
  • Press Release WTF
  • Press Releases WTF
  • private APIs Google
  • Project Template
  • Properties
  • Random Numbers
  • Rant
  • Rejected
  • Resources
  • Responder Chain
  • REST
  • Reverse Engineering
  • Rumors
  • Runtime
  • Sample Code
  • Screencast
  • screenshot
  • Scroll Views
  • snippet
  • Snow Leopard.
  • SOAP
  • Sockets
  • Source
  • Splash Screen
  • SQLite
  • SQLitePersistentObjects
  • Steve Jobs
  • Steve-Note
  • Strings
  • Stupidity
  • Subversion
  • Table Views
  • Taps
  • Template
  • Tip
  • Tips
  • Tririga
  • tutorials
  • Twitter
  • UIAlertView
  • UIColor
  • UIImage
  • UIPickerView
  • UIScrollView
  • UITextField
  • UIView
  • UIWebView
  • Update
  • Utilities
  • UUID
  • Vacation
  • Version Control
  • Web Services
  • Writing
  • WTF
  • WWDC
  • Xcode
  • XML

Blog Archive

  • ►  2009 (141)
    • ►  May (14)
    • ►  April (30)
    • ►  March (48)
    • ►  February (26)
    • ►  January (23)
  • ▼  2008 (163)
    • ►  December (46)
    • ►  November (25)
    • ▼  October (44)
      • Elvis has Left the Building
      • Updated SQLitePersistence
      • Some More Open Source
      • Speaking of Wil Shipley
      • 500%
      • Better Template
      • CGAffineTransform 1.1 - A little more
      • Demystifying CGAffineTransform
      • I'm Flattered, I Think...
      • WSDL2ObjC
      • Table View Multi-Row Edit Mode
      • Captain Obvious Strikes Again
      • It's Beta, but it Shows they are listening...
      • SOAP Web Services Redux
      • Crimson FX Open Sourced
      • Another Open Source Project
      • Scroll Views
      • Cocoa Barcodes
      • Old is New
      • You Shouldn't, but You Will...
      • Getting the contents of a UIView as a UIImage
      • Shuffling Arrays
      • Encodings Can Byte You
      • Idle Timer
      • Starting in Landscape Mode without Status Bar
      • SQLite Persistent Object Update
      • Device vs. Simulator
      • Another Couple of Tips by Captain Obvious
      • Simulator
      • iPhone "Optimized" PNGs
      • Random Thoughts: rand() vs. arc4random()
      • Twitter
      • Another ADC Article Online
      • Completely Off-Topic
      • A Couple More Math Snippets for 2D Graphics
      • Circles, Ellipses, and Regular Polygons in OpenGL ES
      • Radar URLs & Bug Reporting
      • A Little Color in your Life
      • I Really Like the Layout
      • Did you know you could take screenshots without Xc...
      • A Bit About the Responder Chain
      • Handling Double Taps
      • Ego Boost
      • Holy %@#$!
    • ►  September (2)
    • ►  August (5)
    • ►  July (2)
    • ►  June (9)
    • ►  May (2)
    • ►  April (11)
    • ►  March (17)
Powered by Blogger.

About Me

Unknown
View my complete profile