Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Evenly space multiple views within a container view

People also ask

How do you distribute the space between the views in storyboard without using any code?

Just select the views you want to contain in the Interface Builder and choose Editor -> Embed In -> Stack view. Set the appropriate width/height/margin constraints for the stack view, and make sure to set the Distribution property to 'Equal spacing':


LOOK, NO SPACERS!

Based on suggestions in the comments section of my original answer, especially @Rivera's helpful suggestions, I've simplified my original answer.

I'm using gifs to illustrate just how simple this is. I hope you find the gifs helpful. Just in case you have a problem with gifs, I've included the old answer below with plain screen shots.

Instructions:

1) Add your buttons or labels. I'm using 3 buttons.

2) Add a center x constraint from each button to the superview:

enter image description here

3) Add a constraint from each button to the bottom layout constraint:

enter image description here

4) Adjust the constraint added in #3 above as follows:

a) select the constraint, b) remove the constant (set to 0), c) change the multiplier as follows: take the number of buttons + 1, and starting at the top, set the multiplier as buttonCountPlus1:1, and then buttonCountPlus1:2, and finally buttonCountPlus1:3. (I explain where I got this formula from in the old answer below, if you're interested).

enter image description here

5) Here's a demo running!

enter image description here

Note: If your buttons have larger heights then you will need to compensate for this in the constant value since the constraint is from the bottom of the button.


Old Answer


Despite what Apple's docs and Erica Sadun's excellent book (Auto Layout Demystified) say, it is possible to evenly space views without spacers. This is very simple to do in IB and in code for any number of elements you wish to space evenly. All you need is a math formula called the "section formula". It's simpler to do than it is to explain. I'll do my best by demonstrating it in IB, but it's just as easy to do in code.

In the example in question, you would

1) start by setting each label to have a center constraint. This is very simple to do. Just control drag from each label to the bottom.

2) Hold down shift, since you might as well add the other constraint we're going to use, namely, the "bottom space to bottom layout guide".

3) Select the "bottom space to bottom layout guide", and "center horizontally in container". Do this for all 3 labels.

Hold down shift to add these two constraints for each label

Basically, if we take the label whose coordinate we wish to determine and divide it by the total number of labels plus 1, then we have a number we can add to IB to get the dynamic location. I'm simplifying the formula, but you could use it for setting horizontal spacing or both vertical and horizontal at the same time. It's super powerful!

Here are our multipliers.

Label1 = 1/4 = .25,

Label2 = 2/4 = .5,

Label3 = 3/4 = .75

(Edit: @Rivera commented that you can simply use the ratios directly in the multiplier field, and xCode with do the math!)

4) So, let's select Label1 and select the bottom constraint. Like this: enter image description here

5) Select the "Second Item" in the Attributes Inspector.

6) From the drop down select "Reverse first and second item".

7) Zero out the constant and the wC hAny value. (You could add an offset here if you needed it).

8) This is the critical part: In the multiplier field add our first multiplier 0.25.

9) While you're at it set the top "First item" to "CenterY" since we want to center it to the label's y center. Here's how all that should look.

enter image description here

10) Repeat this process for each label and plug in the relevant multiplier: 0.5 for Label2, and 0.75 for Label3. Here's the final product in all orientations with all compact devices! Super simple. I've been looking at a lot of solutions involving reams of code, and spacers. This is far and away the best solution I've seen on the issue.

Update: @kraftydevil adds that Bottom layout guide only appear in storyboards, not in xibs. Use 'Bottom Space to Container' in xibs. Good catch!

enter image description here


So my approach allows you to do this in interface builder. What you do is create 'spacer views' that you have set to match heights equally. Then add top and bottom constraints to the labels (see the screenshot).

enter image description here

More specifically, I have a top constraint on 'Spacer View 1' to superview with a height constraint of lower priority than 1000 and with Height Equals to all of the other 'spacer views'. 'Spacer View 4' has a bottom space constraint to superview. Each label has a respective top and bottom constraints to its nearest 'spacer views'.

Note: Be sure you DON'T have extra top/bottom space constraints on your labels to superview; just the ones to the 'space views'. This will be satisfiable since the top and bottom constraints are on 'Space View 1' and 'Spacer View 4' respectively.

Duh 1: I duplicated my view and merely put it in landscape mode so you could see that it worked.

Duh 2: The 'spacer views' could have been transparent.

Duh 3: This approach could be applied horizontally.


Very quick Interface Builder solution:

For any number of views to be evenly spaced within a superview, simply give each an "Align Center X to superview" constraint for horizontal layout, or "Align Center Y superview" for vertical layout, and set the Multiplier to be N:p (NOTE: some have had better luck with p:N - see below)

where

N = total number of views, and

p = position of the view including spaces

