UIView + position

Placing and moving your views can be a little tricky if you don’t know the details of UIView’s frame, bounds, and center properties.  This post gives a quick intro to those along with a useful category for working with them (code link below) called UIView+position.

frame, bounds, and center

These are the three properties of any UIView (including all subclasses) that determine where a view will appear within its superview.  Of course there are other factors that affect its appearance — mainly for this post, the transform property as well.  For now, we’ll assume the transform is the identity, which is the default and most common case.

The center is a CGPoint in terms of the coordinates of the superview, and it determines the position of the exact center point of your view — which might not be an integer point!

The bounds is a CGRect object that give the bounds of the view’s own coordinates — unlike the center, this is not in terms of the superview.  In all standard cases, your bounds origin will be (0,0), and the only thing that changes is the size.

The frame is another CGRect that gives the position and size of your view in terms of the superview’s coordinates.

At this point, you might be thinking to yourself, “gee, self, what happens if I set these related properties to have conflicting values?”  That’s a good question, and here’s the good answer: these properties update each other to remain consistent.  In more detail, it turns out that frame is not backed by any variable, but rather is just synthesized out of the bounds and center variables (and the transform if it’s non-identity).  So when you change the frame, you’re really changing the bounds and center; if you change the center, you’re updating both the center and the frame.

Here are the key equations that tie these three pieces together:

frame.origin = center - bounds.size / 2

(which is the same as)

center = frame.origin + bounds.size / 2

(and there’s also)

frame.size = bounds.size

These are informal equations that won’t actually work in code (this isn’t C++, people), but they express the correct relationships with mathematical precision.

One thing you usually want in all this is to keep your frame origin at an integer point.  Otherwise, elements of your view such as labels or included images can appear blurry.  Setting your center point to an integer point won’t always work because, if the bounds are odd, the frame origin will end up being non-integral.

Problem: small frame changes are messy

Cocoa touch is set up to primarily work with frames instead of bounds and centers — for example, initWithFrame: is the designated initializer for UIViews.  And frames are great for setting up the initial position and size of your view.  But when it comes to incremental movements or resizing, the relevant code can get ugly.

For example, it would be great if something like this worked:

myView.frame.origin.x += 10;

or

myView.frame.size = CGSizeMake(20, 20);

but these both result in an error: “lvalue required as left operand of assignment.”  Yikes!

The problem is that frame is a non-object property, so the previous call is roughly equivalent to something like this:

const CGRect tmpRect = [myView frame];
tmpRect.size = CGSizeMake(20, 20);

Even if the compiler let us set the value, it would be changing a temporary variable that was shortly lived on the stack, and have no influence on myView.

The obvious work-around to this problem is to use CGRect helper functions, and make calls that look like this instead:

myView.frame = CGRectOffset(myView.frame, 10, 0);

or

myView.frame = CGRectMake(myView.frame.origin.x, myView.frame.origin.y, 20, 20);

Ugly and overly-wordy code!  A blatant violation of all good theology and geometry, my friends.

Solution: finer-grained UIView properties

A better approach is to introduce new properties of all UIView objects (and subclasses) with a few category methods.  This is what UIView+position is about.  With it, you can write code like this to cleanly solve the above cases:

myView.frameX += 10;

or

myView.frameSize = CGSizeMake(20, 20);

Here’s a list of all the new properties:

  • frameOrigin, frameSize
  • frameX, frameY
  • frameWidth, frameHeight
  • frameRight, frameBottom

The last two properties, when changed, will vary the origin but not the size — that is, they move the view rather than resize it.

These extra properties have been extraordinarily convenient to me, and I hope they might be for you as well.

Source

These free-to-use (Apache 2 license) files are available directly from the links below, or as part of the moriarty library.

UIView+position.h

UIView+position.m

5 Comments