Search

Rectangles, Part 2

Mark Dalrymple

7 min read

Nov 18, 2012

Mac

Rectangles, Part 2

Last time you met some Cocoa / CocoaTouch / Core Graphics rectangle utilities. Now it’s time for some more.

Contain Yourself

A common question is “does this rectangle contain this point?”. Has the user tapped on a photo? Has the user clicked inside the left-hand thumb of a range slider? The math is easy, but using CGRectContainsPoint is very straightforward. Here’s a rectangle, and its midpoint. As expected this will print out the “contains point!” line:

        CGRect container = CGRectMake (0.0, 0.0, 100.0, 200.0);
        CGPoint point = CGPointMake (50.0, 100.0);
        if (CGRectContainsPoint(container, point)) {
            NSLog (@"contains point!");
        }

You might have two rectangles that you want to see how they relate to each other. Does one rectangle complete enclose another? Imagine you’re dragging out a selection rectangle and you only want to include objects that are entirely within the section. You’d use CGRectContainsRect. Or perhaps you might want to include objects that are also partially contained in the selection. In that case you’d use CGRectIntersectsRect.

Consider these rectangles:

Rectangles selection

The rectangles might look like this:

    CGRect selection = /{{/   0.0,  0.0 }, { 200.0, 100.0 /}}/;
    CGRect thing1    = /{{/  10.0, 20.0 }, {  80.0,  80.0 /}}/;
    CGRect thing2    = /{{/ 180.0, 20.0 }, { 100.0,  40.0 /}}/;

If you wanted an enclosed-only selection, check for containment:

    if (CGRectContainsRect(selection, thing1)) NSLog (@"thing1 selected");
    if (CGRectContainsRect(selection, thing2)) NSLog (@"thing2 selected");

Which would only print out “thing1 selected”
Or if you wanted intersecting selection, check for that:

    if (CGRectIntersectsRect(selection, thing1)) NSLog (@"thing1 selected");
    if (CGRectIntersectsRect(selection, thing2)) NSLog (@"thing2 selected");

Which would print out “thing1 selected” and “thing2 selected”

CGRectIntersectsRect is useful for more than just selections. You can use it to see if you need to draw something. If you’re managing a bunch of objects being drawn in a view, call CGRectIntersectsRect with the visible rectangle as a very fast test to see if you should draw that object or not.

Union Wages

Two other common rectangle operations are union and intersections. The union of two rectangles is the rectangle that completely encloses them. The intersection of two rectangles is the rectangle that is common between the two:

Rectangles union

You can probably guess that the call that performs the rectangle union is called CGRectUnion, and the other call is CGRectIntersection:

    CGRect unionRect = CGRectUnion (thing1, thing2);
    CGRect intersection = CGRectIntersection (thing1, thing2);

What happens if thing1 and thing2 are disjoint? You’ll get the null rectangle. You can test for nullitude by using CGRectIsNull:

    if (CGRectIsNull(intersection)) {
        NSLog (@"empty intersection");
    }

Rectangle unions are useful for calculating the total extent of a bunch of rectangles. Have a bunch of views in a UIScrollView? You can iterate over the views, and union all their rectangles. When you’re done, the rectangle will have the minimum contentSize that will hold all of the views.

Moving Around

Rectangles are composed of floating point values, which means that they can be rooted at pretty much any value, no matter how many digits are after the decimal point. They can also have sizes that are fractions. Cocoa and CocoaTouch layout typically happens on whole point boundaries. If your rectangles aren’t whole numbers, you could have problems with pixelation. A classic iOS problem is a text field that has blurry text sometimes and crisp text other times. This can happen if you’re manually laying things out and use a fractional origin.

CGRectIntegral will convert a rectangle to an integral origin and size, such that it completely encloses the original rectangle.

Here’s a rectangle with fractional everything that’s then made integral:

    CGRect differential = CGRectMake (0.5, 0.2, 99.9, 105.5);
    CGRect integral = CGRectIntegral (differential);

The resulting rectangle will have members (0, 0, 101, 106).

