Back To Basics: Positioning UIViews
These days I’ve been working on some fairly advanced iOS development techniques on my various projects: I’ve taught myself (badly) about Core Audio, I’m learning OpenGL, I’m developing a series of applications using Core Data, asynchronous parsing of JSON from a streaming HTTP connection, etc. It’s extremely fun and easy once you understand the basics.
What I tend to forget however is that you have to crawl before you can walk, and many people still struggle with some of the simpler techniques that I’ve learned that may not be so obvious, even when reading books or tutorials on Objective-C programming.
Since my previous series of articles on Core Animation (Part 1, Part 2, Part 3, Part 4) were so well received, I thought I’d do another series of articles titled “Back To Basics”.
So without further ado, I give you the first part in my series: Positioning UIViews.
One size does not fit all
First please forgive me for the horrible pun in this title. Originally I meant that there are several different ways to resize and position views in UIKit, but the pun about sizes was a bonus. The point is that there are several properties UIView and UIView subclasses have that can influence their size and position, and none of them are wrong. However there are circumstances where one is better or more convenient to use than the other.
view.frame
The most common method people seem to use for positioning their views is through the “frame” property. For those of you unfamiliar with it, this is used to set the X,Y position of the top-left corner, and the Width/Height of the view. Many people use this without realizing it, because some views are created like so:
CGRect fr = CGRectMake(x, y, w, h);
UIView *view = [[UIView alloc] initWithFrame:fr];
While this is a powerful method to layout interfaces, it can get pretty tedious if you’re arranging several views that need to be positioned relative to each other. Consider a case where you want to align a row of buttons and distribute them evenly across the screen.
NSArray *allButtons; // Add UIButton objects here
NSInteger btnNum = 0;
NSInteger numBtns = [allButtons count];
for (UIButton *button in allButtons) {
CGFloat w, h, x, y;
w = button.frame.size.width;
h = button.frame.size.height;
x = self.view.frame.size.width / numBtns * btnNum;
y = self.view.frame.size.height / 2 - h / 2;
button.frame = CGRectMake(x, y, w, h);
[self.view addSubview:button];
btnNum++;
}
That is a lot of extra code that looks fairly messy. If you have a few instances of that, your code can get pretty cluttered.
view.center
The “center” property represents the X,Y coordinate for the center-point of your view. I know a lot of you will say “Well, duh!” but what you may not realize is that it’s a read/write property that is automatically tied to the “frame” property. So changing the frame for your view will automatically update your “center” property, and vice-versa.
I prefer setting the center property when aligning or arranging multiple views relative to each other because:
- You don’t risk accidentally changing the size of the views;
- You don’t have to constantly divide by 2 to align views;
- The code is much more concise.
This property is especially useful when moving views using animations.
[UIView beginAnimations:@"BounceAnim" context:nil];
[UIView setAnimationRepeatCount:1];
[UIView setAnimationRepeatAutoreverses:YES];
myView.center = CGPointMake(myView.center.x,
myView.center.y + 10);
[UIView commitAnimations];
This simple bit of code lets you animate a view so that it bounces once. By using the “center” property we can trust that we’re simply moving the view around, instead of having to worry about maintaining its size.
view.bounds
Similar to the “center” property, the “bounds” property is related to the “frame” property. When you alter the bounds, it changes the size of the view relative to its center point. This is really convenient if you adjust the center and bounds independently. For example, you can set the bounds property to a CGRect region with an origin of (0,0) to set its size, and can then adjust the center coordinate to position it in the screen.
UIView *view = [[UIView alloc] initWithFrame:CGRectZero];
view.center = CGPointMake(x, y);
view.bounds = CGRectMake(0, 0, w, h);
The example code above shows us creating a brand new UIView instance, and changing its center and bounds properties independently. Even though we’ll be setting those properties after, when we call “initWithFrame:” we still have to supply a valid CGRect value. In this case we use the constant “CGRectZero” which essentially means a frame with all values set to 0.
Managing your views like this is convenient when you either don’t know what size the view should be yet, or you don’t know where on the screen it should be. Or, frankly, it might make your code prettier to separate the construction and positioning of your views.
view.transform
As you may have seen in my previous series on Core Animation, Apple provides a very sophisticated animation framework for your interface. What many people don’t realize is that every UIView object in UIKit is backed by a CALayer, which is where the actual drawing operations happen (there’s more to it than this simple explanation, but for now it’ll do).
By setting the “transform” property of a view to a CGAffineTransform you can alter the position of the view’s layer without having to explicitly move its position. Translation, rotation and even scaling or skewing your view are all possible easily with this property.
This is useful when you want to perform subtle movements such as rotations, or you want to scale your object in or out. For example, you may want a button to “throb” by animating its scale. Or you may want your view to spin, even in 3D. These are all examples of what you’d use this property for.
view.autoresizingMask
This property is often neglected by beginner iOS programmers and is by far one of the most useful for laying out your interfaces. Consider the situation where you want your view to resize when the device is rotated between portrait and landscape orientations. The novice programmer will detect the screen rotation and will explicitly set the “frame” property of all their views to fit the new screen size. This is the WRONG way to manage your interface.
Instead you can use the “autoresizingMask” property to describe how you want your view to adapt to orientation changes. Your options (shortened for brevity) are:
- None
- FlexibleWidth
- FlexibleHeight
- FlexibleLeftMargin
- FlexibleRightMargin
- FlexibleTopMargin
- FlexibleBottomMargin
Consider the example of a toolbar at the bottom of your screen. If you want to have the toolbar stretch to fill the available width, but still remain snug against the bottom of the window, you can say:
myView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleTopMargin);
Using a bitwise “OR” (the “|” pipe character) you combine multiple options together into a single value that tells UIKit how you want your view to adapt to changes to the screen, and even to changes in your view’s superview.
Another example is an interface that has the standard round “i” button in the bottom-right corner of the screen.
myView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleTopMargin);
If you play with those values you can get around the need to explicitly position or resize your views in almost all cases.
When in doubt, use a NIB
If you’re just getting started, or even if you’re a veteran iOS developer, you may want to simply layout your interfaces in Interface Builder rather than positioning your views manually. The performance difference is negligible, and there’s even ways in iOS 4 and above that can cache NIBs for greater performance. And there’s nothing that says you can’t rough-in your interfaces in Interface Builder, and then customize or fine-tune it after your view has loaded.
The most important thing above all else is to just play around with your application and see what works for you. There is no single “silver bullet” to laying out your interfaces. Every case is different, and use the method that is suited to your scenario.
When in doubt, if you find you have a ton of cluttered and error-prone code for laying out your interfaces, chances are you’re just simply doing it wrong. Try something else and do what works.