The best description of how Cocoa’s views are designed that I could find from Apple is in the Cocoa Views Guide PDF in the chapter called View Geometry. If you’re trying to get your head around what a CGRect is and why self.view.frame.origin.x++ doesn’t work, that’s required reading. (A hint to the latter: view.frame is return-by-value; CGRect is a struct, self is an object.)

That said, here’s a handy graphic to not only help you get your head around when to use frame and when to use bounds, but also what they mean in the grand scale of things. Also included are some visual guides on when to use some handy CGGeometry.h functions to do some work for you.

Caveat emptor: The above describes the iOS coordinate system, which is technically inverted. Mac OS X’s origins are at the lower left (Cartesian quadrant 1) so while all the X-related items are correct, in Mac OS X the Y-related items would be reversed with CGRectGetMinY at the bottom.

Examples

How about some examples? There are also two more functions that are day-savers here. These take in a rect and then spit out a modified rect. Because they act on copies (pass-by-value) you can input one rect and output it somewhere else. How about setting a new view to be a 10pt inset from the current bounds? Easy.

(CGRect) CGRectOffset(rect, dx, dy)

Moves the origin of the rect by the delta X and Y.

(CGRect) CGRectInset(rect, dx, dy)

Resizes a rect in-place (maintaining the center point) by the delta X and Y. Positive deltas shrink the rect, negative values grow it.

Example: Move a view up or down by y.

view.frame = CGRectOffset(view.frame, 0, y);

Example: Expand a view by 5.0 in three directions (not down).

view.frame = CGRectInset(view.frame, -10.0, -5.0);
view.frame = CGRectOffset(view.frame, 0, -5.0);

Example: Is a view’s origin on the top half of its superview?

BOOL wellIsIt = (CGRectGetMinY(view.frame) < CGRectGetMidY(view.superview.frame))

Example: Drag a view around in a UIPanGestureRecognizer callback.

Here’s a fun one. Note that you could also pass the resultant point into a transform so that failed drags could simply be an animated reset to an identity transform, but that’s no fun. Keep the original rect around somewhere and then just move it.

if (
  [gestureRecognizer state] == UIGestureRecognizerStateBegan ||
  [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
  CGPoint point = [gestureRecognizer translationInView:self.view];
  floatyView.frame = CGRectOffset(floatyView.frame, point.x, point.y);
  [gestureRecognizer setTranslation:CGPointZero inView:self.view];
}

Exercise: Manually convert a point from one coordinate system to another.

This is fun. (For varying degrees of fun.) A point is just an x/y pair; it has no knowledge of from whence it came. So (10,10) to your view is really (38,17) to your superview or (40,17) to its superview. To live in this hellhole of a world, we typically exploit NSView or UIView and tell them to sort it out like so:

[self.view convertPoint:point fromView:someSubview]

But, where’s the fun in that? More importantly: do you know what’s happening?

I’ll let you work on this on your own. You can always get the right answer with the -convertPoint:fromView: method so you know the goal to reach. Here’s a hint: figure out if the fromView is an ancestor or descendant to you and then crawl the chain of subviews up from the lowest view until you reach the other, calculating the total offset from the frame of each, then apply that offset to the point.

Moving a whole rect is similar, but you’re just setting the origin point.

I love this stuff. :)

February 06, 2013