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

Posted

技术标签:

【中文标题】遇到 NSLayoutConstraints 问题并以编程方式使用自动布局时,UIViews 停留在 (0,0)【英文标题】:Having problems with NSLayoutConstraints and using Auto Layout programmatically, UIViews stuck at (0,0) 【发布时间】:2016-04-17 02:42:11 【问题描述】:

我正在尝试以编程方式生成一个“分数页面”,其中每个属性的分数都有一个 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()
    

额外说明: - 一个属性数组看起来像这样:["Happiness", "Creativity", "Tendency_To_Slip"]

代码非常混乱,而且是原型,没有必要,非常抱歉!请多多包涵!

【问题讨论】:

解决了!也错过了对每个元素的大小进行限制(attributeView、attributeLabel、attributeSlider)。另一个问题也帮助我解决了这个问题! ***.com/questions/26180822/… 【参考方案1】:

问题是这些视图没有完全定义它们的约束(值得注意的是,有很多缺失的垂直约束)。我还注意到您尝试设置各种视图的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 语法来定义约束(它既富有表现力又简洁),但如果您想要/需要使用 VFL,您也可以这样做。只需确保定义了一组明确定义的等效约束(顶部、底部、前导和尾随)。另请注意,我不是硬编码这些容器视图的大小,而是让它从其子视图的大小推断出来,容器视图将相应地调整大小。

说了这么多,我看一下这个 UI,我可能会倾向于使用表格视图来执行此操作,这样您就无需定义所有这些约束,而且还可以优雅地处理以下场景:其中有很多您也想享受滚动行为。或者,如果我知道这些总是能够放在一个屏幕上,我可能会使用UIStackView。但是如果你想用约束来做,你​​可以做类似上面的事情。

【讨论】:

感谢您通过深入了解我们可以以“表格”格式呈现信息的不同方式的全面回答! :D iOS 9 语法似乎不是一种捷径,而 VFL 是一种“更脏”的方法,并且极易出现难以识别的错误(根据我的经验)。

以上是关于遇到 NSLayoutConstraints 问题并以编程方式使用自动布局时,UIViews 停留在 (0,0)的主要内容,如果未能解决你的问题,请参考以下文章

NSLayoutConstraints 在 ios 7 上崩溃,但在 ios 8 上没有

使用 NSLayoutConstraints 添加第二个子视图控制器,始终将其放置在导航栏下方,而不是导航栏之后

带有 NSLayoutConstraints 的 UIView 灵活高度

使用 NSLayoutConstraints 调整当前视图的大小

NSLayoutConstraints 的 UIViewPropertyAnimator 使视图消失

在 UITableView 的标题视图上使用 NSLayoutConstraints