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

  1. Oscar Marquez
    Posted February 3, 2011 at 2:42 pm | Permalink

    Can I use only these files (UIView + position) and remove the license?
    I don’t need the the moriarty library.

  2. Posted February 3, 2011 at 3:11 pm | Permalink

    You can use the UIView+Position files without the rest of moriarty, sure. The way the apache license works, it will always apply to these files. However, the apache 2 license is specifically designed to allow anyone to use and modify something, even if they intend to include it in a commercial piece of software. It is an extremely business-friendly license. So, basically, no, you can’t remove the license, but you can use the files in almost any way you can imagine (see here for details).

  3. Moon
    Posted July 21, 2011 at 5:39 am | Permalink

    Hi Tyler~ Thank u for your article and the moriarty library,they are very useful!
    I am new to iOS develop.And There is something I can’t understand in this article,please help me!
    The question is about the sentence “he problem is that frame is a non-object property, so the previous call is roughly equivalent to something like this:”.What’s the different between non-object property and object property? I mean the memory manage. Why ” it would be changing a temporary variable”? When instant an UIView,i think the instant lives on the stack.Or can you give me some document to read.Thanks a lot!

  4. Posted July 21, 2011 at 4:19 pm | Permalink

    Hi @Moon, here is an answer to your question about non-object vs object properties:

    By an “object property” I mean a property whose type is a class. In most iOS code, this means any property which is a subclass of NSObject. By a “non-object property” I mean anything that is a primitive data type, like an int or a float. Here are some examples:

    // Object properties.
    @property (nonatomic, retain) UIView *myView;
    @property (nonatomic, assign) id delegate;

    // Non-object properties.
    @property (nonatomic) int awesomeLevel;
    @property (nonatomic) float scaleFactor;

    For code like this, if I execute:

    int i = obj.awesomeLevel;
    i = 3;

    Then it only changes the local copy of i, and won’t change the value of obj.awesomeLevel. However, if I run code like this:

    UIView *aView = obj.myView;
    aView.backgroundColor = [UIColor redColor];

    then it _will_ change obj.myView! The main difference is that object properties always use pointers, and non-object properties may not be pointers. When you are working with pointers, if you change the thing pointed to, then it affects the original pointer as well. However, if you’re working with non-pointers, like an int, then modifying a copy of the int won’t change the original int.

    So if you execute code like this:

    CGRect frame = myView.frame;
    frame.origin = CGPointMake(100, 200);

    it won’t change myView.frame at all, because the frame variale and property is not a pointer. That is the problem this post is trying to help solve.

  5. Moon
    Posted July 24, 2011 at 6:15 am | Permalink

    I got it!Thank you very much !

2 Trackbacks

  1. By Bynomial Code » Simplified popovers on May 2, 2010 at 7:35 pm

    [...] The beInit method is explained in an earlier post, as is the handy UIView+position category. [...]

  2. By Bynomial Code » Faster Tables via Row Heights on September 14, 2010 at 4:31 pm

    [...] post assumes your table is run by a UITableViewController; it also uses the UIView+Position category to get the frameHeight property, which is just an abbreviation for [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*