16

I've looked through many answers and they all seem very complex! Most recently I was looking at this answer although I'd prefer not to have to put my buttons inside views.

I have 6 UIButtons that are all the same dimensions. I want to space them evenly horizontally across the full width and at the bottom of my root view controller.

|                                      |
|                                      |
|  [b1]  [b2]  [b3]  [b4]  [b5]  [b6]  |
________________________________________

What is the simplest way to achieve this programmatically?

Community
  • 1
  • 1
fxfuture
  • 1,814
  • 3
  • 22
  • 38
  • Your solution isn't the simplest way, and if you add or remove buttons, you have to calculate your multipliers again. See my answer for the correct way to do it. Also my solution fulfils your other requirement that you don't want to nest your buttons in views. – bandejapaisa Sep 11 '13 at 08:51

8 Answers8

20

I think this is simpler than the link on the accepted answer. Ok, firstly lets create some buttons:

UIButton *button1 = [UIButton buttonWithType:UIButtonTypeSystem];
button1.translatesAutoresizingMaskIntoConstraints = NO;
[button1 setTitle:@"Btn1" forState:UIControlStateNormal];

... do that 6 times for 6 buttons, however you like then add them to the view:

[self.view addSubview:button1];
[self.view addSubview:button2];
[self.view addSubview:button3];
[self.view addSubview:button4];
[self.view addSubview:button5];
[self.view addSubview:button6];

Fix one button to the bottom of your view:

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0]];

Then tell all the buttons to be equal width and spread out equally across the width:

NSDictionary *views = NSDictionaryOfVariableBindings(button1, button2, button3, button4, button5, button6);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[button1][button2(==button1)][button3(==button1)][button4(==button1)][button5(==button1)][button6(==button1)]|" options:NSLayoutFormatAlignAllBottom metrics:nil views:views]];

Result:

enter image description here

