Upcoming and OnDemand Webinars View full list

Rectangles, Part 1

Mark Dalrymple

Geometry is everywhere in modern programming. We have to know how to deal with points, sizes, and rectangles. Back in the old days we’d use NSPoint, NSSize, and NSRect. These types were bitwise-identical with their Core Graphics counterparts (CGPoint, CGSize, and CGRect), but weren’t type miscible, so you had to play games to use use one where another was wanted. With modern flavors of Cocoa, we can use them interchangeably. If you’re looking at a codebase that has both NSBlah and CGBlah for basic geometrical types, rest assured they’re the same at the bit-level.

These structs are pretty straightforward. A point is an x,y location somewhere in space:

CGPoint {
  CGFloat x;
  CGFloat y;
};

A size is a width and height, unconnected to any point in space:

CGSize {
  CGFloat width;
  CGFloat height;
};

And a rectangle is an origin point and a width/height:

struct CGRect {
  CGPoint origin;
  CGSize size;
};

Along with these basic geometrical structures (why are they structs and not objects? check out NS-structs), there’s a bunch of handy utilities available to do some basic operations. If you find yourself tempted to Just Do The Math, consider using one of these functions to make the code a little easier to read later on.

Making Stuff

NS/CGRect, Point, and Size are structures. You can initialize them like any other structure:

        CGSize size = { 10.0, 20.0 };
        CGPoint center = { 160.0, 240.0 };
        CGRect bounds = { { 5.0, 10.0 }, { 100.0, 200.0 } };

These just splat the numerical values into the corresponding place in the structure. Notice the CGRect sample – the first pair of values is the origin, and the second pair is the size. You can leave the braces out and things will work fine, but you might get a compiler warning (-Wmissing-braces) depending on your current warning level. You are fixing your warnings, right?

You do this to make a compile-time array of structures you can then walk through later, perhaps using a technique like table scanning

        CGRect bookLocations[] = {
            { {   0.0,  0.0 }, { 50.0, 70.0 } },
            { {  60.0,  3.0 }, { 50.0, 60.0 } },
            { { 120.0,  5.0 }, { 50.0, 50.0 } },
            { { 180.0, 13.0 }, { 50.0, 40.0 } }
        };

You can also use the convenience inline functions. Kind of wordy, but makes your intentions clear:

        CGSize size = CGSizeMake (10.0, 20.0);
        CGPoint center = CGPointMake (60.0, 240.0);
        CGRect bounds = CGRectMake (5.0, 10.0, 100.0, 200.0);

There’s predefined constants for zero-versions of these types: CGRectZero, CGPointZero, and CGSizeZero.

Caveman Support

If you’re into caveman debugging (logging messages to the console), you can easily turn a rectangle into a string, with NSStringFromRect (in Cocoa) or NSStringFromCGRect (in CocoaTouch):

        CGRect rect = CGRectMake (10.5, 40.75, 300.5, 5);
        NSLog (@"%@", NSStringFromRect(rect));

Prints out

        /{{/10.5, 40.75}, {300.5, 5 /}}/

There’s corresponding versions for Points, Sizes, and other structy-types. These are handy when you’re wanting to log one of these structure.

You can also convert a string in that format into a rectangle:

        NSString *stringForm = @"/{{/ 1.0, 2.0 }, { 3.0, 4.0 /}}/";
        CGRect rect2 = NSRectFromString (stringForm);

Rectangle Extents

There’s a number of different ways you can represent a rectangle in numbers. Some libraries, like the original Mac Quickdraw, use four values: left, top, right, and bottom. To find the width and height you do some math. As you saw, Cocoa and Core Graphics use an origin point plus a size for the width and height:

Point origin

If you want the edges, you do some math.

