由于NSLayoutConstraints和以编程方式使用Auto Layout存在问题,UIViews停留在(0,0)



我正在尝试以编程方式生成一个"分数页",其中每个属性的分数都有一个UILabel和一个UISlider。由于没有固定数量的属性,我决定以编程方式(而不是在故事板中)进行

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

然而,我遇到了一个问题,我无法正确设置约束,或者另一个巨大的错误,我可能因为缺乏做这些事情的经验而错过了。因此,所有的UIViews都被粘在屏幕的左上角(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看起来像这样:["幸福"、"创造力"、"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)
        ])
    }
}

现在,我碰巧使用iOS9语法来定义约束(它很有表达力和简洁性),但如果你想/需要使用VFL,你也可以这样做。只需确保您定义了一组等价的约束,这些约束是明确定义的(顶部、底部、前导和尾部)。还要注意的是,与其硬编码这些容器视图的大小,不如让它根据其子视图的大小来推断,容器视图将相应地调整大小。

说了这么多,我看看这个UI,我可能倾向于用表视图来实现这一点,它可以让你摆脱必须定义所有这些约束的工作,但也可以优雅地处理这样一种情况,即有这么多约束,你也想享受滚动行为。或者,如果我知道这些总是能够放在一个屏幕上,我可能会使用UIStackView。但若你们想在有约束的情况下做这件事,你们可以做上面这样的事情。

最新更新