【问题标题】:Using Auto Layout to define a potentially 0 value使用自动布局定义一个潜在的 0 值
【发布时间】:2026-01-04 00:40:01
【问题描述】:

我有一个包含三个子视图的视图。这些子视图应缩放以填充视图的整个宽度。

我尝试过使用这个约束:

NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:smallView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeWidth multiplier:((1 / 3) * idx) constant:0.0];

这会导致异常:

+[NSLayoutConstraint constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:]: 乘数 0 或 nil 第二项连同位置 第一个属性创建了一个非法的位置约束 为常数。位置属性必须成对指定'

我理解它在抱怨什么,但我无法理解这种行为似乎令人费解。我希望我的第一个视图的 x 原点为 0。

还有其他方法可以让它工作吗?

【问题讨论】:

    标签: ios autolayout


    【解决方案1】:
    1. 您不能使位置NSLayoutAttributeLeft 等于宽度NSLayoutAttributeWidth
    2. 异常表示您使用了乘数 0。因为您将乘数参数设置为((1 / 3) * idx),而我猜idxint 类型。所以表达式 ((1 / 3) * idx) 的计算结果为 0。如果要使用浮点乘数,可以通过将三个数字中的任何一个更改为浮点类型来实现,例如,((1 / 3.0) * idx)

    如果您只有 3 个要布局的子视图,则可视化格式可能是一种更简单易读的应用方式。假设您希望 3 个子视图缩放以填充 containerView,它们都具有相同的宽度。代码如下:

    UIView *smallView0 = [UIView new];
    smallView0.backgroundColor = [UIColor redColor];
    smallView0.translatesAutoresizingMaskIntoConstraints = NO;
    [containerView addSubview:smallView0];
    
    UIView *smallView1 = [UIView new];
    smallView1.backgroundColor = [UIColor blueColor];
    smallView1.translatesAutoresizingMaskIntoConstraints = NO;
    [containerView addSubview:smallView1];
    
    UIView *smallView2 = [UIView new];
    smallView2.backgroundColor = [UIColor greenColor];
    smallView2.translatesAutoresizingMaskIntoConstraints = NO;
    [containerView addSubview:smallView2];
    
    NSDictionary *bindings = NSDictionaryOfVariableBindings(smallView0, smallView1, smallView2);
    
    // Layout the three subviews to fill the `containerView`, and, make theirs widths equal.
    NSArray *hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|[smallView0][smallView1(==smallView0)][smallView2(==smallView0)]|" options:0 metrics:nil views:bindings];
    
    [containerView addConstraints:hConstraints];
    
    // The constraints below are set to satisfy the vertical layout rules. You may have your own choice.
    NSArray *vConstraints0 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[smallView0]-|" options:0 metrics:nil views:bindings];
    NSArray *vConstraints1 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[smallView1]-|" options:0 metrics:nil views:bindings];
    NSArray *vConstraints2 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[smallView2]-|" options:0 metrics:nil views:bindings];
    [containerView addConstraints:vConstraints0];
    [containerView addConstraints:vConstraints1];
    [containerView addConstraints:vConstraints2];
    

    更一般地说,您可能有超过 3 个子视图,或者必须在运行时决定。对于这种情况,for 循环可能会有所帮助:

    UIView *previousView = nil;
    int countOfSubviews = 6;
    for (int i = 0; i < countOfSubviews; ++i) {
        UIView *smallView = [UIView new];
        [containerView addSubview:smallView];
        smallView.backgroundColor = (i % 2 == 0) ? [UIColor redColor] : [UIColor greenColor];
        smallView.translatesAutoresizingMaskIntoConstraints = NO;
    
        NSLayoutConstraint *fixedHeight = [NSLayoutConstraint constraintWithItem:smallView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:50];
        [smallView addConstraint:fixedHeight];
    
        if (i == 0) {
            NSLayoutConstraint *left = [NSLayoutConstraint constraintWithItem:smallView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
            [containerView addConstraint:left];
        } else {
            NSLayoutConstraint *internal = [NSLayoutConstraint constraintWithItem:smallView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:previousView attribute:NSLayoutAttributeRight multiplier:1 constant:0];
            [containerView addConstraint:internal];
        }
    
        if (i == countOfSubviews - 1) {
            NSLayoutConstraint *right = [NSLayoutConstraint constraintWithItem:smallView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeRight multiplier:1 constant:0];
            [containerView addConstraint:right];
        }
    
        NSLayoutConstraint *verticalPostiion = [NSLayoutConstraint constraintWithItem:smallView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
        [containerView addConstraint:verticalPostiion];
    
        if (previousView) {
            NSLayoutConstraint *equalWidth = [NSLayoutConstraint constraintWithItem:smallView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:previousView attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
            [containerView addConstraint:equalWidth];
        }
        previousView = smallView;
    }
    

    【讨论】:

    • 谢谢,这看起来是一个很有希望的答案。我几乎走上了这条路,但我真的想要一个可以在枚举循环中工作的解决方案。
    • 这可能有点棘手。我要发布代码:)
    【解决方案2】:

    如果您的部署目标是 iOS 9.0 或更高版本,将三个子视图放入 UIStackView 可能会更简单。默认的堆栈视图设置应该满足您的要求:水平布置三个视图,首尾相连,拉伸以填充堆栈视图的宽度,中间没有填充。

    【讨论】:

      【解决方案3】:

      正如您所理解的,您不能创建仅将坐标指定为常数的约束。相反,创建一个与某个“里程碑”相关的内容。在这种情况下,由于您希望原点的 x 坐标为 0,这意味着视图的左边缘将与其父视图的左边缘重合。因此,创建一个建立这种关系的约束:

      NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:smallView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeLeft multiplier:1 constant:0.0];
      

      它必须不同于其他两个,尽管实现这种拆分的常用方法是:

      • superview.leading == view1.leading
      • view1.trailing == view2.leading
      • view2.trailing == view3.leading
      • view3.trailing == superview.trailing
      • view1.width == view2.width
      • view2.width == view3.width

      (请注意,我使用的是前导和尾随而不是左右。这样您的 UI 会自动镜像为从右到左的语言。)

      【讨论】:

        最近更新 更多