AutoLayout - NSLayoutConstraint

什么是NSLayoutConstraint

NSLayoutConstraint由名字翻译过去就是布局约束。该类是表示用户界面上两个对象之间的布局关系,并且该关系必须满足基本约束的布局系统。

解析约束(Constraint)

布局视图(view)定义为一系列的线性方程,每一个约束(constraint)都代表一个方程,格式如下:

AutoLayout - NSLayoutConstraint_第1张图片

上面的约束表示RedView的左边距(left或者说leading)是在blue view右边(righttrailing)偏移8个点.


简化一下上图的内容就是如下格式

item1.attribute1 = multiplier × item2.attribute2 + constant

方程式的各部分:


Item1 :表示方程式中的第一个对象,这里是 RedView

Attribute 1:第一个item对象约束的属性(attribute),这里是RedViewleading

Relationship:关系,即等式左边和等式右边的约束关系。relationship可以含有3个值之一:等于(equal),大于或者等于(greater than or equal),小于或者等于(or less than equal).上面的例子是使用了equal

Multiplier:倍数,attribute 2的值乘以这个值。上面Multiplier是1.0

Item2:方程式的第二个对象,这里是blue view,不像第一个对象(item),这里可以为空,表示只有常量值

Attribute 2:对第二个对象的约束,上面表示右边(right/trailing尾部)。如果第二个item为空,那么表示没有Attribute

Constant:常量值,偏移量,这里是8,该值是加在Attribute 2上的


自动布局属性(AutoLayout Attributes)

Auto Layout中属性能够添加约束,属性(Attribute)包括4个边缘部分(leading, trailing, top, and bottom),高度(height),宽度(width),垂直和水平中心(horizontal centers)等。对于文本对象(Text items )也包含一个或者多个 baseline attributes.


AutoLayout - NSLayoutConstraint_第2张图片

对于完整的属性列表,可以看这里。


注意:为视图(view)添加约束必须是有效布局,有效布局是指约束不模糊,约束之间没有冲突。有效的约束布局,实现的方式不止一种。如果约束添加无效,那么会导致约束模糊或者冲突。

此外,约束并没有限制只使用等于关系,正像前面说提到的,存在大于等于或者小于等于关系。约束也有优先级,从1到1000,1000表示必须的(required),所有小于1000的约束都是可选的(optional),默认情况下,所有的约束都是1000.

在处理了所有required的约束之后,Auto Layout将按可选约束的优先级由高到低进行处理。如果不能够处理可选约束(optional constraints),Auto Layout将尝试尽可能靠近预期的结果,并移动到下一个约束。


NSLayoutConstraint的使用

创建约束的方式

初始化方法创建约束

public convenience init(item view1: Any,
                   attribute attr1: NSLayoutAttribute,
                   relatedBy relation: NSLayoutRelation,
                   toItem view2: Any?,
                   attribute attr2: NSLayoutAttribute,
                   multiplier: CGFloat,
                   constant c: CGFloat)


方法中的每一个参数正好对应我们之前讲到的方程式:view1.attr1 = view2.attr2 * multiplier + constant,如果方程式中没有第二个对象,那么使用nil作为参数。


使用VFL语言

open class func constraints(withVisualFormat format: String,
                              options opts: NSLayoutFormatOptions = [],
                              metrics: [String : Any]?,
                              views: [String : Any]) -> [NSLayoutConstraint]

具体使用NSLayoutConstraint

首先我们创建一个简单view,相对于父视图上下左右边缘20.看看如何实现:

   override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.yellow

        let testView = UIView()
        testView.backgroundColor = UIColor.red
        // 1 添加约束之前,需要将视图添加到父视图上
        view.addSubview(testView)

        // 2 为视图添加约束
        let leading = NSLayoutConstraint(item: testView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 20)
        let top = NSLayoutConstraint(item: testView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 20)
        let trailing = NSLayoutConstraint(item: testView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1.0, constant: -20)
        let bottom = NSLayoutConstraint(item: testView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1.0, constant: -20)

        view.addConstraints([leading, top, trailing, bottom])
    }
添加约束很简单,创建各个方面的约束并添加,但是却看不到我们的testView,而且console中打印了很多的信息,如下:

AutoLayout - NSLayoutConstraint_第3张图片
这是一个常见的错误,是因为视图(view)的translatesAutoresizingMaskIntoConstraints属性导致的。因为默认情况下,系统会我们进行Autoresizing Mask布局,这里我们又自己添加了约束布局,所以导致了冲突,上图中也指出了该属性。为了解决这个问题,我们需要将视图的translatesAutoresizingMaskIntoConstraints属性设置为false。现在我们就能够很好的看到我们自己添加的视图了,距离父视图上下左右各20间距。

AutoLayout - NSLayoutConstraint_第4张图片

这里有几个地方需要注意一下:

1)添加约束前腰确保把需要布局的子视图(view)添加到对应的父视图上
2)一定要记得禁止子视图的 Autoresizing Mask,如上面:

testView.translatesAutoresizingMaskIntoConstraints = false
3)创建约束之后,将约束添加到父视图(view)上

4)注意bottom和trailing是为负值


5)当约束引用两个视图时,这两个视图一定要属于同一个视图层次结构中,对于引用两个视图的约束,只有两种合法的情况:要么一个视图是另一个视图的父视图,要么两个视图必须是某种类型的兄弟(即它们必须有一个非nil的共同视图祖先)。如果视图让约束引用其它情况的视图,将会崩溃。