bandejapaisa
  • 24,838
  • 13
  • 86
  • 110
  • Thanks bandejapaisa - this is much more elegant! – fxfuture Sep 11 '13 at 12:05
  • 1
    This somehow didn't work for me, so I've created this helper class in Swift: [DistributeViewsEvenly-Swift](https://github.com/chrisben/DistributeViewsEvenly-Swift) that uses invisible spacers, as recommended by Apple in [this documentation](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html#//apple_ref/doc/uid/TP40010853-CH5-SW8) – chrisben Jan 18 '15 at 18:09
  • @chrisben I think your solution warrants a separate answer as opposed to just a comment. – Johan Kool Mar 19 '15 at 13:21
  • How can we set 'Y' position of buttons like button1.frame.origin.y = 50 – Narasimha Nallamsetty May 18 '16 at 12:06
  • I have a 5 buttons in a row which dimentions are : 75 x 50 full fill to 4.7 inch screen. When i tried to constraint it fit on 4 inch too but ratio has just broken. How to obey ratio while scaling buttons for smaller screens too? – Onder OZCAN Jun 02 '16 at 12:30
8

UIStackView was introduced in iOS9.

https://developer.apple.com/documentation/uikit/uistackview

Cœur
  • 32,421
  • 21
  • 173
  • 232
Albin Stigo
  • 1,932
  • 2
  • 16
  • 14
3

I know this question is a bit old, but I've been programming in iOS for a few years now and dislike using autolayout. So I wrote a helper method to evenly space UIButtons horizontally and center them vertically within a UIView. This works great for menu bars.

- (void) evenlySpaceTheseButtonsInThisView : (NSArray *) buttonArray : (UIView *) thisView {
    int widthOfAllButtons = 0;
    for (int i = 0; i < buttonArray.count; i++) {
        UIButton *thisButton = [buttonArray objectAtIndex:i];
        [thisButton setCenter:CGPointMake(0, thisView.frame.size.height / 2.0)];
        widthOfAllButtons = widthOfAllButtons + thisButton.frame.size.width;
    }

    int spaceBetweenButtons = (thisView.frame.size.width - widthOfAllButtons) / (buttonArray.count + 1);

    UIButton *lastButton = nil;
    for (int i = 0; i < buttonArray.count; i++) {
        UIButton *thisButton = [buttonArray objectAtIndex:i];
        if (lastButton == nil) {
            [thisButton setFrame:CGRectMake(spaceBetweenButtons, thisButton.frame.origin.y, thisButton.frame.size.width, thisButton.frame.size.height)];
        } else {
            [thisButton setFrame:CGRectMake(spaceBetweenButtons + lastButton.frame.origin.x + lastButton.frame.size.width, thisButton.frame.origin.y, thisButton.frame.size.width, thisButton.frame.size.height)];
        }

        lastButton = thisButton;
    }
}

Just copy and paste this method into any view controller. Then to access it, I first created all the buttons I wanted, then called the method with all of the buttons in an array, along with the UIView I wanted it in.

[self evenlySpaceTheseButtonsInThisView:@[menuButton, hierarchyMenuButton, downButton, upButton] :menuView];

The advantage of this method is that you don't need autolayout and it's super easy to implement. The disadvantage is that if your app works in landscape and portrait, you will need to make sure to call this method again after the view has been rotated.

Kjell
  • 751
  • 5
  • 17
1

I usually do something like:

int numButtons = 6;
float gap = 10.0f;
float y = 50.0f;
float width = (self.view.frame.size.width - gap * (numButtons + 1)) / numButtons;
float height = 60.0f;
for (int n=0;n<numButtons;n++) {
    float x = gap * (n+1) + width * n;
    UIButton *button = [self.buttons objectAtIndex:n]; //Or get button some other way/make button.
    [button setFrame:CGRectMake(x,y,width,height)];
}

You can set numButtons to however many buttons you want in the row, and if you have an array of buttons, you can set it to the length of that array.

The y is just whatever y coordinate you want and the same goes for the height and gap, which is the space between buttons. The width is just a calculation of how wide each button will be based on the screen width and the gap space you want between each button.

Jsdodgers
  • 5,183
  • 2
  • 18
  • 35
1

You can send the Label/button/view's as array to this methods and arrange the button frames.

-(void)arrangeViewsXposition:(NSArray*)anyView y:(CGFloat)y width:(CGFloat)width height:(CGFloat)height mainViewWdith:(UIView*)mainview {
int count = (int)anyView.count;
CGFloat widthTemp = mainview.bounds.size.width, temp1 = widthTemp-(width*count),space = temp1/(count+1);
for (int i = 0; i<count; i++) {
    UIView *btnTemp = (UIView*)[anyView objectAtIndex:i];
    if (btnTemp) {
        btnTemp.frame = CGRectMake(space+((space+width)*i), y, width, height);
    }
}
}

Call this method like this:

[self arrangeViewsXposition:@[btnSave,btnCancel] y:5 width:80 height:30 mainViewWdith:footerView];
GNANA VEL
  • 21
  • 4
0

There are a number of decent auto-layout solutions discussed in this question:

Evenly space multiple views within a container view

Basically it can be a lot of manual constraint-definition code, which can be conveniently wrapped in a category extension for encapsulation/reuse.

Community
  • 1
  • 1
TomSwift
  • 38,979
  • 11
  • 115
  • 147
0

I just cooked this up in Swift using Cartography.

func disributeEvenlyAcrossWithCartography(views: [UIView], enclosingBox: UIView, spacer: CGFloat = 10.0) {
    var priorView = UIView() // never null
    for (index, view) in views.enumerate() {
        constrain(view, priorView, enclosingBox) { view, prior, enclosingBox in
            view.height == enclosingBox.height
            view.centerY == enclosingBox.centerY
            if index == 0 {
                view.width == enclosingBox.width / CGFloat(views.count) - spacer
                view.left == enclosingBox.left
            } else {
                view.left == prior.right + (spacer + spacer / CGFloat(views.count - 1))
                view.width == prior.width
            }
        }
        priorView = view
    }
}

Result with 5 views and a small spacer:

enter image description here

Dan Rosenstark
  • 64,546
  • 54
  • 267
  • 405
0

This is my first post on Stackoverflow. This site has been very helpful in getting me up to speed on Xcode and swift. Anyway, below is my quick-and-dirty fix to the above question.

Pin the left most button and the right most button, respectively. Create "dummy" labels between each of the buttons. Set all of the "dummy" labels as equal widths. For each of the "dummy" label, add constraints (0 to the nearest neighbor on the left, and 0 to the nearest neighbor on the right). The buttons will then be equally spaced throughout, even when you change from portrait to landscape orientation.