BuySellAds.com

My book Barcodes with iOS is nearing completion. Buy it now to get early access!
Our DNA is written in Objective-C
Jump

CGRect Tricks

CGRect might just be one of the most often used structures that you have in your fingers when coding for iPhone. View frames and bounds are something that you touch way more often than dealing with CGSize or CGPoint values.

So it pays to know about all the nifty utilities that Apple provides for you to make your life easier.

Creating Rectangles

For most structures that you will encounter in Apple’s SDKs you will also find a corresponding macro to create such a structure on the fly and in the same line fill it with values. Usually those macros are set up for you as a preprocessor define and are named like the structure they are creating plus suffice Make.

// create a rectangle
CGRect newRectangle = CGRectMake(0, 0, 100, 200);

Actually CGRect consists of two sub-structures, a CGPoint for the (usually) upper left origin as well as a CGSize for the width and height. But CGRectMake takes care of filling these accordingly, the first two parameters go into the origin (x,y) and the latter two parameters end up in size (width, weight). Even though it looks like a C-function it’s actually not. Due to being configured as preprocessor macro there is no C-function generated at compile time, but actually the commands to fill the structure are copied into your code so you don’t incur an overhead for pushing the parameters onto the stack and function calling.

Shrinking, Expanding, Edge Insetting

If you need to shrink a CGRect by a certain value horizontally and vertically you could do so by modifying the structure members themselves. But way more elegant is to use the provided convenient macro CGRectInset. Positive values will decrease the size evenly for the new rectangle to be centered in the old one. Negative values by the same token will increase it.

CGRect aRectangle = CGRectMake(0,0, 100, 200);
CGRect smallerRectangle = CGRectInset(aRectangle, 10, 20);
// result origin (10, 20) and size (80, 160)

This method is fine for 80% of use cases, but sometimes you want to apply different insets for each edge. Imagine, if you will that you want, to have a property contentInsets that would specify the distance a view content should have from each border. The structure of choice for this is UIEdgeInsets(top, left, bottom, right), and of course, there is also a matching Make macro available.

Once you have both you can easily apply the insets to your rect to receive the finally content rectangle. The macro for this purpose is UIEdgeInsetsInsetRect, would you have guessed?

CGRect rect = CGRectMake(0, 0, 100, 200);
UIEdgeInsets contentInsets = UIEdgeInsetsMake(10, 20, 30, 40);
 
CGRect result = UIEdgeInsetsInsetRect(rect, contentInsets);
NSLog(@"x: %f, y: %f, width: %f, height: %f",
		result.origin.x, result.origin.y,
		result.size.width, result.size.height);
// x: 20.000000, y: 10.000000, width: 40.000000, height: 160.000000

Actually the above NSLog is how I have been inspecting CGRect for the past 2 years. Right after I published the first version of this blog post I was pointed out to me that there is an even more elegant method: NSStringFromCGRect. Wow, a third as much key strokes.

NSLog(@"%@", NSStringFromCGRect(result));
// {{20, 10}, {40, 160}}

Wway more elegant, I’m sure you’ll agree? (Thanks Matt and Thomas But that’s the point of making these write ups. You learn a bit from writing them and you learn a bit more when other pros tell you the mistakes you made. ;-)

Interesting Intersecting

Another thing that you might want to do often with as little code as possible is to check whether a point is inside a rectangle or whether two rectangle are intersecting each other. Did you hit the rectangle? Did your avatar rectangle intersect with the mine rectangle? Boom!

// enemy hit?
CGRect enemyRect = CGRectMake(0, 0, 100, 200);
CGPoint hitPoint = CGPointMake(50, 50);
 
if (CGRectContainsPoint(enemyRect, hitPoint))
{
	// YES!
}
 
// landmine touched?
CGRect playerRect = CCRectMake(200, 300, 10, 10);
CGRect mineRect = CGRectMake(10, 10, 20, 20);
 
if (CGRectIntersectsRect(playerRect, mineRect))
{
	// OUCH!
}

Doing it like this as opposed to complicated if statements makes your game engine code read almost like plain English. Further similar macros are defined in CGGeometry.h which you can easily look at by CMD+Doubleclicking on such a CGRect macro.

On the WWDC videos I remember seeing the use of for the 4 macros to get the maximum and/or minimum values for the coordinates. CGRectGetMaxX, CGRectGetMinX, CGRectGetMaxY, CGRectMinY can save you from writing more code than is necessary.

Dictionary Representation

Often you might have to store a CGRect in a plist on disk, but you don’t have to create the dictionary in a difficult fashion. CGRect, being at the same level as Core Foundation has a function to create a CFDictionary for it’s current contents.

CFDictionaryRef frameDictRef = CGRectCreateDictionaryRepresentation(self.view.frame);
NSDictionary *frameDict = [NSDictionary dictionaryWithDictionary:
		(NSDictionary *)frameDictRef]; // autoreleased
CFRelease(frameDictRef);
 
NSLog(@"%@", frameDict);

You can directly treat the CFDictionary you get back as NSDictionary, but I recommend that you create a new autoreleased instance instead and properly release the CFDictionaryRef. CoreFoundation objects don’t have an autorelease, that’s why we have to do it this way to not have a leak.

 {
    Height = 1004;
    Width = 768;
    X = 0;
    Y = 20;
}

The reverse is way easier because we don’t have to create something to release afterwards. Just define a CGRect to take up the decoded values and provide the address to it to a function.

CGRect rect;
CGRectMakeWithDictionaryRepresentation((CFDictionaryRef)frameDict, &rect);

The same works also for CGPoint and CGSize by the way, but then you naturally get X+Y or Width+Height into your dictionary.

Storing it in Obj-C Storage Classes

One more thing I had forgotten about mentioning which comes into play if you are already thinking like an object oriented pro. CGRect are structures, not objects. So you would naturally be out of luck if you wanted to save them in storage classes like NSArray, NSSet or NSDictionary.

How very nice of Apple to provide some class methods in UIGeometry.h that let us wrap all those structures into handy NSObjects.

NSMutableArray *tmpArray = [NSMutableArray array];
[tmpArray addObject:[NSValue valueWithCGRect:rect]];
[tmpArray addObject:[NSValue valueWithCGRect:result]];
 
NSLog(@"%@", tmpArray);

Similar methods are available for points, sizes, transforms and edge insets.

Conclusion

You see that a great deal if thinking went into all these convenience methods on our behalf. So that we won’t have to. This makes working with and manipulating structures that you encounter in your daily Cocoa life a breeze and they should be second nature. So don’t just store them in your tool chest, but use them sub-consciously. The amount of code you save will take your breath away.


Categories: Recipes

%d bloggers like this: