Where I used to work, we had this issue as well. Our app was getting translated into 10 different languages.
At first, we tried doing what Wil suggested, which is to make everything super wide and fit in every language. Unfortunately, "online backup" might be pretty short in English, but in other languages (especially Spanish), it's really long ("copia de seguridad" just means "backup"). Widening our UI made everything look pretty terrible.
One day, I was playing around with some Core Animation stuff and discovered the CAConstraint
class. CAConstraint
is basically a way to define a layout relationship between two CALayers
. You give one layer a name (like "layerA") and then say "layerB is constrained [in such-and-such a way] to a sibling layer called layerA". Then, whenever the layer named layerA
is repositioned or resized, layerB
automatically moves as well. It's really neat, and it's just what we were looking for.
After a couple of days of work, I came up with what is now CHLayoutManager
. It's basically a re-make of CAConstraint
and friends, but for NSViews
. Here's a simple example of how it works:
CHLayoutConstraint * centerHorizontal = [CHLayoutConstraint constraintWithAttribute:CHLayoutConstraintAttributeMidX relativeTo:@"superview" attribute:CHLayoutConstraintAttributeMidX];
CHLayoutConstraint * centerVertical = [CHLayoutConstraint constraintWithAttribute:CHLayoutConstraintAttributeMidY relativeTo:@"superview" attribute:CHLayoutConstraintAttributeMidY];
[aView addConstraint:centerHorizontal];
[aView addConstraint:centerVertical];
This will keep aView
centered in its superview, regardless of how the superview is resized. Here's another:
[button1 setLayoutName:@"button1"];
[button2 addConstraint:[CHLayoutConstraint constraintWithAttribute:CHLayoutConstraintAttributeMinX relativeTo:@"button1" attribute:CHLayoutConstraintAttributeMaxX]];
[button2 addConstraint:[CHLayoutConstraint constraintWithAttribute:CHLayoutConstraintAttributeMaxY relativeTo:@"button1" attribute:CHLayoutConstraintAttributeMaxY]];
[button2 addConstraint:[CHLayoutConstraint constraintWithAttribute:CHLayoutConstraintAttributeWidth relativeTo:@"button1" attribute:CHLayoutConstraintAttributeWidth]];
This will keep button2
anchored to the right edge of button1
, as well as keeping button2
's Y position and width the same as button1
's.
Internally, CHLayoutManager
uses an NSValueTransformer
to calculate the new positioning information. Some of the CHLayoutConstraint
initializers accept an NSValueTransformer
, so you can create arbitrarily complex layout manipulations.
We used this for constraining and laying out the entire UI, and then doing all of the localization in code (and subsequently calling -sizeToFit
, with some modifications). Our UI would just flow into its final layout. It turned out to be extremely convenient. We'd just package up our .strings
files, send them off to the translators, and then drop them in to place when we got them back, and our app would instantly be localized for that language.
CHLayoutManager
isn't perfect. It doesn't resolve conflicts, but simply applies constraints in the order they're added. So you can constrain (for example) the MinX
of a view 42 different ways, but only the last one will be used. Also, if you constrain the MinX and the MaxX, they'll also be applied in the order they're added and will not end up stretching or shrinking the width. In other words, constraining one attribute of a view will not affect the other attributes. It's compatible with 10.5+ (GC and non). However, due to some changes in Lion, it's unlikely that I'll address the shortcomings.
Despite these shortcomings, it's an extremely flexible framework, and (IMO) some pretty nifty code. (Plus, I swizzle -[NSView dealloc]
! Yay!)
Update
Now that AppKit has this same functionality (via NSLayoutConstraint
), I recommend using that system instead of CHLayoutManager
. It is far more robust.