Swift 约束以编程方式产生奇怪的行为

Swift constraints programmatically creates odd behavior

我正在尝试以编程方式布置我的所有约束,因为我必须根据用户输入添加和删除框。当我 运行 一个在 ViewDidLoad 中应用我所有约束的函数时,它工作得很好。但是如果我再次 运行 它,在将它们全部删除之后,我的 2 个标签会消失在我的导航栏后面。我不知道为什么!请记住,如果我从不 运行 下面粘贴的函数,我的屏幕将是空白的,因为我对从故事板启动没有任何限制。我的目标是弄清楚为什么我的标签和按钮在同一段代码再次 运行 时消失。

下面是一张图片,我将在下面 post 的代码最初是 运行 在 ViewDidLoad 中:

下图是同一段代码在按下下一个按钮后再次运行时的图片。这只是一个测试,以后我需要在更改约束后重新设置所有约束:

下面是调试视图层次结构的图片:

创建约束的代码如下。请记住,我只有 5 个元素,我的主视图、滚动视图、作为标签的 aglAltitudeLabel、作为按钮的 nextButton 和作为文本输入的 aglAltitude:

    //Remove all pre-existing contraints
    var constraints:NSArray = aglAltitudeLabel.constraints()
    aglAltitudeLabel.removeConstraints(constraints)
    constraints = aglAltitude.constraints()
    aglAltitude.removeConstraints(constraints)
    constraints = nextButton.constraints()
    nextButton.removeConstraints(constraints)
    constraints = scrollView.constraints()
    scrollView.removeConstraints(constraints)
    constraints = view.constraints()
    self.view.removeConstraints(constraints)
    constraints = calculateButton.constraints()
    calculateButton.removeConstraints(constraints)

    //Make scrollview fit normal View, accounting for Navigation bar
    self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[first(\(self.view.frame.width))]|", options: nil, metrics: nil, views: ["first": scrollView]))
    self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-64-[first(\(self.view.frame.height-64))]|", options: nil, metrics: nil, views: ["first": scrollView]))

    //Set the heights for the label and button
    let labelHeight = NSLayoutConstraint(item: aglAltitudeLabel, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 20)
    scrollView.addConstraint(labelHeight)
    let buttonHeight = NSLayoutConstraint(item: nextButton, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 30)
    scrollView.addConstraint(buttonHeight)

    //Setup the vertical layout
    scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-8-[first(30)]-8-|", options: nil, metrics: nil, views: ["first": aglAltitude]))


    //Setup the horizontal layout
    scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-8-[first(97)]-8-[second(>=100)]-8-[third(46)]-8-|", options: nil, metrics: nil, views: ["first": aglAltitudeLabel, "second": aglAltitude, "third": nextButton]))

    //Align baselines - this also occurs with the option above of AlignAllBaselines and leaving this code out
    let aglLabelBaseline = NSLayoutConstraint(item: aglAltitudeLabel, attribute: NSLayoutAttribute.Baseline, relatedBy: NSLayoutRelation.Equal, toItem: aglAltitude, attribute: NSLayoutAttribute.Baseline, multiplier: 1.0, constant: 0)
    scrollView.addConstraint(aglLabelBaseline)
    let nextButtonBaseline = NSLayoutConstraint(item: nextButton, attribute: NSLayoutAttribute.Baseline, relatedBy: NSLayoutRelation.Equal, toItem: aglAltitude, attribute: NSLayoutAttribute.Baseline, multiplier: 1.0, constant: 0)
    scrollView.addConstraint(nextButtonBaseline)

    self.view.layoutSubviews()

感谢您的帮助。我确定我做错了 55 件事。

