0
votes

I have a view which contains three subviews. These subviews should scale to fill the entire width of the view.

I've tried using this constraint:

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

This causes an exception:

+[NSLayoutConstraint constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:]: A multiplier of 0 or a nil second item together with a location for the first attribute creates an illegal constraint of a location equal to a constant. Location attributes must be specified in pairs'

I understand what it's complaining about but it seems baffling that I can't get this behaviour. I want my first view to have an x origin of 0.

Is there another way to get this working?

3

3 Answers

1
votes
  1. You cannot make a location NSLayoutAttributeLeft equal to a width NSLayoutAttributeWidth
  2. The exception says you have used a multiplier of 0. Because you set the multiplier parameter to ((1 / 3) * idx), and I guess the idx is a int type. So the expression ((1 / 3) * idx) evaluates to 0. If you want to use a float multiplier, you could achieve that by changing any one of the three numbers to a float type, for example, ((1 / 3.0) * idx).

If you have only 3 subviews to layout, visual format may be a simpler and easy to read way to apply. Say you want the 3 subviews to scale to fill the containerView, all of them have an equal width. The code goes like this:

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

More generally, you may have more than 3 subviews or have to decide it at runtime. For this case, a for loop could help:

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;
}
1
votes

If your deployment target is iOS 9.0 or later, it would probably be simpler to just put the three subviews into a UIStackView. The default stack view settings should do what you want: lay out the three views horizontally, end to end, stretched to fill the width of the stack view, with no padding in between.

0
votes

As you seem to understand, you can't create a constraint that just specifies a coordinate as a constant. Instead, create one that's relative to some "milepost". In this case, since you want the x coordinate of the origin to be 0, that means the view's left edge will be coincident with its superview's left edge. So, create a constraint that establishes that relationship:

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

It will have to be different than other two, although the usual way to achieve this splitting would be:

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

(Note that I'm using leading and trailing instead of left and right. That makes it so that your UI automatically mirrors for right-to-left languages.)