OK,我们继续来添加视图和约束,在给testView添加一个居中并且宽为200,高为200的子视图


let subView = UIView()
        subView.backgroundColor = UIColor.cyan
        subView.translatesAutoresizingMaskIntoConstraints = false
        testView.addSubview(subView)

        let centerX = NSLayoutConstraint(item: subView, attribute: .centerX, relatedBy: .equal, toItem: testView, attribute: .centerX, multiplier: 1.0, constant: 0)
        let centerY = NSLayoutConstraint(item: subView, attribute: .centerY, relatedBy: .equal, toItem: testView, attribute: .centerY, multiplier: 1.0, constant: 0)
        let width = NSLayoutConstraint(item: subView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 200)
        let height = NSLayoutConstraint(item: subView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 200)

        testView.addConstraints([centerX, centerY, width, height])
实现步骤跟之前一样,效果如下:

AutoLayout - NSLayoutConstraint_第5张图片

是不是很简单,但是也需要注意几个地方:

1)如果是设置视图(view)自身的属性,不需要跟其他视图建立约束关系,那么添加约束方法的第四个参数为nil,并且属性为notAnAttribute。文章最开始我也提到了。
2)上面视图的宽和高约束,关系使用了.equal,但是实际的开发中不会经常进行设置固定值,除非你自己明确知道不需要改变,否则建议应该选择大于等于或者小于等于。

接下来看如何实现等间距排列3个视图,宽度不固定随屏幕的变化而变化

class ViewController: UIViewController {
    lazy var redView: UIView  = {
        let redView = UIView()
        redView.backgroundColor = UIColor.red
        redView.translatesAutoresizingMaskIntoConstraints = false
        return redView
    }()
    lazy var blackView: UIView = {
        let blackView = UIView()
        blackView.backgroundColor = UIColor.black
        blackView.translatesAutoresizingMaskIntoConstraints = false
        return blackView
    }()
    lazy var blueView: UIView = {
        let blueView = UIView()
        blueView.backgroundColor = UIColor.blue
        blueView.translatesAutoresizingMaskIntoConstraints = false
        return blueView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.yellow
        setupSubView()
        setupConstraints()
    }

    func setupSubView() {
        view.addSubview(redView)
        view.addSubview(blackView)
        view.addSubview(blueView)
    }

    func setupConstraints() {
        let space: CGFloat = 20
        let height: CGFloat = (UIScreen.main.bounds.height - 20 * 4) / 3.0

        // 为红色视图添加约束,leading、top、trailing间距各20
        let redViewLeading = NSLayoutConstraint(item: redView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: space)
        let redViewTop = NSLayoutConstraint(item: redView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: space)
        let redViewTrailing = NSLayoutConstraint(item: redView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1.0, constant: -space)
        let redViewHeight = NSLayoutConstraint(item: redView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: height)
        view.addConstraints([redViewLeading, redViewTop, redViewTrailing, redViewHeight])

        // 为黑色视图添加约束,黑色视图除了top是相对于redView布局,其它等同于redView
        let blackViewLeading = NSLayoutConstraint(item: blackView, attribute: .leading, relatedBy: .equal, toItem: redView, attribute: .leading, multiplier: 1.0, constant: 0)
        let blackViewTop = NSLayoutConstraint(item: blackView, attribute: .top, relatedBy: .equal, toItem: redView, attribute: .bottom, multiplier: 1.0, constant: space)
        let blackViewTrailing = NSLayoutConstraint(item: blackView, attribute: .trailing, relatedBy: .equal, toItem: redView, attribute: .trailing, multiplier: 1.0, constant: 0)
        let blackViewHeight = NSLayoutConstraint(item: blackView, attribute: .height, relatedBy: .equal, toItem: redView, attribute: .height, multiplier: 1.0, constant: 0)
        view.addConstraints([blackViewLeading, blackViewTop, blackViewTrailing, blackViewHeight])

        // 为蓝色视图添加约束,黑色视图除了top是相对于blackView布局,其它等同于blackView
        let blueViewLeading = NSLayoutConstraint(item: blueView, attribute: .leading, relatedBy: .equal, toItem: blackView, attribute: .leading, multiplier: 1.0, constant: 0)
        let blueViewTop = NSLayoutConstraint(item: blueView, attribute: .top, relatedBy: .equal, toItem: blackView, attribute: .bottom, multiplier: 1.0, constant: space)
        let blueViewTrailing = NSLayoutConstraint(item: blueView, attribute: .trailing, relatedBy: .equal, toItem: blackView, attribute: .trailing, multiplier: 1.0, constant: 0)
        let blueViewHeight = NSLayoutConstraint(item: blueView, attribute: .height, relatedBy: .equal, toItem: blackView, attribute: .height, multiplier: 1.0, constant: 0)
        view.addConstraints([blueViewLeading, blueViewTop, blueViewTrailing, blueViewHeight])
    }
}
实现效果如下:


AutoLayout - NSLayoutConstraint_第6张图片

实现等间距排列效果还是很简单,值需要保证设置好视图之间对应的间距,然后动态计算高度。下一个视图相对于上一个视图进行布局即可。但是看到上面的代码是不是觉得代码量很大,确实是这样的。不过也不用担心,凡事麻烦的事情,都会有大师替我们处理,所以会有许多第三方库,对于Swift语言这里有SnapKit.后面会慢慢介绍。


你可能感兴趣的:(iOS,AutoLayout)