Springs in Auto Layout: Distribute views evenly, with constraints, in Xcode 5

18,005

Solution 1

EDIT Note that in iOS 9 this technique will become unnecessary, because a UIStackView will perform distribution automatically. I'll add another answer explaining how that works.

How to Perform Even Distribution Using Autolayout

The simplest way to do this in Interface Builder alone (rather than constructing constraints in code) is to use "spacer" views:

  1. Position the top and bottom buttons absolutely.

  2. Place spacer views between all the buttons. Use constraints to position them horizontally (centering them horizontally is simplest) and to set their widths.

  3. Make constraints between each button and the spacer view above and below it, with a Constant of 0.

  4. Now select all the spacer views and set their heights to be equal.

The first screen shot shows me setting this up in IB:

enter image description here

I have deliberately not corrected for the "misplaced views" because I want you to see what it looks like while I'm designing the constraints. Here's the result on both a 4 inch and a 3.5 inch screen:

enter image description here

I have left the spacer views black, just to show you how this technique works, but of course in real life you would make them transparent and hence invisible! So the user sees just your buttons, evenly distributed on either height of screen.

The reason for the use of this technique is that although the notion of equality performs the distribution of values you are asking for, constraints can apply equality only between aspects of views; thus we need the extra views (the spacer views) so that we have things we can make equal to other things (here, the heights of the spacer views).

Other Approaches

Obviously, a more flexible approach is to assign the constraints in code. This may sound daunting, but there's a lot of third-party code out there to help you, such as this sort of thing.

For example, if we have a (possibly invisible) superview whose height acts as a boundary to dictate maximum vertical distribution of our four buttons, we can pin their tops to the vertical center of that superview with a constant of 0 but a multiplier of 0.000001, 0.666667, 1.33333, and 2.0 respectively (if we have four buttons); now the buttons will stay vertically distributed even as the superview changes size in response to screen height or whatever. [In Xcode 5.1, it will be possible to set that up in Interface Builder, but in earlier versions of Xcode it is not possible.]

Solution 2

In iOS 9 / Xcode 7 this problem will be trivially solved in IB. Simply select the buttons (or whatever it is you want to distribute vertically) and choose Editor > Embed In > Stack View. Then you simply configure the stack view:

  • Provide constraints that position and size the stack view itself. For example, pin the four edges of the stack view to the four edges of its superview.

  • Set the stack view's attributes. In this case we want Vertical axis, Fill alignment, Equal Spacing distribution.

That's all! However, you may be curious about how this works, because it is still possible to do the same thing manually in code. A stack view performs distribution, not by inserting spacer views, but by inserting spacer guides. A guide (a UILayoutGuide) is a lightweight object that behaves like a view for purposes of layout constraints, but is not a view and therefore doesn't have to be made invisible and doesn't carry any of the overhead of a view.

To illustrate, I'll do in code what the stack view is doing. Presume we have four views to distribute vertically. We assign them constraints for everything but their distribution:

  • They all have absolute height constraints

  • Their left is pinned to the superview's left, and their right is pinned to the superview's right

  • The top view's top is pinned to the superview's top, and the bottom view's bottom is pinned to the superview's bottom

Now, presume we have references to the four views as views, an array. Then:

let guides = [UILayoutGuide(), UILayoutGuide(), UILayoutGuide()]
for guide in guides {
    self.view.addLayoutGuide(guide)
}
NSLayoutConstraint.activateConstraints([
    // guide heights are equal
    guides[1].heightAnchor.constraintEqualToAnchor(guides[0].heightAnchor),
    guides[2].heightAnchor.constraintEqualToAnchor(guides[0].heightAnchor),
    // guide widths are arbitrary, let's say 10
    guides[0].widthAnchor.constraintEqualToConstant(10),
    guides[1].widthAnchor.constraintEqualToConstant(10),
    guides[2].widthAnchor.constraintEqualToConstant(10),
    // guide left is arbitrary, let's say superview margin
    guides[0].leftAnchor.constraintEqualToAnchor(self.view.leftAnchor),
    guides[1].leftAnchor.constraintEqualToAnchor(self.view.leftAnchor),
    guides[2].leftAnchor.constraintEqualToAnchor(self.view.leftAnchor),
    // bottom of each view is top of following guide
    views[0].bottomAnchor.constraintEqualToAnchor(guides[0].topAnchor),
    views[1].bottomAnchor.constraintEqualToAnchor(guides[1].topAnchor),
    views[2].bottomAnchor.constraintEqualToAnchor(guides[2].topAnchor),
    // top of each view is bottom of preceding guide
    views[1].topAnchor.constraintEqualToAnchor(guides[0].bottomAnchor),
    views[2].topAnchor.constraintEqualToAnchor(guides[1].bottomAnchor),
    views[3].topAnchor.constraintEqualToAnchor(guides[2].bottomAnchor)
])

(Obviously I could make that code cuter and shorter using loops, but I have deliberately unrolled the loops for clarity, so that you can see the pattern and the technique.)

Share:
18,005
Kenny Wyland
Author by

Kenny Wyland

Kenny is a successful iPhone and Android app developer, a real estate investor and a life-long geek. #SOreadytohelp

Updated on June 06, 2022

Comments

  • Kenny Wyland
    Kenny Wyland almost 2 years

    I understand the old Struts and Springs method of aligning, sizing and distributing views in Interface Builder. However, I cannot seem to figure out how to evenly distribute views using auto layout with Xcode 5. There was a way to do it using Xcode 4, but that option is gone.

    I have 7 buttons arranged in a vertical stack. On a 3.5" layout, it looks great. When I preview the screen in the 4" layout, all of the buttons remain tightly packed and there is a large amount of space below the last button.

    I want them to stay the same height, but I want the space between them to be able flex so they can spread out across the screen.

    enter image description here

    I've been able to get the height of the buttons to flex and fill the space, but that is not my desired behavior. I would like to learn how to use Auto Layout to replace my old Springs behavior, but I can't seem to find any way to do it through Interface Builder.

    I'm ok with the top button either being a fixed space from the top edge or a proportional space from the top edge, likewise for the bottom button and the bottom edge. Those are less important to me, I'm good with either.

    But I really need to figure out how to evenly distribute the extra space between each of the items in the view.

  • Kenny Wyland
    Kenny Wyland over 10 years
    I am displeased with both of these options, but that's Apple's fault, not yours. I haven't needed to use spacers since we were making all web pages with tables and 1 px transparent gifs. sigh Thank you for the comprehensive answer.
  • Goles
    Goles almost 10 years
    I generalized a solution to this problem in my custom UITabBar/UITabBarController replacement. Just check the integration tests for my Auto Layout Constraints generation. The project is on Github, GGTabBar
  • George
    George over 9 years
    Ugh, agreed. Good solution under the limiting AutoLayout circumstances, but geez, it feels like we're back in the 90's laying out websites with tables. UIToolbar has flexible spacer elements which is what's needed here, why can't we have it as a layout element?
  • wuf810
    wuf810 about 9 years
    This solution users multipliers and requires no spacers: stackoverflow.com/a/25898949/202711