One of my favorite parts about being an iOS developer is the opportunity to create amazing User Interfaces. I love nothing more than taking an awesome designer’s PSD and actualizing it on the iPhone or iPad. One my least favorite parts about being an iOS developer is…programmatic view configuration. It’s painful, it’s tedious, it isn’t always immediate obvious what’s happening, and it just bloats class files with a lot of extra cruft. In general, I dislike lots of boilerplate code, but this is especially true about setting up a complex view hierarchy entirely in code. So how do I minimize the inevitable impact of configuring views programmatically?
Use Interface Builder
Yes, this one should be a no-brainer, but it’s still worth mentioning. I occasionally will hear a “hardcore” developer make statements like, “No way, man, you have to do it all programmatically. Interface Builder is a joke.” Yeah, well I’m a visual person, and that “joke” lets me complete 90% of my view configuration easily and accurately. Admittedly, if you’re new to the platform, Interface Builder can take a little while to grasp, especially with all of the behind-the-scenes “magic” that goes on. Once in a blue moon, I’ll still get caught forgetting to wire up an outlet, but for the most part, I love Interface Builder. And with iOS 5, even cooler features are coming that I’m super excited about.
Anyway, let’s do a quick comparison. Which of these is more immediately understandable?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
(The correct answer is, “The first one!”) No, really, at the end of the day, it’s a personal preference. I just don’t see why anyone would actually want to go with a fully-programmatic approach. And the fact that Apple continues to improve upon this notion of visually constructing the view hierarchies of your apps by maintaining and improving Interface Builder? Well, it just seems like it’s the obvious choice.
Also, with Interface Builder, you gain the implicit awareness of Apple’s Human Interface Guidelines. Objects snap to margins based on sensible defaults and guides. In addition, Interface Builder—to some people’s annoyance—won’t allow you to do certain things that don’t “make sense” in Apple’s eyes. One example is that you cannot add a subview to a
UIImageView. Nothing prevents you from doing so programmatically, but seemingly, Apple is saying, “Stop that. Don’t add a subview to
UIImageView. Your design is wrong.”
Make Your Layout Code Semantic
Okay, so unfortunately, it’s impossible for Interface Builder to solve all of our problems (yet). Some things need to be sized and positioned dynamically, so we have to work with the frame or bounds eventually. But why should it be so laborious and painful? Say we want to move a view down by 10 points:
Easy! But wait, that won’t work. The compiler is throwing a hissy fit (rightly so). We’ve gone and accessed
frame property, followed directly by an access to the
origin struct, followed, finally, by the
y value. Then
y is incremented by
10.0f. Of course, accessing a struct makes a copy, so all we end up doing is increasing
y on some copied struct and immediately discarding the result.
frame is never set to anything new. In any case, the compiler refuses to proceed because
frame, acting as a getter, is an r-value; i.e., it is meant to used on the right side of an assignment statement, but we put it on the left side. Bummer. Okay, so how about this?
1 2 3 4
Or, if we really don’t like “wasting” lines of code:
That’s a valid one-liner, and
frame is an l-value this time because the dot syntax in this case invokes the setter. But boy, that line is long enough to make a T-Rex angry. So we saved a couple lines, but it’s still a pain.
Now, let’s say we want to right align a view, A, next to another view, B, with a padding of 10 points in between them. In addition, we want view A to be vertically centered relative to view B. Here’s how that might look:
1 2 3 4 5 6
So this does the job, but if I had only 5 seconds to look at that code, I probably wouldn’t have figured out what the intent was. Commenting the code is no good, because with a task as common as this, 60% of your source code would end up as trivial, annoying comments. 「仕方ない」as they say in Japan, or “It can’t be helped.” Oh, but it can! Part of the problem is that most view layout code is just lots and lots and lots and lots of repetition. (See what I did there?) E.g., We’re writing stuff like
view.frame.size.width all over the place. Let’s get rid of all that nonsense—or at least die trying! We’ll introduce some shorthand properties to help us out:
1 2 3 4 5 6
That’s a little better. We can, for example, reduce
viewA.frame.origin.x to just
viewA.frameX. The same goes for all of
frame’s structs’ members. Let’s take it a step further:
1 2 3 4
Even better. But since we’ve gone that far, how about…
With this, we’ve done a pretty good job of consolidating a bunch of procedural boilerplate—storing individual frame values, copying the existing frame, modifying the copy, reassigning the copied frame—into just two lines. It’s readable, sure, but even still, the intent of what we’re trying to do isn’t obvious. For my final trick, I give you:
What does this mean? To understand (if you don’t already), it’s important to remember that by working with view geometry in UIKit, we are always working with respect to the origin. This happens to be the upper left corner of the frame. This works fine a lot of the time, but it is inconvenient when we can more easily express the intent of our view layout with respect to a different origin.
Both right alignment and vertical centering are great examples of this. As we see in the “Grand Finale”, it’s easier to simply take the
x value of
frame (which represents the left edge), subtract 10 points from it (per the original problem description), and assign that position to the right edge of
frame. The property setter will do the calculations to do the actual frame adjustments with respect to the real origin.
Even more dramatic an improvement is the vertical centering. We merely express that we want
frame to be set with respect to the vertical center. And the value we want to set it to is the vertical center of
frame. That is to say, “I want the middle of my frame, with respect to
y, to be the middle of some other frame, also with respect to
y”. Compare this to the manual calculations we originally used, and it should be clear how much simpler semantic view layout can be.
Of course, these semantic
UIView properties don’t exist, so we’ll have to create them (among others): UIView Semantic Layout Properties. These will also be available in LTKit, which I’ll blog about soon. (I think I finally found a focus for it!)
The inspiration for these properties mostly came from a series of
CGRect methods that return individual values from the struct. For example,
CGRectGetMaxX, which gives you the right edge of the
CGRect you pass to the function. I thought, “Well, if you can get these values from a rect, why can’t you set them, too?” Applied broadly to
CGRect, this didn’t seem too useful or practical. So I decided instead to target
bounds properties. Also, looking at
anchorPoint property, I realized that you can apply transforms about any arbitrary point within the layer. Not really the same concept, but the fact that you can move the transform’s origin point got me thinking in that direction.
It’s notable that there really isn’t anything special about these properties. They’re just simple calculations used for short-handing really long
CGRectMake function calls! But a side effect of the naming—which, by the way, is possibly being reconsidered—is that you get to apply a little meaning behind what you’re actually trying to do to the frame or bounds. In any case, I’ll never develop iOS applications without them.
So maybe you agree or disagree with my approach to developing UI layouts. As a general rule, the less code in my final project, the better. Possibly a contrarian opinion, sure, but as you gain more code, you also “gain” more chances for Error to show its ugly face. By the way, Error is probably related to this guy:
I wonder how many people get that reference. Either way, you don’t want to press your luck when dealing with errors…
Anyway, by letting finely-tuned tools—such as Interface Builder—do as much of the mundane, boilerplate work for you, you can focus on just creating awesome apps. And even if you’re the type who insists on keeping everything programmatic, that doesn’t mean you can’t be a little more creative with how you define your view interactions. If you feel there’s a legitimately better way, or if you feel like there’s a strong argument for why I’m wrong, please let me know. I love discussing—not to be confused with yelling and arguing—things like this with other developers. I try to be an open book about stuff like this!