Search code examples
iosautolayoutnslayoutconstraint

iOS auto layout: why is zero multiplier not allowed?


I am computing some multipliers for constraints for views created dynamically. Sometimes it is zero. When that happens, I get the following unhandled exception:

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

from this code:

NSLayoutConstraint(item: spanView,
    attribute: .leading,
    relatedBy: .equal,
    toItem: self.miniOnOffView,
    attribute: .trailing,
    multiplier: m1,
    constant: 0.0).isActive = true

Where m1 = 0, and spanView has already been added as a subview of self.miniOnOffView.

Basically, given a fraction between 0 and 1, I'm trying specify the fractional placement of the leading part of the subview. What I don't understand is why this is an error. Why is zero an illegal value?

Is there a better solution? I basically have subviews that I want to position the left and right fractionally relative to the parent, where both are anywhere between 0 and 1 of the width.

Clarification 1

@Pärserk thanks for the link. Not shooting the messenger, but Apple's statements don't entirely jive with what Interface Builder will let me do then. I've used the following as a way of position a button 3/4 with a 30 pixel bias across a container view:

enter image description here

I've not (yet) tried 0 for the multiplier there, but 0.75 isn't an identity value either. It's worked fine for a while.

Clarification 2

@matt, here's an example of what I'm trying to do:

enter image description here

Basically, I have a calendar day view. I'm trying to position the red and blue time bars based on their fractional positions within the day (the green annotations show approximate values).

I could of course make a "layout" view that implements layoutSubviews() and do all of this proportional layout there, but I was trying to use the machinery at hand so it would be more declarative up front. I'm doing a bunch of these in a UICollectionView and the performance when I took the "just draw it all myself using draw()" was dissapointing.


Solution

  • I'm unclear on what you want, but you can't create a width relationship between constraints, only between views. Therefore, if you are doing what I think you want to do, you will need to create an invisible "spacer" view that has the desired width and position to generate the space, and pin your visible subviews to that.

    EDIT Yes, now that you've shown more of what you want, my answer is right. Here's my rendering:

    enter image description here

    The black-bordered rectangle is the superview, and the bars are positioned in accordance with your diagram, in relation to the width of the superview.

    How is this achieved? There are invisible spacer views at the left of the blue bar and the red bar, providing the space from the left of the superview by means of their widths. Thus, the horizontal layout of the blue and red bars comes from four width constraints (sup is the superview):

    spacerView1.widthAnchor.constraintEqualToAnchor(sup.widthAnchor, multiplier: 0.1).active = true
    spacerView2.widthAnchor.constraintEqualToAnchor(sup.widthAnchor, multiplier: 0.25).active = true
    blueView.widthAnchor.constraintEqualToAnchor(sup.widthAnchor, multiplier: 0.6-0.1).active = true
    redView.widthAnchor.constraintEqualToAnchor(sup.widthAnchor, multiplier: 0.9-0.25).active = true