Evenly space multiple views within a container view

141,299

Solution 1

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.

Solution 2

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

Solution 3

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... :-)

Solution 4

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.

Solution 5

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.

Share:
141,299

Related videos on Youtube

nothappybob
Author by

nothappybob

Updated on January 23, 2022

Comments

  • nothappybob
    nothappybob over 2 years

    Auto Layout is making my life difficult. In theory, it was going to be really useful when I switched, but I seem to fight it all of the time.

    I've made a demo project to try to find help. Does anyone know how to make the spaces between views increase or decrease evenly whenever the view is resized?

    Here are three labels (manually spaced vertically even):

    image1

    What I want is for them to resize their spacing (not the view size) evenly when I rotate. By default, the top and bottom views squish towards the center:

    image2

    • BergP
      BergP over 11 years
      maybe set constraint from one label to another, and set them relation "greater then or equals" will be helpful
    • BergP
      BergP over 11 years
      programmatically for example with using this method of NSLayoutConstraint: + (id)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c
    • Goles
      Goles almost 10 years
      I open sourced a generic UITabBar replacement that generalizes this approach using Auto Layout. You could check out the tests to see how I generate my Auto Layout constraints. GGTabBar
  • nothappybob
    nothappybob over 11 years
    You sir, are a lifesaver. I did not understand #1 until now. Interface builder is driving me nuts, but I thought it was able to do all of the same things. I prefer using code anyway, so this is perfect, and I'll get right on the category methods.
  • Dorian Roy
    Dorian Roy over 11 years
    I found that this only works when you resize the superview so that the spacing takes exactly the min or max values. When you resize it to a value somewhere between, the subviews don't get distributed evenly.
  • paulmelnikow
    paulmelnikow about 11 years
    I've used the solution of hidden spacer views, which works well.
  • paulmelnikow
    paulmelnikow about 11 years
    This works. In fact spacer views can be hidden and they'll still produce the correct layout.
  • carlos_ms
    carlos_ms about 11 years
    It worked by removing all constraints and immediately after, adding the Width and Height constraints to those objects, then calling the method constraintsForEvenDistributionOfItems:relativeToCenterOfItem‌​:vertically:
  • Ben Dolman
    Ben Dolman about 11 years
    @carlos_ms I'd love to see your code, because when I try with an even number of items this code works perfectly when switching device orientations. Are you sure you don't inadvertently have some other constraints (such as autoresizing constraints) that are causing a conflict?
  • Ben Dolman
    Ben Dolman about 11 years
    Here's an example of creating 4 labels and evenly spacing them along the vertical axis: gist.github.com/bdolman/5379465
  • shulmey
    shulmey almost 11 years
    This was super helpful. As was the comment about just marking the spacer views as hidden. Then you can still see them in your storyboard/xib, but they don't show up in your app. Very nice.
  • smileyborg
    smileyborg over 10 years
    While definitely pretty close, this does not quite solve the exact problem asked (keeping the view size the same with even spacing between subviews). This solution is the refined general case answer.
  • jrturton
    jrturton over 10 years
    Nice work! Though you should probably rename the repo so you can make it available via cocoapods.
  • Desty
    Desty over 10 years
    I got it nearly working after several hours of repeated attempts. Unfortunately Xcode keeps deleting my top-space-to-button and bottom-space constraints, breaking the chain between buttons and spacers, and replacing them with arbitrary top-space-to-superview constraints which are useless.
  • Kamil Nomtek.com
    Kamil Nomtek.com over 10 years
    The same approach works for me in horizontal layout - I have fixed width views separated by equal width spacer views. It's great that I don't need to mix it with any code. Thanks!
  • nothappybob
    nothappybob about 10 years
    Thanks! I've revisited this problem in a new project and have found it to be a more accurate solution to my problem. Plus it has the benefit of being entirely in interface builder.
  • kraftydevil
    kraftydevil over 9 years
    I ask because I can't find the constraint menu in #4 (First Item, Second Item, Relation, etc) after doing the previous steps. After choosing the size inspector, all I see are these submenus: View, Content Hugging Priority, Content Compression Resistance Priority, and Constraints
  • kraftydevil
    kraftydevil over 9 years
    my mistake was that I didn't realize I could find the constraint in the Document Outline. #5 should be 'Attributes' inspector. It is originally named "Vertical Space".
  • SmileBot
    SmileBot over 9 years
    @kraftydevil I changed it to "Attributes Inspector". My bad. Did it work for you? I find this very powerful. I solved quite a complex layout using this. No spacers needed.
  • kraftydevil
    kraftydevil over 9 years
    I'm still struggling with it. I think you must be using Xcode 6 because there is no 'wC hAny' option in Xcode 5 constraints. When I follow your instructions, all of the subviews get stuck to the top center.
  • SmileBot
    SmileBot over 9 years
    No need to use spacers. See my answer below.
  • Timur Kuchkarov
    Timur Kuchkarov over 9 years
    @kraftydevil AFAIK Bottom layout guide only appear in storyboards, not in xibs. Use 'Bottom Space to Container' in xibs.
  • SmileBot
    SmileBot over 9 years
    @kraftydevil I'll add that to the answer. I rarely use nibs so wasn't aware of this.
  • SarahR
    SarahR over 9 years
    Brilliant! Thanks for working this out. Now I can stop using the tedious nested views method.
  • Benjohn
    Benjohn over 9 years
    I like this approach. On it's own, it won't make the views squeeze if they don't all fit (imagine using it for square buttons over the width of a screen). However, it should be easy to also constrain the view width to be no more than available-width / number-of-views
  • Benjohn
    Benjohn over 9 years
    Oh – also worth noting that IB in Xcode 6 supports ratio multipliers, so you can put in things like "1:4", "2:5", "3:4", etc.
  • SmileBot
    SmileBot over 9 years
    @kfmfe04 The same idea could be translated to the visual format language too. :)
  • hyperspasm
    hyperspasm over 9 years
    Perfect! This is a much better solution than the 'chain of equal spacer views' technique, which always gives me problems with complex interdependent constraint priorities. Thanks for pointing out how the bottom layout guide can be used in conjunction with positional constraint multipliers.
  • kfmfe04
    kfmfe04 over 9 years
    Trying to figure out how to do this horizontally - like first TextView 1/3 of the way from the left, and a second TextView 2/3 (rest of the way)... ...(EDIT) got it - do the first 1/3 (left one) Trailing-Space to Right Margin, reverse, zero, assign like before...
  • QED
    QED over 9 years
    I like this better in principle but can't make it work in a subview. Multiplier won't take a decimal number (reverts to 1) and fractions don't produce expected results.
  • SmileBot
    SmileBot about 9 years
    I used the section formula. Here's the scratch notes if you're interested evernote.com/shard/s28/sh/ecd21f8d-1cc5-4059-ab15-54fd75fce9‌​bc/…
  • SmileBot
    SmileBot about 9 years
    @Rivera Actually, the ratios is a great suggestion! And yeah size classes have no bearing on this. I just happened to be in compact width. Did I say something misleading?
  • Rivera
    Rivera about 9 years
    Nothing misleading really, just that I've seen so many bugs lately due to Size Classes misuse.
  • Rivera
    Rivera about 9 years
    Also step 6 is not needed, just inverse the ratio 3:4 -> 4:3.
  • João Nunes
    João Nunes about 9 years
    i dont think this works as the OP wants, when you make ratios it wont make the same as using spacers. with ratios there will be more space between the border than the labels. With spacers the space is equal betwen labels and borders
  • SmileBot
    SmileBot about 9 years
    @JoãoNunes Make sure you're not attaching to the top layout guide. If you want even spacing including margins attach to the superview instead.
  • SmileBot
    SmileBot about 9 years
    @JoãoNunes It's working for me no problem with 50x50 buttons. I just did a scratch project. Make sure you give the buttons a height and width. The other thing is remember you can always adjust the constant if you need to.
  • nr5
    nr5 about 9 years
    What if the middle view is a scrollview nd I want its height to grow some more(current height is 20, but can be <= 40) as the device height increases?
  • wod
    wod almost 9 years
    This solutions didn't work with me because application support Arabic & English Language . Does this solution supported RTL & LTR?
  • wod
    wod almost 9 years
    This solution will not reflect design if you switch to another language
  • Mete
    Mete almost 9 years
    @wod What do you mean? Switch what to another language? And what breaks exactly?
  • SmileBot
    SmileBot almost 9 years
    @wod I don't see why it wouldn't. Can you explain the issue a bit more in detail. Are you able to get the LTR version working?
  • SmileBot
    SmileBot almost 9 years
    @Nikita Ivaniushchenko I think address the case where you increase button heights in the comments May 11. Check it out.
  • Nikita Ivaniushchenko
    Nikita Ivaniushchenko almost 9 years
    Yeah, but it's not possible to do anything with views that use intrinsic content size in IB-only solution, so spacer views are better solution in this case and these 2 solutions are not equal
  • eager to learn
    eager to learn almost 9 years
    Has anyone had luck getting this to work with elements spacing out in a subview? I tried it, and it works when the buttons are in the root view, but when my layout requires the buttons to be in a subview, and it doesn't seem like changing the modifier does anything at that point.
  • SmileBot
    SmileBot almost 9 years
    @NikitaIvaniushchenko Why would there be any issues using intrinsic content size? May 11 I give the answer of how to adjust for bigger buttons. I've tried it. It works.
  • Nikita Ivaniushchenko
    Nikita Ivaniushchenko almost 9 years
    Anyway, this solution fails with variable-length localized texts
  • ucangetit
    ucangetit almost 9 years
    what if elements are not the same size? this pattern won't work correctly right?
  • Mete
    Mete almost 9 years
    they'll all be centred at the right places, sure. If you want them to have different dimensions, but the same amount of space from edge to edge, you will need to do this a different way of course!
  • ucangetit
    ucangetit almost 9 years
    using spacers is overly complicated. See my answer below using multipliers. stackoverflow.com/questions/13075415/…
  • Ricardo Sanchez-Saez
    Ricardo Sanchez-Saez almost 9 years
    How to do this programmatically? I've been trying to reproduce with no luck.
  • Afzaal Ahmad
    Afzaal Ahmad over 8 years
    I follow this tutorial for horizontal spacing youtube.com/watch?v=WTMpJJ9Ofm8
  • Sreejith
    Sreejith over 8 years
    what if I have 4 buttons? How can I space the buttons?
  • myles
    myles over 8 years
    I don't understand how this solution is correct? The gap between the edge of the screen and outer elements is different to the gap between outer elements and the centre element?
  • Mete
    Mete over 8 years
    @Sreejith the answer is generic, just apply it to the 4 element case.
  • noobsmcgoobs
    noobsmcgoobs over 8 years
    This is the best solution when using different sized views that must be centered.
  • Jacob
    Jacob over 8 years
    Can this be done if the buttons/labels have different widths? I have tried it and it makes the spacing off a little. Any suggestions?
  • SmileBot
    SmileBot over 8 years
    @Jacob do you mean different heights? Check the May 11 comment.
  • Jacob
    Jacob over 8 years
    @smileBot no, I mean different widths.
  • Mehul Thakkar
    Mehul Thakkar over 8 years
    After release of iOS 9 having UIStackView as its best option for doing this task, Apple have removed this page
  • user3344977
    user3344977 over 8 years
    For horizontal spacing, simply set trailing constraints to the superview for each subview, and then set the ratio multiplier eg. 5:1, 5:2, 5:3, etc. moving from left to right.
  • WINSergey
    WINSergey over 8 years
    It works before iOS9. Right answer, but if you chanle number of items - you feel youself sad((.
  • jheriko
    jheriko over 8 years
    this is a deliciously well put together answer. regarding your comment about people having problems with gifs... the reason gif persists so indefinitely is that its the lowest common denominator of animated stuff on the web. it always works for everyone to a very good approximation. :) thanks for your efforts. :)
  • SmileBot
    SmileBot over 8 years
    @jheriko The reason I made the comments about gifs is I once had someone down vote because I included gifs. I think it was a troll thing.
  • ZiggyST
    ZiggyST about 8 years
    Yep, It's sad that this doesn't have retro compatibility, so until your app needs support for IOS8 @Mete response is the best for now.
  • rebellion
    rebellion about 8 years
    I didn't get this to work with two elemtents, using for multiplier 2:1 for the first item and 2:3 for the second.
  • Hamzah Malik
    Hamzah Malik about 8 years
    I actually had to do 1/n 3/n 5/n 7/n upto (2n-1)/n
  • Ces
    Ces about 8 years
    This didn't for me "as is" I had to reverse the multipliers (ej. the 3:1 from the first item converts to 1:3). Just throwing this out there if it helps anyone.
  • Joel
    Joel about 8 years
    This solution only works for an odd number of views. See Mete's answer below for a formula that works with odd and even number of views to space.
  • Mete
    Mete almost 8 years
    edited to explain why some people have to reverse the multiplier.
  • Mike Simz
    Mike Simz almost 8 years
    I have a case with 4 labels and 5 spacer views and it's not working. Everything is perfect until I make the equal widths connection with Spacer View 1 and Last Spacer View I'm getting constraint errors
  • DivideByZer0
    DivideByZer0 over 7 years
    This is >=iOS 9.0 only so check your deployment target before you start implementing :) Great to see this has been added in though!
  • JAL
    JAL over 7 years
    Elaborating on how this code answers the questions would help future visitors.
  • RainCast
    RainCast over 7 years
    Thanks for the nice answer, works well on iOS10. Yeah, just for other's reference, I have 4 buttons. And the ratio I am using are: 1/4, 3/4, 5/4, 7/4. Works like a charm! I am amazed how Apple makes everything so complicated for UI stuff, lol.
  • Josh O'Connor
    Josh O'Connor almost 7 years
    Don't understand the downvote, my solution is thinking outside the box. Why go through the headache of creating a list with autolayout when cocoa gives you a list in UITableView?
  • rd_
    rd_ almost 6 years
    this should fail with RTL Arabic
  • SmileBot
    SmileBot almost 6 years
    @rd_ I don't think RTL will matter in this case. But these days you should be using stackviews. This solution predates stackviews.
  • Jingshao Chen
    Jingshao Chen over 5 years
    2018 now, use stack view
  • Nick Turner
    Nick Turner over 5 years
    This is a hack. Throw in another control and you have to recalculate the spacing which is the whole point of AutoLayout. So with a demo yeah looks good. In real life this is a disaster.
  • SmileBot
    SmileBot over 5 years
    @NikitaIvaniushchenko You should be using stackviews these days unless you can't for some reason. This way of doing it is no longer needed.