Evenly space multiple views within a container view

后端 未结 29 2481
佛祖请我去吃肉
佛祖请我去吃肉 2020-11-22 06:14

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 proje

相关标签:
29条回答
  • 2020-11-22 06:28

    swift 3 version

    let _redView = UIView()
            _redView.backgroundColor = UIColor.red
            _redView.translatesAutoresizingMaskIntoConstraints = false
    
            let _yellowView = UIView()
            _yellowView.backgroundColor = UIColor.yellow
            _yellowView.translatesAutoresizingMaskIntoConstraints = false
    
            let _blueView = UIView()
            _blueView.backgroundColor = UIColor.blue
            _blueView.translatesAutoresizingMaskIntoConstraints = false
    
            self.view.addSubview(_redView)
            self.view.addSubview(_yellowView)
            self.view.addSubview(_blueView)
    
            var views = ["_redView": _redView, "_yellowView": _yellowView, "_blueView":_blueView]
    
            var nslayoutConstraint_H = NSLayoutConstraint.constraints(withVisualFormat: "|->=0-[_redView(40)]->=0-[_yellowView(40)]->=0-[_blueView(40)]->=0-|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views)
            self.view.addConstraints(nslayoutConstraint_H)
    
            var nslayoutConstraint_V = NSLayoutConstraint.constraints(withVisualFormat: "V:[_redView(60)]", options: NSLayoutFormatOptions.init(rawValue: 0), metrics: nil, views: views)
            self.view.addConstraints(nslayoutConstraint_V)
    
    
            let constraint_red = NSLayoutConstraint.init(item: self.view, attribute: .centerY, relatedBy: .equal, toItem: _redView, attribute: .centerY, multiplier: 1, constant: 0)
            self.view.addConstraint(constraint_red)
    
            let constraint_yellow = NSLayoutConstraint.init(item: self.view, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .centerX, multiplier: 1, constant: 0)
            self.view.addConstraint(constraint_yellow)
    
            let constraint_yellow1 = NSLayoutConstraint.init(item: _redView, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .leading, multiplier: 0.5, constant: 0)
            self.view.addConstraint(constraint_yellow1)
    
            let constraint_yellow2 = NSLayoutConstraint.init(item: _blueView, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .leading, multiplier: 1.5, constant: 40)
            self.view.addConstraint(constraint_yellow2)
    
    0 讨论(0)
  • 2020-11-22 06:29

    Here is a solution that will vertically center any number of subviews, even if they have unique sizes. What you want to do is make a mid-level container, center that in the superview, then put all the subviews in the container and arrange them with respect to one another. But crucially you also need to constrain them to the top and bottom of the container, so the container can be correctly sized and centered in the superview. By figuring the correct height from its subviews, the container can be vertically centered.

    In this example, self is the superview in which you are centering all the subviews.

    NSArray *subviews = @[ (your subviews in top-to-bottom order) ];
    
    UIView *container = [[UIView alloc] initWithFrame:CGRectZero];
    container.translatesAutoresizingMaskIntoConstraints = NO;
    for (UIView *subview in subviews) {
        subview.translatesAutoresizingMaskIntoConstraints = NO;
        [container addSubview:subview];
    }
    [self addSubview:container];
    
    [self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual
                                                        toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f]];
    [self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual
                                                        toItem:self attribute:NSLayoutAttributeRight multiplier:1.0f constant:0.0f]];
    [self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual
                                                        toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f]];
    
    if (0 < subviews.count) {
        UIView *firstSubview = subviews[0];
        [container addConstraint:[NSLayoutConstraint constraintWithItem:firstSubview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
                                                                 toItem:container attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f]];
        UIView *lastSubview = subviews.lastObject;
        [container addConstraint:[NSLayoutConstraint constraintWithItem:lastSubview attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual
                                                                 toItem:container attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f]];
    
        UIView *subviewAbove = nil;
        for (UIView *subview in subviews) {
            [container addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual
                                                                     toItem:container attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0.0f]];
            if (subviewAbove) {
                [container addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
                                                                         toItem:subviewAbove attribute:NSLayoutAttributeBottom multiplier:1.0f constant:10.0f]];
            }
            subviewAbove = subview;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 06:30

    Check out the open source library PureLayout. It offers a few API methods for distributing views, including variants where the spacing between each view is fixed (view size varies as needed), and where the size of each view is fixed (spacing between views varies as needed). Note that all of these are accomplished without the use of any "spacer views".

    From NSArray+PureLayout.h:

    // NSArray+PureLayout.h
    
    // ...
    
    /** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (variable) in the dimension along the axis and will have spacing (fixed) between them. */
    - (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
                                    alignedTo:(ALAttribute)alignment
                             withFixedSpacing:(CGFloat)spacing;
    
    /** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (fixed) in the dimension along the axis and will have spacing (variable) between them. */
    - (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
                                    alignedTo:(ALAttribute)alignment
                                withFixedSize:(CGFloat)size;
    
    // ...
    

    Since it's all open source, if you're interested to see how this is achieved without spacer views just take a look at the implementation. (It depends on leveraging both the constant and multiplier for the constraints.)

    0 讨论(0)
  • 2020-11-22 06:32

    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.

    0 讨论(0)
  • 2020-11-22 06:33

    Late to the party but I have a working solution for creating a menu horizontally with spacing. It can be easily done using == in NSLayoutConstraint

    const float MENU_HEIGHT = 40;
    
    - (UIView*) createMenuWithLabels: (NSArray *) labels
        // labels is NSArray of NSString
        UIView * backgroundView = [[UIView alloc]init];
        backgroundView.translatesAutoresizingMaskIntoConstraints = false;
    
        NSMutableDictionary * views = [[NSMutableDictionary alloc] init];
        NSMutableString * format = [[NSMutableString alloc] initWithString: @"H:|"];
        NSString * firstLabelKey;
    
        for(NSString * str in labels)
        {
            UILabel * label = [[UILabel alloc] init];
            label.translatesAutoresizingMaskIntoConstraints = false;
            label.text = str;
            label.textAlignment = NSTextAlignmentCenter;
            label.textColor = [UIColor whiteColor];
            [backgroundView addSubview: label];
            [label fixHeightToTopBounds: MENU_HEIGHT-2];
            [backgroundView addConstraints: [label fixHeightToTopBounds: MENU_HEIGHT]];
            NSString * key = [self camelCaseFromString: str];
            [views setObject: label forKey: key];
            if(firstLabelKey == nil)
            {
                [format appendString: [NSString stringWithFormat: @"[%@]", key]];
                firstLabelKey = key;
            }
            else
            {
                [format appendString: [NSString stringWithFormat: @"[%@(==%@)]", key, firstLabelKey]];
            }
        }
    
        [format appendString: @"|"];
    
        NSArray * constraints = [NSLayoutConstraint constraintsWithVisualFormat: (NSString *) format
                                                                                   options: 0
                                                                                   metrics: nil
                                                                                     views: (NSDictionary *) views];
        [backgroundView addConstraints: constraints];
        return backgroundView;
    }
    
    0 讨论(0)
  • 2020-11-22 06:34

    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

    0 讨论(0)
提交回复
热议问题