自动布局让我的生活很困难。从理论上讲,当我转换的时候,它会非常有用,但我似乎一直在与它作斗争。

我做了一个演示项目来寻求帮助。有人知道如何使视图之间的空间增加或减少均匀,每当视图调整大小?

下面是三个标签(手动垂直对齐):

我想要的是,当我旋转时,它们均匀地调整间距(而不是视图大小)。默认情况下,顶部和底部视图向中心挤压:


当前回答

我的方法允许你在接口构建器中做这个。你要做的是创建“间隔视图”,你已经设置为匹配高度相等。然后在标签上添加顶部和底部约束(见截图)。

更具体地说,我在“Spacer View 1”上有一个顶部约束,以优先级低于1000的高度约束来superview,并且高度等于所有其他的“Spacer views”。'Spacer View 4'有一个用于superview的底部空间约束。每个标签都有各自的顶部和底部约束到其最近的“间隔视图”。

注意:确保你的标签上没有额外的顶部/底部空间限制;只有那些“空间视图”。这是可以满足的,因为顶部和底部的约束分别在'Space View 1'和'Spacer View 4'上。

废话1:我复制了我的视图,只是把它设置为横向模式,这样你就可以看到它工作了。

废话2:“间隔视图”本可以是透明的。

废话3:这种方法可以横向应用。

其他回答

看,没有间隔!

根据我最初回答的评论部分的建议,特别是@Rivera的有用建议,我简化了我的原始答案。

我用动图来说明这有多简单。我希望这些动图对你有帮助。为了防止你对动图有问题,我在下面附上了旧的答案和简单的屏幕截图。

产品说明:

1)添加按钮或标签。我用了3个按钮。

2)从每个按钮添加一个中心x约束到superview:

3)从每个按钮添加一个约束到底部布局约束:

4)调整上文第3条添加的约束如下:

A)选择约束; B)去掉常数(设为0), c)更改乘数如下:取按钮数量+ 1,从顶部开始,设置乘数为buttonCountPlus1:1,然后是buttonCountPlus1:2,最后是buttonCountPlus1:3。(我在下面的旧答案中解释了我从哪里得到这个公式,如果你感兴趣的话)。

5)这里是一个演示运行!

注意:如果你的按钮有较大的高度,那么你需要在常量值中补偿这一点,因为约束是从按钮的底部开始的。


旧的答案


不管苹果的文档和Erica Sadun的优秀书籍(Auto Layout Demystified)说什么,没有间隔器也可以均匀地分隔视图。这在IB和代码中是非常简单的,适用于任意数量的元素。你所需要的是一个数学公式,叫做“剖面公式”。做起来比解释起来简单。我将尽我最大的努力在IB中演示它,但在代码中也很容易做到。

在这个例子中,你会

1)首先设置每个标签有一个中心约束。这很简单。只需从每个标签control拖动到底部。

2)按住shift,因为你也可以添加我们将要使用的另一个约束,即“底部空间到底部布局指南”。

3)选择“bottom space to bottom layout guide”,“在容器中水平居中”。对所有3个标签都这样做。

基本上,如果我们将我们想要确定其坐标的标签除以标签总数加1,那么我们就有一个数字可以加到IB中得到动态位置。我简化了这个公式,但你可以用它来设置水平间距或者同时设置垂直和水平间距。这是超级强大的!

这是乘数。

Label1 = 1/4 = .25,

Label2 = 2/4 = .5,

Label3 = 3/4 = .75

(编辑:@Rivera评论说,你可以简单地直接在乘数字段使用比率,xCode与做数学!)

4)所以,让我们选择Label1,并选择底部约束。是这样的:

5)在属性检查器中选择“第二项”。

6)在下拉菜单中选择“反转第一项和第二项”。

7)将常数归零,wC值为any。(如果需要,可以在这里添加偏移量)。

8)这是关键部分:在乘数字段中添加我们的第一个乘数0.25。

9)当你在它设置顶部的“第一项”为“CenterY”,因为我们想把它居中到标签的y中心。这是所有这些应该看起来的样子。

10)对每个标签重复这个过程,并插入相应的乘数:Label2为0.5,Label3为0.75。这里是最终产品在所有方向与所有紧凑的设备!超级简单。我一直在研究许多涉及大量代码和间隔的解决方案。这无疑是我在这个问题上看到的最好的解决办法。

更新:@kraftydevil补充说底部布局指南只出现在故事板中,而不是在xib中。在xibs中使用'Bottom Space to Container'。好赶上!

我一直在坐过山车,爱自动布局和讨厌它。喜欢它的关键似乎是接受以下几点:

接口构建器的编辑和“有帮助的”自动创建约束在大多数情况下几乎无用 创建类别来简化常见操作是一种拯救,因为代码是如此重复和冗长。

也就是说,你正在尝试的不是直接的,在接口构建器中很难实现。这在代码中很简单。这段代码,在viewDidLoad,创建和定位三个标签,你需要他们:

// 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]];

我说过,这段代码通过UIView中的一些类别方法简化了很多,但为了清晰起见,这里我做了很多。

对于感兴趣的人来说,这个类别在这里,它有一个方法,可以沿着特定的轴均匀地间隔一个视图数组。

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
}

我的方法允许你在接口构建器中做这个。你要做的是创建“间隔视图”,你已经设置为匹配高度相等。然后在标签上添加顶部和底部约束(见截图)。

更具体地说,我在“Spacer View 1”上有一个顶部约束,以优先级低于1000的高度约束来superview,并且高度等于所有其他的“Spacer views”。'Spacer View 4'有一个用于superview的底部空间约束。每个标签都有各自的顶部和底部约束到其最近的“间隔视图”。

注意:确保你的标签上没有额外的顶部/底部空间限制;只有那些“空间视图”。这是可以满足的,因为顶部和底部的约束分别在'Space View 1'和'Spacer View 4'上。

废话1:我复制了我的视图,只是把它设置为横向模式,这样你就可以看到它工作了。

废话2:“间隔视图”本可以是透明的。

废话3:这种方法可以横向应用。

迟到的派对,但我有一个工作的解决方案,创建一个菜单水平间距。在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;
}