NSLayoutConstraints 出现问题并以编程方式使用自动布局时,UIViews 停留在 (0,0)

Having problems with NSLayoutConstraints and using Auto Layout programmatically, UIViews stuck at (0,0)

我正在尝试以编程方式生成一个 'score page',其中每个属性的分数都有一个 UILabel 和一个 UISlider。由于没有固定数量的属性,我决定以编程方式执行此操作(而不是在情节提要中)

我的想法是为每个属性创建一个 UIView,然后将一个 UILabel 和一个 UISlider 插入到 UIView 中,然后设置约束。

但是,我 运行 遇到了一个问题,即我无法正确设置约束,或者由于缺乏执行此类操作的经验而可能错过的另一个巨大错误。结果,所有 UIView 都粘在屏幕的左上角 (0,0) 并且彼此重叠。

到目前为止,这是我的代码:

func addLabels(attributesArray: [String], testResultsDictionary: [String:Double]){
    var viewsDictionary = [String:AnyObject]()
    //let numberOfAttributes = attributesArray.count //(vestigial, please ignore)
    let sliderHeight = Double(20)
    let sliderWidth = Double(UIScreen.mainScreen().bounds.size.width)*0.70 // 70% across screen
    let labelToSliderDistance = Float(10)
    let sliderToNextLabelDistance = Float(30)
    let edgeHeightConstraint = Float(UIScreen.mainScreen().bounds.size.height)*0.10 // 10% of screen height

    for attribute in attributesArray {

        let attributeView = UIView(frame: UIScreen.mainScreen().bounds)
        attributeView.backgroundColor = UIColor.blueColor()
        attributeView.translatesAutoresizingMaskIntoConstraints = false
        attributeView.frame.size = CGSize(width: Double(UIScreen.mainScreen().bounds.size.width)*0.80, height: Double(80))
        self.view.addSubview(attributeView)

        var attributeViewsDictionary = [String:AnyObject]()
        let attributeIndex = attributesArray.indexOf(attribute)! as Int

        let attributeLabel = UILabel()
        attributeLabel.translatesAutoresizingMaskIntoConstraints = false
        attributeLabel.text = attribute.stringByReplacingOccurrencesOfString("_", withString: " ")
        attributeLabel.sizeToFit()

        let attributeSlider = UISlider()
        attributeSlider.translatesAutoresizingMaskIntoConstraints = false
        attributeSlider.setThumbImage(UIImage(), forState: .Normal)
        attributeSlider.frame.size = CGSize(width: sliderWidth, height: sliderHeight)
        attributeSlider.userInteractionEnabled = false

        if let sliderValue = testResultsDictionary[attribute] {
            attributeSlider.value = Float(sliderValue)
        }
        else {
            attributeSlider.value = 0
        }

        attributeView.addSubview(attributeLabel)
        attributeView.addSubview(attributeSlider)

        //attributeView.sizeToFit()

        attributeViewsDictionary["Label"] = attributeLabel
        attributeViewsDictionary["Slider"] = attributeSlider

        viewsDictionary[attribute] = attributeView
        print(viewsDictionary)

        let control_constraint_H = NSLayoutConstraint.constraintsWithVisualFormat("H:|-[\(attribute)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary)
        var control_constraint_V = [NSLayoutConstraint]()
        if attributeIndex == 0 {
            control_constraint_V = NSLayoutConstraint.constraintsWithVisualFormat("V:|-\(edgeHeightConstraint)-[\(attribute)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary)
        }
        else if attributeIndex == attributesArray.indexOf(attributesArray.last!){
            control_constraint_V = NSLayoutConstraint.constraintsWithVisualFormat("V:[\(attribute)]-\(edgeHeightConstraint)-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary)
        }
        else {
            control_constraint_V = NSLayoutConstraint.constraintsWithVisualFormat("V:[\(attributesArray[attributeIndex-1])]-\(sliderToNextLabelDistance)-[\(attribute)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary)
        }

        self.view.addConstraints(control_constraint_H)
        self.view.addConstraints(control_constraint_V)

        let interAttributeConstraint_V = NSLayoutConstraint.constraintsWithVisualFormat("V:[Label]-\(labelToSliderDistance)-[Slider]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: attributeViewsDictionary)
        //let interAttributeConstraint_H = NSLayoutConstraint.constraintsWithVisualFormat("H:[Label]-5-[Slider]", options: NSLayoutFormatOptions.AlignAllCenterX, metrics: nil, views: attributeViewsDictionary)

        attributeView.addConstraints(interAttributeConstraint_V)
        //attributeView.addConstraints(interAttributeConstraint_H)
        //attributeView.sizeToFit()
    }
}

补充说明: - attributeArray 看起来像这样:["Happiness"、"Creativity"、"Tendency_To_Slip"]

问题是这些视图没有完全定义它们的约束(值得注意的是,缺少很多垂直约束)。我还注意到您已尝试设置各种视图的 framesize,但这是徒劳的,因为当您使用自动布局时,所有 frame 值都将被丢弃并且由自动布局过程重新计算。相反,请确保视图尺寸完全由约束定义。

例如:

let spacing: CGFloat = 10

func addLabels(attributesArray: [String], testResultsDictionary: [String: Float]) {
    var previousContainer: UIView?   // rather than using the index to look up the view for the previous container, just have a variable to keep track of the previous one for you.

    for attribute in attributesArray {
        let container = UIView()
        container.backgroundColor = UIColor.lightGrayColor() // just so I can see it
        container.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(container)

        // set top anchor for container to superview if no previous container, otherwise link it to the previous container

        if previousContainer == nil {
            container.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: spacing).active = true
        } else {
            container.topAnchor.constraintEqualToAnchor(previousContainer!.bottomAnchor, constant: spacing).active = true
        }
        previousContainer = container

        // set leading/trailing constraints for container to superview

        NSLayoutConstraint.activateConstraints([
            container.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant: spacing),
            view.trailingAnchor.constraintEqualToAnchor(container.trailingAnchor, constant: spacing),
        ])

        // create label

        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = attribute
        container.addSubview(label)

        // create slider

        let slider = UISlider()
        slider.translatesAutoresizingMaskIntoConstraints = false
        slider.value = testResultsDictionary[attribute]!
        container.addSubview(slider)

        // constraints for label and slider

        NSLayoutConstraint.activateConstraints([
            label.topAnchor.constraintEqualToAnchor(container.topAnchor, constant: spacing),
            slider.topAnchor.constraintEqualToAnchor(label.bottomAnchor, constant: spacing),
            container.bottomAnchor.constraintEqualToAnchor(slider.bottomAnchor, constant: spacing),

            label.leadingAnchor.constraintEqualToAnchor(container.leadingAnchor, constant: spacing),
            slider.leadingAnchor.constraintEqualToAnchor(container.leadingAnchor, constant: spacing),

            container.trailingAnchor.constraintEqualToAnchor(label.trailingAnchor, constant: spacing),
            container.trailingAnchor.constraintEqualToAnchor(slider.trailingAnchor, constant: spacing)
        ])
    }
}

现在,我碰巧使用 iOS 9 语法来定义约束(它具有表达力和简洁性),但是如果您 want/need 使用 VFL,您也可以这样做。只需确保您定义了一组明确定义的等效约束(顶部、底部、前导和尾随)。另请注意,我没有对这些容器视图的大小进行硬编码,而是让它从其子视图的大小中推断出来,并且容器视图将相应地调整大小。

说完所有这些,我看看这个 UI 我可能倾向于用 table 视图来做这件事,这让你不必定义所有的业务这些限制,但也优雅地处理了很多这样的场景,你也想享受滚动行为。或者,如果我知道这些总是能够适合单个屏幕,我可能会使用 UIStackView。但是如果你想用约束来做,你​​可以像上面那样做。