I created JFViewPositionUtil for KEYBOX 2.0. I hand code my interfaces rather than use Interface Builder, and version 1.0 proved extremely difficult to lay out professionally while also balancing the consideration of localization.
Why would anybody in their right mind NOT use Interface Builder? Don’t get me wrong, it’s a fine tool but for the degree of customization I and many developers require it doesn’t quite cut it.
In the case of KEYBOX 1.0, a layout that would look good in English might make Japanese look awful. My initial approach to handle this was to adjust the font size on a per-language basis. It worked but was nowhere near optimal.
In version 2.0 I resolved to fix this and since its release I’ve used this class in all my products. It’s matured to the point where I believe it to be beneficial to others as well, and to this end I’ve decided to open source it.
I designed this class to be goal oriented. That is, whatever your goal, you typically only need to invoke a single method offered by this class. It’s even a singleton so there is no need to instantiate it.
This utility class works with both OS X and iOS projects but it should be noted that it is designed to work in coordinate systems where the point of origin (0, 0) is the top left corner. In other words, you’ll want to flip your views in OS X projects when using this class.
Lazy-initialization of views
A quick note, since this utility class operates mainly on the notion of positioning views relative to others, it is beneficial to adopt a lazy-initialization approach to view creation. This way sibling views upon which your view depends will be initialized and positioned by the time you need to base your view’s position on them. All of the below examples in this post will use lazy-initialization of UIViews for iOS.
The best way to demonstrate this class’ abilities is to simply show you. Since this class is goal oriented I’ll break things down by common layout goals.
■ Label to the right of an icon
This is a common layout pattern, and the code might look something like this…
One variation of this is the case of a multiline label. You probably wouldn’t want to vertically center your label to the right of your icon. Instead, it might look more professional to snap the icon’s top right anchor point 5 points left of the label’s top left point. I’ll discuss this technique a bit later.
■ Center aligned titles/headers
Sounds simple enough right? The title is a label with a bold font. Here’s the code…
It’s generally a good idea to first call sizeToFit on labels prior to aligning them with JFViewPositionUtil, especially when you intend to center align them.
■ Text field under a label
Have a text field that should appear left aligned, say, 10 points under a label explaining its purpose? Here is how to accomplish this…
And to right align it instead…
This just touches upon some of the high-level functionality of this class. Other useful methods exist, each aptly named for its purpose.
If JFViewPositionUtil’s high levels actions don’t suffice its low-level anchoring system will. The code is based on anchoring points (shown below) at the cardinal points on a rectangle as well as a center point and allow for snapping of any anchor point of one view to any point of another while also allowing for offsets from this snap point for pixel-perfect precision.
Earlier I mentioned aligning a multilined label’s top left corner 5 points to the right of an icon’s top right corner. Here is how to accomplish this…
Notice that I’m aligning the label to the icon. You could just as easily do the reverse.
Going Even Further
JFViewPositionUtil’s high and low-level methods ought to satisfy all your view positioning needs, but if your app has some specialized effect that isn’t handled you might also find the pointForAnchorPoint:ofView: method useful. It retrieves the location (relative to the parent view) of any of the subview’s anchor points. Armed with this information you can create your own frames to position views just the way you want.
Grouped Layout Strategy
With this introduction to the class’ capabilities out of the way it’s now time to talk layout strategies.
While there is nothing wrong with laying out each control in relation to a sibling control it often makes sense to group controls within an otherwise invisible NSView/UIView and adjust the size of that parent view perfectly for the subviews it contains.
Why would you want to do this? This approach allows for a group view to be dynamically slid in between two others and to have them adjust their positions to accommodate it. Having to re-adjust dozens of controls in tandem is a lot harder than re-adjusting a single group view acting as the parent view for these controls.
JFViewPositionUtil offers the setHeightOfSuperview:toFitAllSubviewsWithMargin: method to help you in this regard. Think of this method as being like the UIView sizeToFit method except that it allows for an adjustable margin on the bottom of the lowest subview.
This method is also quite useful for adjusting the vertical height of UIScrollViews, even if you don’t plan to dynamically insert or remove group views within them. The logic has a special handler for UIScrollViews to set their content size in addition to their regular view size.
One last thing to note: The lazy-initialization approach I advocated earlier comes into play again here. Keep in mind that the group view needs to ensure that all its subviews are initialized and positioned prior to adjusting its own size to fit them. By going with lazy-initialization the group view doesn’t need to worry about the possibility of re-initializing its subviews.
As I mentioned earlier it was when I was localizing KEYBOX that I encountered difficulty with laying out my views. Japanese is a very compact language whereas French and Spanish are comparatively stretched out. English is somewhere in the middle.
The approach I took in version 2.0 was to use fixed size font, multilined labels to handle any text length and have any sibling controls appearing under them dynamically adjust their y positions to always be N points away. As long as the parent view had enough vertical room or was UIScrollView-based it worked like magic.
In case you were wondering, this class is also compatible with the animation systems of both OS X and iOS. Simply use JFViewPositionUtil to set the new position within an animation context and watch iOS do the rest for you. For OS X you can use a view’s animator for the same effect or directly reset the view position at each progress step of an NSAnimation if need be.
Lastly, if you are developing an OS X project and use auto-resizing views (setAutoResizingMask:) you’ll be happy to know that they are completely supported as usual in much the same manner as animated views are.
JFViewPositionUtil is the result of my own frustrations with dynamic, hand-coded views. If you are equally frustrated with the difficulties of having to code your UI by hand but simply cannot rely on Interface Builder to meet your exacting needs JFViewPositionUtil might be the sweet spot for you. I invite you to give it a try and find out for yourself!
If you have any questions or comments please contact me at jay [AT] this domain.