为什么我的 spring-label-label-spring 自动布局约束不起作用?

Why my spring-label-label-spring auto layout constraints don't work?

我试图在其容器内水平居中放置标签,使用 UIView 在两侧充当 springs。

不幸的是,系统告诉它可以满足所有约束,因此结果是错误的。但是我可以想出一个解决办法。

我的代码如下:

 func addSpringConstraints(items: [UIView], mainView: UIView, spacing: CGFloat) {

    let pad = spacing / 2

    var constraints = [NSLayoutConstraint]()


    // Add two spacer to serve as springs
    let lSpring = UIView()
    lSpring.frame.size.width = 50
    //lSpring.hidden = true
    lSpring.backgroundColor = UIColor.redColor().colorWithAlphaComponent(0.5)
    lSpring.translatesAutoresizingMaskIntoConstraints = false
    addSubview(lSpring)

    let rSpring = UIView()
    rSpring.frame.size.width = lSpring.frame.size.width
    //rSpring.hidden = true
    rSpring.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.5)
    rSpring.translatesAutoresizingMaskIntoConstraints = false
    addSubview(rSpring)


    // Constraint left spring
    constraints.append(NSLayoutConstraint(item: lSpring, attribute: .Top,    relatedBy: .Equal, toItem: mainView, attribute: .Top,    multiplier: 1, constant: 0))
    constraints.append(NSLayoutConstraint(item: lSpring, attribute: .Bottom, relatedBy: .Equal, toItem: mainView, attribute: .Bottom, multiplier: 1, constant: 0))
    constraints.append(NSLayoutConstraint(item: lSpring, attribute: .Left,   relatedBy: .Equal, toItem: mainView, attribute: .Left,   multiplier: 1, constant: 0))


    // Constraint right spring
    constraints.append(NSLayoutConstraint(item: rSpring, attribute: .Top,    relatedBy: .Equal, toItem: mainView, attribute: .Top,    multiplier: 1, constant: 0))
    constraints.append(NSLayoutConstraint(item: rSpring, attribute: .Bottom, relatedBy: .Equal, toItem: mainView, attribute: .Bottom, multiplier: 1, constant: 0))
    constraints.append(NSLayoutConstraint(item: rSpring, attribute: .Right,  relatedBy: .Equal, toItem: mainView, attribute: .Right,  multiplier: 1, constant: 0))


    for (index, segment) in items.enumerate() {

        // Add top and bottom constraints

        constraints.append(NSLayoutConstraint(item: segment, attribute: .Top,    relatedBy: .Equal, toItem: mainView, attribute: .Top,    multiplier: 1, constant: 0))
        constraints.append(NSLayoutConstraint(item: segment, attribute: .Bottom, relatedBy: .Equal, toItem: mainView, attribute: .Bottom, multiplier: 1, constant: 0))


        // Add left constraint if any and width constraint

        if index == 0 {
            let prev = lSpring          // first label is attached to the left spring
            constraints.append(NSLayoutConstraint(item: segment, attribute: .Left, relatedBy: .Equal, toItem: prev, attribute: .Right, multiplier: 1, constant: 0))
        }
        else {
            let prev = items[index-1]   // label attached to the previous label by its left side
            constraints.append(NSLayoutConstraint(item: segment, attribute: .Left, relatedBy: .Equal, toItem: prev, attribute: .Right, multiplier: 1, constant: pad))

            // Same width than first segment
            constraints.append(NSLayoutConstraint(item: segment, attribute: .Width, relatedBy: .Equal, toItem: items.first, attribute: .Width, multiplier: 1, constant: 0))
        }


        // Add right constraints if any

        if index == items.count - 1 {
            let next = rSpring          // last label is attached to the right spring
            constraints.append(NSLayoutConstraint(item: segment, attribute: .Right, relatedBy: .Equal, toItem: next, attribute: .Left, multiplier: 1, constant: 0))
        }
        else {
            let next = items[index+1]   // label attached to the next label by its right side
            constraints.append(NSLayoutConstraint(item: segment, attribute: .Right, relatedBy: .Equal, toItem: next, attribute: .Left, multiplier: 1, constant: -pad))
        }
    }

    // Add all constraints to the view
    mainView.addConstraints(constraints)
}

这是 spring -"PHOTO" - "LIVE VIDEO" - spring 的问题:

Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
    (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSAutoresizingMaskLayoutConstraint:0x14ddf0d60 h=--& v=--& H:[Visage.ZEENSUISegmentedControl:0x14df01300(0)]>",
    "<NSLayoutConstraint:0x14dec1490 H:|-(0)-[UIView:0x14debe390](LTR)   (Names: '|':Visage.ZEENSUISegmentedControl:0x14df01300 )>",
    "<NSLayoutConstraint:0x14deaabb0 UIView:0x14deaaa40.right == Visage.ZEENSUISegmentedControl:0x14df01300.right>",
    "<NSLayoutConstraint:0x14deb7780 H:[UIView:0x14debe390]-(0)-[UILabel:0x14df3af70'PHOTO'](LTR)>",
    "<NSLayoutConstraint:0x14deaada0 UILabel:0x14df3af70'PHOTO'.right == UILabel:0x14debe9e0'LIVE VIDEO'.left - 22.5>",
    "<NSLayoutConstraint:0x14dea5930 UILabel:0x14debe9e0'LIVE VIDEO'.width == UILabel:0x14df3af70'PHOTO'.width>",
    "<NSLayoutConstraint:0x14dea5980 UILabel:0x14debe9e0'LIVE VIDEO'.right == UIView:0x14deaaa40.left>"
)

好的,我通过进一步试验发现了问题。

问题是 spacers 必须具有相同的大小(实际上是宽度)才能实现 "spring" 行为,因此我添加了如下附加约束:

    // Constraint right spring
    constraints.append(NSLayoutConstraint(item: lSpring, attribute: .Width,  relatedBy: .Equal, toItem: rSpring,  attribute: .Width,  multiplier: 1, constant: 0))

原来要设置这个额外的约束是为了限制只有一个自由度(左右均匀space)。如果不加这个约束,负责求解约束的底层代码有多个解:left=0 or right=0等。