Evenly space multiple views within a container view

后端 未结 29 2477
佛祖请我去吃肉
佛祖请我去吃肉 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:25

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

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

    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
    
    0 讨论(0)
  • 2020-11-22 06:26

    I set a width value just for the first item (>= a width) and a minimum distance between each item (>= a distance). Then I use Ctrl to drag second, third... item on the first one to chain dependencies among the items.

    enter image description here

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

    I wanted to horizontally align 5 images, so I ended up following Mete's response with a small difference.

    The first image will be centered horizontally in container equal to 0 and a multiplier of 1:5:

    The second image will be centered horizontally in container equal to 0 and a multiplier of 3:5:

    And like that for the rest of the images. For example, the fifth (and last) image will be centered horizontally in container equal to 0 and a multiplier of 9:5:

    As Mete explained, the order goes 1, 3, 5, 7, 9, etc. The positions follow the same logic: the first position is 1, then space, then the next position 3, and so on.

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

    Android has a method of chaining views together in its constraint based layout system that I wanted to mimic. Searches brought me here but none of the answers quite worked. I didn't want to use StackViews because they tend to cause me more grief down the line than they save up front. I ended up creating a solution that used UILayoutGuides placed between the views. Controlling their width's allows different types of distributions, chain styles in Android parlance. The function accepts a leading and trailing anchor instead of a parent view. This allows the chain to be placed between two arbitrary views rather than distributed inside of the parent view. It does use UILayoutGuide which is only available in iOS 9+ but that shouldn't be a problem anymore.

    public enum LayoutConstraintChainStyle {
        case spread //Evenly distribute between the anchors
        case spreadInside //Pin the first & last views to the sides and then evenly distribute
        case packed //The views have a set space but are centered between the anchors.
    }
    
    public extension NSLayoutConstraint {
    
        static func chainHorizontally(views: [UIView],
                                      leadingAnchor: NSLayoutXAxisAnchor,
                                      trailingAnchor: NSLayoutXAxisAnchor,
                                      spacing: CGFloat = 0.0,
                                      style: LayoutConstraintChainStyle = .spread) -> [NSLayoutConstraint] {
        var constraints = [NSLayoutConstraint]()
        guard views.count > 1 else { return constraints }
        guard let first = views.first, let last = views.last, let superview = first.superview else { return constraints }
    
        //Setup the chain of views
        var distributionGuides = [UILayoutGuide]()
        var previous = first
        let firstGuide = UILayoutGuide()
        superview.addLayoutGuide(firstGuide)
        distributionGuides.append(firstGuide)
        firstGuide.identifier = "ChainDistribution\(distributionGuides.count)"
        constraints.append(firstGuide.leadingAnchor.constraint(equalTo: leadingAnchor))
        constraints.append(first.leadingAnchor.constraint(equalTo: firstGuide.trailingAnchor, constant: spacing))
        views.dropFirst().forEach { view in
            let g = UILayoutGuide()
            superview.addLayoutGuide(g)
            distributionGuides.append(g)
            g.identifier = "ChainDistribution\(distributionGuides.count)"
            constraints.append(contentsOf: [
                g.leadingAnchor.constraint(equalTo: previous.trailingAnchor),
                view.leadingAnchor.constraint(equalTo: g.trailingAnchor)
            ])
            previous = view
        }
        let lastGuide = UILayoutGuide()
        superview.addLayoutGuide(lastGuide)
        constraints.append(contentsOf: [lastGuide.leadingAnchor.constraint(equalTo: last.trailingAnchor),
                                        lastGuide.trailingAnchor.constraint(equalTo: trailingAnchor)])
        distributionGuides.append(lastGuide)
    
        //Space the according to the style.
        switch style {
        case .packed:
            if let first = distributionGuides.first, let last = distributionGuides.last {
                constraints.append(first.widthAnchor.constraint(greaterThanOrEqualToConstant: spacing))
                constraints.append(last.widthAnchor.constraint(greaterThanOrEqualToConstant: spacing))
                constraints.append(last.widthAnchor.constraint(equalTo: first.widthAnchor))
                constraints.append(contentsOf:
                    distributionGuides.dropFirst().dropLast()
                        .map { $0.widthAnchor.constraint(equalToConstant: spacing) }
                    )
            }
        case .spread:
            if let first = distributionGuides.first {
                constraints.append(contentsOf:
                    distributionGuides.dropFirst().map { $0.widthAnchor.constraint(equalTo: first.widthAnchor) })
            }
        case .spreadInside:
            if let first = distributionGuides.first, let last = distributionGuides.last {
                constraints.append(first.widthAnchor.constraint(equalToConstant: spacing))
                constraints.append(last.widthAnchor.constraint(equalToConstant: spacing))
                let innerGuides = distributionGuides.dropFirst().dropLast()
                if let key = innerGuides.first {
                    constraints.append(contentsOf:
                        innerGuides.dropFirst().map { $0.widthAnchor.constraint(equalTo: key.widthAnchor) }
                    )
                }
            }
        }
    
        return constraints
    }
    
    0 讨论(0)
  • 2020-11-22 06:28

    Building on Ben Dolman's answer, this distributes the views more evenly (with padding, etc):

    +(NSArray *)constraintsForEvenDistributionOfItems:(NSArray *)views
                               relativeToCenterOfItem:(id)toView vertically:(BOOL)vertically
    {
        NSMutableArray *constraints = [NSMutableArray new];
        NSLayoutAttribute attr = vertically ? NSLayoutAttributeCenterY : NSLayoutAttributeCenterX;
    
        CGFloat min = 0.25;
        CGFloat max = 1.75;
        CGFloat d = (max-min) / ([views count] - 1);
        for (NSUInteger i = 0; i < [views count]; i++) {
            id view = views[i];
            CGFloat multiplier = i * d + min;
            NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view
                                                                          attribute:attr
                                                                          relatedBy:NSLayoutRelationEqual
                                                                             toItem:toView
                                                                          attribute:attr
                                                                         multiplier:multiplier
                                                                           constant:0];
            [constraints addObject:constraint];
        }
    
        return constraints;
    }
    
    0 讨论(0)
提交回复
热议问题