You can also scoot a rectangle around by using CGRectOffset. It takes an x and y parameter (along with the rectangle), and adds x and y to the rectangle’s origin. The size isn’t affected. Here’s a rectangle to be moved, and the amount it’s going to move:

    CGRect original = CGRectMake (0.0, 50.0, 100.0, 200.0);
    CGRect shifted = CGRectOffset (original, 10.0, 20.0);

As expected, the rectangle members are (10, 70, 100, 200)

What would be a use for this? Say you have a diagramming application that lets the user move selected objects by using the arrow keys. You would spin through all of your selected objects and CGRectOffset them by the current grid size.

Centerville

Not all useful utilities are in Core Graphics or one of the Cocoa toolkits. Buried in the ScreenSaver framework is a useful utility called SSCenteredRectInRect that takes one rectangle, and centers a second rectangle relative to it. Rather than dragging in the screensaver framework for one function that’s defined in a header file, here it is for your convenience. I took off the SS prefix because it’s bad idea to use Apple’s prefixes for your own stuff:

static __inline__ NSRect CenteredRectInRect(NSRect innerRect, NSRect outerRect)
{
#if CGFLOAT_IS_DOUBLE
    innerRect.origin.x = outerRect.origin.x
        + floor((outerRect.size.width - innerRect.size.width) / (CGFloat) 2.0);
    innerRect.origin.y = outerRect.origin.y
        + floor((outerRect.size.height - innerRect.size.height) / (CGFloat) 2.0);
#else
    innerRect.origin.x = outerRect.origin.x
        + floorf((outerRect.size.width - innerRect.size.width) / (CGFloat) 2.0);
    innerRect.origin.y = outerRect.origin.y
        + floorf((outerRect.size.height - innerRect.size.height) / (CGFloat) 2.0);
#endif
    return innerRect;
}

outerRect is held constant, while innerRect is tweaked so that its center is the same as outerRect’s center. I also tweaked Apple’s version, which assumed that outerRect had an origin of (0, 0), so that it would work wherever outerRect happens to live. The two thin rectangles are the original rectangles:

Rectangles centered

The thicker rectangle comes from centering the larger one with respect to the smaller one.

Teeming with Insets

Sometimes you want to take a rectangle and make a larger or smaller version of it. CGRectInset will take a width and height value and pull in the sides of the rectangle by that amount. Here is a rectangle that’s 20 points high and 10 points wide:

    CGRect before = CGRectMake (0.0, 0.0, 20.0, 10.0);

And then it’s inset by 3 horizontally and 2 vertically:

    CGRect inset = CGRectInset (before, 3, 2);

The new rectangle has elements (3, 2, 14, 6), calculated by adding the new width and height to the origin, and subtracting two-times those values from the rectangle’s width and height. You can use negative numbers to make the rectangle larger.

What if you want to manipulate a rectangle, but don’t want it to happen uniformly? There isn’t a Core Graphics call for that, but CocoaTouch includes a struct called UIEdgeInsets:

typedef struct UIEdgeInsets {
    CGFloat top, left, bottom, right;
} UIEdgeInsets;

This specifies how far to adjust a rectangle’s edges. Positive values mean to pull an edge inward, and negative value means mean to push the edge outward. The call UIEdgeInsetsInsetRect will take a rectangle, and a structure of insets, and do the Right Thing. There is an NSEdgeInsets that’s the same, but there is no NSEdgeInsetsInsetRect, so this is iOS only.

Say you have a program where the user can position items around the screen. Selected items get some extra annotations put on them, like their x,y position, or perhaps how many bytes are left for a download. You can take the existing rectangle, and by using the edge insets, make a larger rectangle that’ll hold the new annotations.

The original object is at this rectangle:

    CGRect before = CGRectMake (100.0, 100.0, 200.0, 100.0);

New annotations will go to the left, and underneath:

    UIEdgeInsets insets = UIEdgeInsetsMake (0.0, -20.0, -30, 0.0);