First position is 1, then a space, making the next position 3, so p becomes a series [1,3,5,7,9,...]. Works for any number of views.

So, if you have 3 views to space out, it looks like this:

Illustration of how to evenly spread views in IB

EDIT Note: The choice of N:p or p:N depends on the relation order of your alignment constraint. If "First Item" is Superview.Center, you may use p:N, while if Superview.Center is "Second Item", you may use N:p. If in doubt, just try both out... :-)


As of iOS 9, Apple has made this very easy with the (long-awaited) UIStackView. Just select the views you want to contain in the Interface Builder and choose Editor -> Embed In -> Stack view. Set the appropriate width/height/margin constraints for the stack view, and make sure to set the Distribution property to 'Equal spacing':

Of course, if you need to support iOS 8 or lower, you'll have to choose one of the other options.


I've been on a rollercoaster ride of loving autolayout and hating it. The key to loving it seems to be to accept the following:

  1. Interface builder's editing and "helpful" auto-creation of constraints is near useless for all but the most trivial case
  2. Creating categories to simplify common operations is a life-saver since the code is so repetitive and verbose.

That said, what you are attempting is not straightforward and would be difficult to achieve in interface builder. It is pretty simple to do in code. This code, in viewDidLoad, creates and positions three labels how you are asking for them:

// Create three labels, turning off the default constraints applied to views created in code
UILabel *label1 = [UILabel new];
label1.translatesAutoresizingMaskIntoConstraints = NO;
label1.text = @"Label 1";

UILabel *label2 = [UILabel new];
label2.translatesAutoresizingMaskIntoConstraints = NO;
label2.text = @"Label 2";

UILabel *label3 = [UILabel new];
label3.translatesAutoresizingMaskIntoConstraints = NO;
label3.text = @"Label 3";

// Add them all to the view
[self.view addSubview:label1];
[self.view addSubview:label2];
[self.view addSubview:label3];

// Center them all horizontally
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label3 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

// Center the middle one vertically
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]];

// Position the top one half way up
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label1 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:label2 attribute:NSLayoutAttributeCenterY multiplier:0.5 constant:0]];

// Position the bottom one half way down
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label3 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:label2 attribute:NSLayoutAttributeCenterY multiplier:1.5 constant:0]];

As I say, this code is much simplified with a couple of category methods in UIView, but for clarity I've done it the long way here.

The category is here for those interested, and it has a method for evenly spacing an array of views along a particular axis.


The correct and easiest way is to use Stack Views.

  1. Add your labels/views to the Stack View:

enter image description here

  1. Select the Stack View and set Distribution to be Equal Spacing:

enter image description here

  1. Add Spacing to nearest neighbor constraints to the Stack View and update frames:

enter image description here

  1. Add Height constraints to all the labels (optional). Needed only for views that does not have Intrinsic Size). Labels for example does not need here height constrains and only need to set numberOfLines = 3 or 0 for example.

enter image description here

  1. Enjoy Preview:

enter image description here


Most of these solutions depend on there being an odd number of items so that you can take the middle item and center it. What if you have an even number of items that you still want to be evenly distributed? Here's a more general solution. This category will evenly distribute any number of items along either the vertical or horizontal axis.

Example usage to vertically distribute 4 labels within their superview:

[self.view addConstraints:
     [NSLayoutConstraint constraintsForEvenDistributionOfItems:@[label1, label2, label3, label4]
                                        relativeToCenterOfItem:self.view
                                                    vertically:YES]];

NSLayoutConstraint+EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

/**
 * Returns constraints that will cause a set of views to be evenly distributed horizontally
 * or vertically relative to the center of another item. This is used to maintain an even
 * distribution of subviews even when the superview is resized.
 */
+ (NSArray *) constraintsForEvenDistributionOfItems:(NSArray *)views
                             relativeToCenterOfItem:(id)toView
                                         vertically:(BOOL)vertically;

@end

NSLayoutConstraint+EvenDistribution.m

@implementation NSLayoutConstraint (EvenDistribution)

+(NSArray *)constraintsForEvenDistributionOfItems:(NSArray *)views
                           relativeToCenterOfItem:(id)toView vertically:(BOOL)vertically
{
    NSMutableArray *constraints = [NSMutableArray new];
    NSLayoutAttribute attr = vertically ? NSLayoutAttributeCenterY : NSLayoutAttributeCenterX;

    for (NSUInteger i = 0; i < [views count]; i++) {
        id view = views[i];
        CGFloat multiplier = (2*i + 2) / (CGFloat)([views count] + 1);
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view
                                                                      attribute:attr
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:toView
                                                                      attribute:attr
                                                                     multiplier:multiplier
                                                                       constant:0];
        [constraints addObject:constraint];
    }

    return constraints;
}

@end