Width and height don’t have to be positive, so you can have a rectangle with a negative width or a negative height. This can make some of your rectangle calculations easier, especially if the user is dragging out a rectangle: have the rectangle origin be the initial tap/click point, and set the width and height as the current delta from that point. All of Apple’s code is set up to handle negative widths and heights, but your code or other library code might not be so savvy. In that case, you can standardize a rectangle so that it has positive extents by using CGRectStandardize:

        CGRect weirdRect = { 100.0, 100.0, -30.0, -50.0 };
        CGRect standardRect = CGRectStandardize (weirdRect);
        NSLog (@"standardizing %@ -> %@",
               NSStringFromRect(weirdRect),
               NSStringFromRect(standardRect));

this prints out

standardizing /{{/100, 100}, {-30, -50/}}/ -> /{{/70, 50}, {30, 50/}}/

You can see the origin point has pushed back so that the width and height could become positive.

Remember “if you want the edges, you can do some math”? Core Graphics has some utilities for this, too:

CGFloat CGRectGetMinX(CGRect rect);
CGFloat CGRectGetMaxX(CGRect rect);
CGFloat CGRectGetMinY(CGRect rect);
CGFloat CGRectGetMaxY(CGRect rect);

These automatically standardize the rectangle if needed. Both weirdRect and standardRect yield the same min/max values:

        NSLog (@"weird X's (%f %f) and standard X's (%f %f)",
               CGRectGetMinX(weirdRect), CGRectGetMaxX(weirdRect),
               CGRectGetMinX(standardRect), CGRectGetMaxX(standardRect));

which prints

weird X's (70.000000 100.000000) and standard X's (70.000000 100.000000)

Finding the midpoint is easy too:

CGFloat CGRectGetMidX(CGRect rect);
CGFloat CGRectGetMidY(CGRect rect);

With these you can easily find the middle of the rectangle.

If you want to get a rectangle’s size without having to standardize it yourself, there’s functions for that too:

CGFloat CGRectGetWidth(CGRect rect);
CGFloat CGRectGetHeight(CGRect rect);

You can also turn any fractional values in the rectangle into integral values by using CGRectIntegral.

Special Cases

There’s two special rectangles, the null and the infinite rectangle. There are constants for these:

const CGRect CGRectInfinite;
const CGRect CGRectNull;

The infinite rect is a really really huge rectangle, going from an extremely negative origin point and having totally gigantic extents. Feel free to log one out to see its content. This is used for describing things that don’t really have defined bounds, such as the core image tiling filter. It’ll happily tile the entire universe if given half a chance.

The null rect is used to represent rectangles that don’t make sense. It’s represented internally with floating-point infinities for the origin and zero extents. Uses for a null rectangle? Say you intersect two rectangles that don’t overlap. The intersection is the empty set, and so you’d get CGRectNull back for that. The null rectangle behaves nicer than the SQL NULL, so a null rect can be compared to itself and return a true value.

You can ask a rectangle if it is empty (zero width or height) by using CGRectIsEmpty, or if it’s infinite or null with CGRectIsNull or CGRectIsInfinite.

Comparisons

Speaking of comparisons, there are functions for seeing if two points, sizes, or rectangles are the same. You’ll want to use these rather than comparing elements piecewise. The functions less typing, plus recall that these data structures are based on CGFloat, which is as the name implies, a floating point value. You shouldn’t compare two floating point values for equality directly, due to potential round-off errors, but instead see if their difference is small enough to be considered insignificant (sometimes called epsilon). That’s annoying, so I just use the conveniences instead:

bool CGPointEqualToPoint (CGPoint point1, CGPoint point2);
bool CGSizeEqualToSize (CGSize size1, CGSize size2);
bool CGRectEqualToRect (CGRect rect1, CGRect rect2);

These return zero if the given structures are not equal, and return one if they do. 1 happens to be equal to YES in Objective-C, which is convenient. As always with BOOL values in Objective-C, be wary of its sharp corners.

Coming Up

That’s the basics of what some of the available functions are for making and interrogating rectangles. Next time we’ll look at some of the interesting manipulations you can do with them.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project