使用 NSLayoutConstraints 初始值设定项与使用 Anchors 设置约束之间的区别

Difference between using NSLayoutConstraints initializer compared to Anchors for setting constraints

为什么有些开发人员会添加这样的约束:

NSLayoutConstraint(item: myView, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1.0, constant: 20.0).isActive = true

有些像这样:

myView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 20).isActive = true

他们基本上做同样的事情......对吧?那么它们之间有什么区别呢?为什么应该使用一个而不是另一个?使用一个与另一个相比有性能差异吗?

在我工作的地方,我们的 iOS 领导专门使用 NSLayoutConstraint 初始化方式,每个人都被迫这样做,以便在整个代码中具有更高的一致性和可读性,我都喜欢方式,我只想知道 was/is 使用一个比另一个有什么好处吗?还是仅基于偏好的差异?

在很大程度上,它是新的语法和可读性,但仍有一些您可以使用 NSLayoutConstraint(...) 做的事情,而您不能用“新”方式做。

例如,我们来执行一个简单的任务,添加一个 UILabel 水平居中,距离底部 40 磅。以下每个示例都将以此开头:

override func viewDidLoad() {
    super.viewDidLoad()
    
    let myView = UILabel()
    myView.backgroundColor = .green
    myView.text = "Hello"
    
    myView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(myView)
    
    // add constraints....

}

所以,我们的第一个方法是这样的:

        // center horizontally
        NSLayoutConstraint(item: myView,
                           attribute: .centerX,
                           relatedBy: .equal,
                           toItem: view,
                           attribute: .centerX,
                           multiplier: 1.0,
                           constant: 0.0).isActive = true
        
        // bottom = 40-pts from view Bottom
        NSLayoutConstraint(item: myView,
                           attribute: .bottom,
                           relatedBy: .equal,
                           toItem: view,
                           attribute: .bottom,
                           multiplier: 1.0,
                           constant: -40.0).isActive = true

我们可以使用这种语法得到完全相同的结果,通常认为这种语法更“可读”:

        // center horizontally
        myView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        // bottom = 40-pts from view Bottom
        myView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40.0).isActive = true

现在,我们通常会有更多的约束要设置,所以我们可以用这个让它更具可读性(消除每行末尾的 .isActive = true):

        NSLayoutConstraint.activate([
            // center horizontally
            myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            // bottom = 40-pts from view Bottom
            myView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40.0),
        ])

所以...如果我们稍微复杂一点,比如说 "Keep the Bottom of the Label 20% from the bottom 会发生什么观点?


为了坚持“新的、更具可读性”的语法,我们有几个选择...

1 - 将 Label 的底部约束到视图的底部,等到布局完成 - 这样我们就知道视图的高度 - 然后将底部锚点上的 .constant 设置为 -(view height * 0.2)。这可行,但每次标签的超级视图发生变化时(例如设备旋转时),我们都必须重新计算。

2 - 添加一个 UIView 作为“底部垫片”:

        // add a hidden UIView for bottom "space"
        let spacerView = UIView()
        spacerView.isHidden = true
        view.addSubview(spacerView)
        spacerView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            // spacerView at bottom, height = 20% of view height
            spacerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
            spacerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.2),

            // center horizontally
            myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            // bottom = spacerView Top
            myView.bottomAnchor.constraint(equalTo: spacerView.topAnchor, constant: 0.0),
        ])

这行得通,并且可以处理父视图大小的变化,但我们已经将另一个视图添加到视图层次结构中。对于这个简单的例子,没什么大不了的,但我们可能不想为复杂的布局添加一堆。

3 - 添加一个 UILayoutGuide 作为“底部间隔”:

        // add a UILayoutGuide for bottom "space"
        let spacerGuide = UILayoutGuide()
        view.addLayoutGuide(spacerGuide)
        NSLayoutConstraint.activate([
            // spacerGuide at bottom, height = 20% of view height
            spacerGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
            spacerGuide.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.2),
            
            // center horizontally
            myView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            // bottom = spacerGuide Top
            myView.bottomAnchor.constraint(equalTo: spacerGuide.topAnchor, constant: 0.0),
        ])

完成同样的事情,但现在我们使用 非渲染 UI 元素,因此我们不会降低视图层次结构。

4 - 使用 NSLayoutConstraint(...) 语法,避免所有这些:

        // center horizontally
        NSLayoutConstraint(item: myView,
                           attribute: .centerX,
                           relatedBy: .equal,
                           toItem: view,
                           attribute: .centerX,
                           multiplier: 1.0,
                           constant: 0.0).isActive = true
        
        // bottom = 80% of view bottom (leaves 20% space at bottom)
        NSLayoutConstraint(item: myView,
                           attribute: .bottom,
                           relatedBy: .equal,
                           toItem: view,
                           attribute: .bottom,
                           multiplier: 0.8,
                           constant: 0.0).isActive = true
    }

所以,对于大多数情况,这是一个偏好问题 and/or 一致性,但你会发现偶尔的情况 一个区别。

锚是后来添加的。它们看起来更干净,您可以更轻松地限制在安全区域,因为它有自己的锚点。