So top = 0.0, left = -20.0, bottom= -30.0, and right = 0.0. Negative values mean to push out the rectangle. Then run the rectangle through the set of insets

    CGRect rectPlusAnnotations = UIEdgeInsetsInsetRect (before, insets);

And get a rectangle with elements (80, 100, 220, 130). The left edge has been pushed back by 20, and the width has been increased by 20 to compensate. Similarly for the height. It is now 30 taller than it was to start out with. Because the rectangle’s origin is upper-left, the y coordinate was not changed.

Here are the original, and new rectangles:

Rectangles insets

Continental Divide

The final stop on the tour of the rectangle kingdom is CGRectDivide. It has the most complicated signature of all of the functions seen so far:

void CGRectDivide (CGRect inRect, CGRect *outSlice, CGRect *outRemainder,
                   CGFloat amount, CGRectEdge edge);

This function will take a rectangle, and some additional bits of data, and will divide the rectangle into two parts, returning each part through the rectangle pointers.

amount is how much space to slice off. edge is where it should take off that space. Pass one of the constants CGRectMinXEdge, CGRectMinYEdge, CGRectMaxXEdge, or CGRectMaxYEdge.

Here’s a rectangle, along with slicing off the minimum X edge:

    CGRect startingRect = CGRectMake (37.0, 42.0, 300.0, 100.0);
    CGRect slice, remainder;
    CGRectDivide (startingRect, &slice, &remainder, 100.0, CGRectMinXEdge);

The orange portion to the left is the slice, and the brown portion to the right is the remainder:

Rectangles slice

The slice has elements (37, 42, 100, 100). You can see the slice width being 100.

The remainder has elements (137, 42, 200, 100). You can see it was shifted over by 100 points, and is 100 smaller.

The slice won’t be any bigger than the original rectangle. Attempting a negative slice will return an empty (zero for one of the sizes) rectangle.

What would you use this for? Recall the annotated rectangle above created by using UIEdgeInsets. You could then use CGRectDivide to get the rectangles for the annotation area.

If you are doing manual layout of objects in a window, you could divide a rectangle at the top for a header area. Use the remainder for the content area. Then you could divide the content area on either side for UI chrome such as info panels, leaving the interior portion for actual content. The steps in dividing the rect would look like this:

Rectangles inset2

And the code to get all of these rectangles would look like this:

    CGRect startingRect = CGRectMake (50.00, 50.0, 100.0, 100.0);
    CGRect remainder;
    CGRect headerRect;
    CGRectDivide (startingRect, &headerRect, &remainder, 20.0, CGRectMinYEdge);
    CGRect footerRect;
    CGRectDivide (remainder, &footerRect, &remainder, 10.0, CGRectMaxYEdge);
    CGRect leftPanelRect;
    CGRectDivide (remainder, &leftPanelRect, &remainder, 15.0, CGRectMinXEdge);
    CGRect rightPanelRect;
    CGRectDivide (remainder, &rightPanelRect, &remainder, 30, CGRectMaxXEdge);
    CGRect contentArea = remainder;

Now you have six very useful rectangles for the rest of your code to use, without having to touch the guts of the rectangles.

That’s It

As you can see, there are a lot of convenience functions provided by Core Graphics, and some provided by Cocoa or CocoaTouch. If you find yourself drawn to mucking around with rectangle origins and sizes directly, there might already be a function available that will do that.

Mark Dalrymple

Author Big Nerd Ranch

MarkD is a long-time Unix and Mac developer, having worked at AOL, Google, and several start-ups over the years.  He’s the author of Advanced Mac OS X Programming: The Big Nerd Ranch Guide, over 100 blog posts for Big Nerd Ranch, and an occasional speaker at conferences. Believing in the power of community, he’s a co-founder of CocoaHeads, an international Mac and iPhone meetup, and runs the Pittsburgh PA chapter. In his spare time, he plays orchestral and swing band music.

Speak with a Nerd

Schedule a call today! Our team of Nerds are ready to help

Let's Talk

Related Posts

We are ready to discuss your needs.

Not applicable? Click here to schedule a call.

Stay in Touch WITH Big Nerd Ranch News