Working with CGPoint
, CGSize
, and CGRect
structures isn't difficult if you're used to a language that supports the dot syntax. However, programmatically positioning views or writing drawing code is verbose and can become difficult to read.
In this tutorial, I'd like to clear out a few misconceptions about frames and bounds, and introduce you to CGGeometry, a collection of structures, constants, and functions that make working with CGPoint
, CGSize
, and CGRect
that much easier.
1. Data Types
If you're new to iOS or OS X development, you may be wondering what CGPoint
, CGSize
, and CGRect
structures are. The CGGeometry Reference defines a number of geometric primitives or structures and the ones we are interested in are CGPoint
, CGSize
, and CGRect
.
As most of you probably know, CGPoint
is a C structure that defines a point in a coordinate system. The origin of this coordinate system is at the top left on iOS and at the bottom left on OS X. In other words, the orientation of its vertical axis differs on iOS and OS X.
CGSize
is another simple C structure that defines a width and a height value, and CGRect
has an origin
field, a CGPoint
, and a size
field, a CGSize
. Together the origin
and size
fields define the position and size of a rectangle.
The CGGeometry Reference also defines other types, such as CGFloat
and CGVector
. CGFloat
is nothing more than a typedef
for float
or double
, depending on the architecture the application runs on, 32-bit or 64-bit.
2. Frames and Bounds
The first thing I want to clarify is the difference between a view's frame
and its bounds
, because this is something that trips up a lot of beginning iOS developers. The difference isn't difficult though.
On iOS and OS X, an application has multiple coordinate systems. On iOS, for example, the application's window is positioned in the screen's coordinate system and every subview of the window is positioned in the window's coordinate system. In other words, the subviews of a view are always positioned in the view's coordinate system.
Frame
As the documentation clarifies, the frame
of a view is a structure, a CGRect
, that defines the size of the view and its position in the view's superview, the superview's coordinate system. Take a look at the following diagram for clarification.
Bounds
The bounds
property of a view defines the size of the view and its position in the view's own coordinate system. This means that in most cases the origin of the bounds of a view are set to {0,0}
as shown in the following diagram. The view's bounds
is important for drawing the view.
When the frame
property of a view is modified, the view's center
and/or bounds
are also modified.
3. CGGeometry Reference
Convenient Getters
As I mentioned earlier, the CGGeometry Reference is a collection of structures, constants, and functions that make it easier to work with coordinates and rectangles. You may have run into code snippets similar to this:
CGPoint point = CGPointMake(self.view.frame.origin.x + self.view.frame.size.width, self.view.frame.origin.y + self.view.frame.size.height);
Not only is this snippet hard to read, it's also quite verbose. We can rewrite this code snippet using two convenient functions defined in the CGGeometry Reference.
CGRect frame = self.view.frame; CGPoint point = CGPointMake(CGRectGetMaxX(frame), CGRectGetMaxY(frame));
To simplify the above code snippet, we store the view's frame
in a variable named frame
and use CGRectGetMaxX
and CGRectGetMaxY
. The names of the functions are self-explanatory.
The CGGeometry Reference defines functions to return the smallest and largest values for the x- and y-coordinates of a rectangle as well as the x- and y-coordinates that lie at the rectangle's center. Two other convenient getter functions are CGRectGetWidth
and CGRectGetHeight
.
Creating Structures
When it comes to creating CGPoint
, CGSize
, and CGRect
structures, most of us use CGPointMake
and its cousins. These functions are also defined in the CGGeometry Reference. Even though their implementation is surprisingly easy, they are incredibly useful and make you write less code. For example, this is how CGRectMake
is actually implemented:
CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height) { CGRect rect; rect.origin.x = x; rect.origin.y = y; rect.size.width = width; rect.size.height = height; return rect; }
Modifying Rectangles
The functions we've covered so far are pretty well known among iOS developers and help us write less code that is more readable. However, the CGGeometry Reference also defines a number of other functions that are less known. For example, the CGGeometry Reference defines half a dozen functions for modifying CGRect
structures. Let's take a look at a few of these functions.
CGRectUnion
CGRectUnion
accepts two CGRect
structures and returns the smallest possible rectangle that contains both rectangles. This may sound trivial and I'm sure you can easily accomplish the same task with a few lines of code, but CGGeometry is all about providing you with a few dozen functions that make your code cleaner and more readable.
If you add the following code snippet to a view controller's viewDidLoad
method, you should get the following result in the iOS Simulator. The gray rectangle is the result of using CGRectUnion
.
// CGRectUnion CGRect frame1 = CGRectMake(80.0, 100.0, 150.0, 240.0); CGRect frame2 = CGRectMake(140.0, 240.0, 120.0, 120.0); CGRect frame3 = CGRectUnion(frame1, frame2); UIView *view1 = [[UIView alloc] initWithFrame:frame1]; [view1 setBackgroundColor:[UIColor redColor]]; UIView *view2 = [[UIView alloc] initWithFrame:frame2]; [view2 setBackgroundColor:[UIColor orangeColor]]; UIView *view3 = [[UIView alloc] initWithFrame:frame3]; [view3 setBackgroundColor:[UIColor grayColor]]; [self.view addSubview:view3]; [self.view addSubview:view2]; [self.view addSubview:view1];
CGRectDivide
Another useful function is CGRectDivide
, which lets you divide a given rectangle into two rectangles. Take a look at the following code snippet and screenshot to see how it's used.
// CGRectDivide CGRect frame = CGRectMake(10.0, 50.0, 300.0, 300.0); CGRect part1; CGRect part2; CGRectDivide(frame, &part1, &part2, 100.0, CGRectMaxYEdge); UIView *view1 = [[UIView alloc] initWithFrame:frame]; [view1 setBackgroundColor:[UIColor grayColor]]; UIView *view2 = [[UIView alloc] initWithFrame:part1]; [view2 setBackgroundColor:[UIColor orangeColor]]; UIView *view3 = [[UIView alloc] initWithFrame:part2]; [view3 setBackgroundColor:[UIColor redColor]]; [self.view addSubview:view1]; [self.view addSubview:view2]; [self.view addSubview:view3];
If you were to calculate the red and orange rectangle without using CGRectDivide
, you'd end up with a few dozen lines of code. Give it a try if you don't believe me.
Comparison and Containment
Comparing geometric structure and checking for membership is very easy with the following six functions:
CGPointEqualToPoint
CGSizeEqualToSize
CGRectEqualToRect
CGRectIntersectsRect
CGRectContainsPoint
CGRectContainsRect
The CGGeometry Reference has a few other gems, such as CGPointCreateDictionaryRepresentation
for converting a CGPoint structure to a CFDictionaryRef
, and CGRectIsEmpty
to check if a rectangle's width and height are equal to 0.0
. Read the documentation of the CGGeometry Reference to find out more.
4. Bonus: Logging
Logging structures to Xcode's console is cumbersome without a few helper functions. Luckily, the UIKit framework defines a handful of functions that make this very easy to do. I use them all the time. Take a look at the following code snippet to see how they work. It's no rocket science.
CGPoint point = CGPointMake(10.0, 25.0); CGSize size = CGSizeMake(103.0, 223.0); CGRect frame = CGRectMake(point.x, point.y, size.width, size.height); NSLog(@"\n%@\n%@\n%@", NSStringFromCGPoint(point), NSStringFromCGSize(size), NSStringFromCGRect(frame));
There are also convenience functions for logging affine transforms (NSStringFromCGAffineTransform
), edge insets structs (NSStringFromUIEdgeInsets
), and offset structs (NSStringFromUIOffset
).
Conclusion
The iOS SDK contains a lot of gems many developers don't know about. I hope I've convinced you of the usefulness of the CGGeometry Reference. Once you start using its collection of functions, you'll start wondering how you used to manage without it.
Comments