编辑:如果我更改最后一段代码:

    //Align baselines - this also occurs with the option above of AlignAllBaselines and leaving this code out
    let aglLabelBaseline = NSLayoutConstraint(item: aglAltitudeLabel, attribute: NSLayoutAttribute.Baseline, relatedBy: NSLayoutRelation.Equal, toItem: aglAltitude, attribute: NSLayoutAttribute.Baseline, multiplier: 1.0, constant: 0)
    scrollView.addConstraint(aglLabelBaseline)
    let nextButtonBaseline = NSLayoutConstraint(item: nextButton, attribute: NSLayoutAttribute.Baseline, relatedBy: NSLayoutRelation.Equal, toItem: aglAltitude, attribute: NSLayoutAttribute.Baseline, multiplier: 1.0, constant: 0)
    scrollView.addConstraint(nextButtonBaseline)

为此:

    //Align baselines - this also occurs with the option above of AlignAllBaselines and leaving this code out
    let aglLabelBaseline = NSLayoutConstraint(item: aglAltitudeLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: scrollView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 14)
    self.view.addConstraint(aglLabelBaseline)
    let nextButtonBaseline = NSLayoutConstraint(item: nextButton, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: scrollView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 8)
    self.view.addConstraint(nextButtonBaseline)

它完全符合我的预期。为什么不同?

在不知道您的要求是什么的情况下,很难给您好的建议。这是我如何做的一个例子。我在代码中添加了所有视图,并为视图添加了背景颜色,以便我可以更好地查看它们的布局。我假设滚动视图只添加一次,并且永远不会删除,所以它对 self.view 的约束应该只在 viewDidLoad 中添加一次。如果您不需要更改其他视图的高度,它们也是如此(请注意,我将高度限制添加到视图本身,而不是它的父视图)。唯一需要删除并重新添加的是滚动视图及其子视图之间的约束。

class ViewController: UIViewController {

    var aglAltitudeLabel = UILabel()
    var aglAltitude = UITextField()
    var nextButton = UIButton.buttonWithType(.System) as UIButton
    var scrollView = UIScrollView()
    var viewsDict: NSDictionary!

    override func viewDidLoad() {
        super.viewDidLoad()
        aglAltitudeLabel.text = "AGL Altitude"
        aglAltitudeLabel.backgroundColor = UIColor.lightGrayColor()
        aglAltitudeLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
        aglAltitude.setTranslatesAutoresizingMaskIntoConstraints(false)
        aglAltitude.backgroundColor = UIColor.yellowColor()
        aglAltitudeLabel.addConstraint(NSLayoutConstraint(item: aglAltitudeLabel, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 30))
        scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
        nextButton.setTranslatesAutoresizingMaskIntoConstraints(false)
        nextButton.setTitle("Next", forState: .Normal)
        nextButton.backgroundColor = UIColor.lightGrayColor()
        nextButton.addConstraint(NSLayoutConstraint(item: nextButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 30))
        self.view.addSubview(scrollView)
        scrollView.addSubview(aglAltitude)
        scrollView.addSubview(aglAltitudeLabel)
        scrollView.addSubview(nextButton)
        scrollView.backgroundColor = UIColor(red: 1, green: 1, blue: 0.8, alpha: 1)

        viewsDict = ["scrollView": scrollView, "aglAltitudeLabel": aglAltitudeLabel, "aglAltitude":aglAltitude, "nextButton":nextButton] as NSDictionary

        self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[scrollView]|", options: nil, metrics: nil, views: viewsDict))
        self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-64-[scrollView]|", options: nil, metrics: nil, views: viewsDict))

        self.redoConstraints()
    }


    @IBAction func redoConstraints() {

        scrollView.removeConstraints(scrollView.constraints())

        scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-8-[aglAltitude(30)]-8-|", options: nil, metrics: nil, views: viewsDict))

        scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-8-[aglAltitudeLabel(97)]-8-[aglAltitude(>=100)]-8-[nextButton(46)]-8-|", options: .AlignAllBottom , metrics: nil, views: viewsDict))
    }
}

请注意,我将固定宽度和高度排除在滚动视图的约束之外。没有必要这样做,这会导致它在轮换时无法正常工作。