1、Views - AutoLayout

AutoLayout不是一个必须要使用的技术,你可以选择使用它或者不适用它。

一个View可以通过以下三种方式来使用AutoLayout:

  • 在代码中给view添加constraint(约束)
  • 你的app使用xib并勾选了"Use Auto Layout"
  • 自定义一个继承自UIView的view,将requiresConstraintBasedLayout: 方法返回true

class MyView: UIView {
    
    override func updateConstraints() {
        super.updateConstraints()
        print("执行")
    }
    
    override class func requiresConstraintBasedLayout()->Bool{
        return true
    }
}

你可以在 updateConstraints 方法中使用代码创建约束。但是如果AutoLayout没有开放,这个方法并不会被调用。


Constraints ( 约束 )

一个AutoLayout的约束是一个NSLayoutConstraint的实例 , 用来描述view自身的宽高或者与别的view的属性的关系。这两个view要拥有同一个superview(不一定是直接父视图)

来看看NSLayoutConstraint 的属性:

  • firstItem, firstAttribute, secondItem, secondAttribute

    一个约束用来描述两个view的属性的关系,如果约束是描述一个view自己的height或者width,那么second view的值就是nil,它的attribute就是.NotAnAttribute 。 另外NSLayoutAttribute有以下值:

    • .Top, .Bottom
    • .Left, .Right, .Leading, .Trailing
    • .Width, .Height
    • .CenterX, .CenterY
    • .FirstBaseline, .LastBaseline

    .Leading, .Trailing 等价于 .Left , .Right
    .FirstBaseline主要用于多个labels,指从上到下有一定距离。.LastBaseline是指从下到上。

  • multiplier, constant
    用来描述属性的关系,multiplier做乘法,constant做加法
    关系表达式:a1=m*a2+c,默认a1 = 1 * a2+0
    比如要写一个v的宽度是v1的宽度的0.5被多3

    NSLayoutConstraint(item: v,
                           attribute: .Width,
                           relatedBy: .Equal,
                           toItem: v1,
                           attribute: .Width,
                           multiplier: 0.5,
                           constant: 3)
    
  • relation
    是一个NSLayoutRelation类型的值,表示两个属性之间的关系,有三种关系,分别是:LessThanOrEqual Equal、GreaterThanOrEqual。上面的例子也有用到。

  • priority (优先级)
    priority的值从1000-1,每个约束可以有不同的优先级,决定他们使用的顺序。

约束是作用在view上的,一个view可以有多个约束,so view有一个constraints 属性。还有一些实例方法:

  • addConstraint:, addConstraints:
  • removeConstraint:, removeConstraints:

一般需要把约束添加在superview上

NSLayoutConstraint属性除过 priority 和 constant 其他都是只读的,所以如果你想改变已经存在的约束的其他属性,你必须先remove掉这个约束,然后再添加一个新的。


Autoresizing constraints

AutoLayout会根据我们写的约束在运行时决定view的frame和autoresizingMask属性的设置。

如果你准备对你的view使用明确的约束,不需要自动去计算的,记得将translatesAutoresizingMaskIntoConstraints 属性设置为false,如果你没有这么做,可能会引起冲突。


Creating constraints in code(在代码中使用约束)

一般会使用NSLayoutConstraint的初始化方法 init(item:attribute:relatedBy:toItem:attribute: multiplier:constant:) 需要设置基本所需的属性

将上节最后的那个布局用约束试试,我们给v1使用frame,v2、v3使用AutoLayout , 给 v2、v3 的translatesAutoresizingMaskIntoConstraints属性设置为false


let mainview = self.view
let v1 = UIView(frame:CGRectMake(100, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView()
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
let v3 = UIView()
v3.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
v1.addSubview(v3)
v2.translatesAutoresizingMaskIntoConstraints = false
v3.translatesAutoresizingMaskIntoConstraints = false

v1.addConstraint(
    NSLayoutConstraint(item: v2,
        attribute: .Left,
        relatedBy: .Equal,
        toItem: v1,
        attribute: .Left,
        multiplier: 1, constant: 0)
)

v1.addConstraint(
    NSLayoutConstraint(item: v2,
        attribute: .Right,
        relatedBy: .Equal,
        toItem: v1,
        attribute: .Right,
        multiplier: 1, constant: 0)
)

v1.addConstraint(
    NSLayoutConstraint(item: v2,
        attribute: .Top,
        relatedBy: .Equal,
        toItem: v1,
        attribute: .Top,
        multiplier: 1, constant: 0)
)

v2.addConstraint(
    NSLayoutConstraint(item: v2,
        attribute: .Height,
        relatedBy: .Equal,
        toItem: nil,
        attribute: .NotAnAttribute,
        multiplier: 1, constant: 10)
)

v3.addConstraint(
    NSLayoutConstraint(item: v3,
        attribute: .Width,
        relatedBy: .Equal,
        toItem: nil,
        attribute: .NotAnAttribute,
        multiplier: 1, constant: 20)
)

v3.addConstraint(
    NSLayoutConstraint(item: v3,
        attribute: .Height,
        relatedBy: .Equal,
        toItem: nil,
        attribute: .NotAnAttribute,
        multiplier: 1, constant: 20)
)

v1.addConstraint(
    NSLayoutConstraint(item: v3,
        attribute: .Right,
        relatedBy: .Equal,
        toItem: v1,
        attribute: .Right,
        multiplier: 1, constant: 0)
)

v1.addConstraint(
    NSLayoutConstraint(item: v3,
        attribute: .Bottom,
        relatedBy: .Equal,
        toItem: v1,
        attribute: .Bottom,
        multiplier: 1, constant: 0)
)

v1和v2相关的约束都加载v1上 , v1和v3相关的约束也都加在v1上。代码量虽然多了,但是复杂度并不高,可读性也变高了。随便改变v1的frame,v2、v3都会自己适应。

效果:

1、Views - AutoLayout_第1张图片
配图

Anchor notation

iOS9开始我们可以使用一些简短紧凑的语法来描述AutoLayout,我们把关注点从约束转移到了描述约束关系的属性。
以下列出view的一些属性:

  • topAnchor, bottomAnchor
  • leftAnchor, rightAnchor, leadingAnchor, trailingAnchor
  • centerXAnchor, centerYAnchor
  • firstBaselineAnchor, lastBaselineAnchor

这些属性都是NSLayoutAnchor的对象(或者它的子类).

处理relation的一些方法:

  • constraintEqualToConstant:
  • constraintGreaterThanOrEqualToConstant:
  • constraintLessThanOrEqualToConstant:
  • constraintEqualToAnchor:
  • constraintGreaterThanOrEqualToAnchor:
  • constraintLessThanOrEqualToAnchor:
  • constraintEqualToAnchor:constant:
  • constraintGreaterThanOrEqualToAnchor:constant:
  • constraintLessThanOrEqualToAnchor:constant:
  • constraintEqualToAnchor:multiplier:
  • constraintGreaterThanOrEqualToAnchor:multiplier:
  • constraintLessThanOrEqualToAnchor:multiplier:
  • constraintEqualToAnchor:multiplier:constant:
  • constraintGreaterThanOrEqualToAnchor:multiplier:constant:
  • constraintLessThanOrEqualToAnchor:multiplier:constant:

有了这些,前面的8条约束,可以写成下面这样:

NSLayoutConstraint.activateConstraints([
                v2.leftAnchor.constraintEqualToAnchor(v1.leftAnchor),
                v2.rightAnchor.constraintEqualToAnchor(v1.rightAnchor),
                v2.topAnchor.constraintEqualToAnchor(v1.topAnchor),
                v2.heightAnchor.constraintEqualToConstant(10),
                v3.widthAnchor.constraintEqualToConstant(20),
                v3.heightAnchor.constraintEqualToConstant(20),
                v3.rightAnchor.constraintEqualToAnchor(v1.rightAnchor),
                v3.bottomAnchor.constraintEqualToAnchor(v1.bottomAnchor)
            ])

运行效果一样的,但是代码变的非常简洁了有木有。可惜从iOS 9 开始支持呀。。不过幸亏还有SnapKit库。


Visual format notation

更简写的方式,来看一个表达式:

"V:|[v2(10)]"

V: vertical(垂直)方向的尺寸。H:水平方向
| 在这里表示它的superview , 这里表示v2的顶部贴着它superview的顶部。小括号中的10表示v2的高度是10.
我们需要提前指定那个字符串代表那个view

所以我们约束表达式也可以这么写:

let d = ["v2":v2,"v3":v3]
        
NSLayoutConstraint.activateConstraints([
        NSLayoutConstraint.constraintsWithVisualFormat(
        "H:|[v2]|", options: [], metrics: nil, views: d),
        NSLayoutConstraint.constraintsWithVisualFormat(
        "V:|[v2(10)]", options: [], metrics: nil, views: d),
        NSLayoutConstraint.constraintsWithVisualFormat(
        "H:[v3(20)]|", options: [], metrics: nil, views: d),
        NSLayoutConstraint.constraintsWithVisualFormat(
        "V:[v3(20)]|", options: [], metrics: nil, views: d)
    ].flatten().map{$0})

运行效果也是一样的。前面用了8行,这里只有四行。

下面看下更多关于这个语法的东西:

  • metrics 参数是一个放numeric值的字典,允许你用name解释一个numeric数值
  • options: 是一个NSLayoutFormatOptions类型的值,主要做alignments。
  • 指定两个view之间的距离可以用这种语法"[v1]-20-[v2]"
  • 括号中的值也可以指定符号"[v1(>=20@400,<=30)]"

等多语法的只是可以在Apple’s Auto Layout Guide 的 “Visual Format Syntax” 章节学习


Constraints as objects

有时候界面需要变换,比如一个view移动到另一个地方,一个view移除,或者新增一个view,这时候我们需要事先把布局保存起来。

看下面的例子:

首先创建两个数组保存约束,并声明三个全局变量

var constraintsWith = [NSLayoutConstraint]()
var constraintsWithout = [NSLayoutConstraint]()
var v1:UIView!
var v2:UIView!
var v3:UIView!

我们首先展示的是v1\v2\v3三个view纵向排列。然后移除v2将v3的位置移动到v2。 也可以还原。

初始化信息:

let mainview = self.view
let v1 = UIView()
v1.backgroundColor = UIColor.redColor()
v1.translatesAutoresizingMaskIntoConstraints = false
let v2 = UIView()
v2.backgroundColor = UIColor.yellowColor()
v2.translatesAutoresizingMaskIntoConstraints = false
let v3 = UIView()
v3.backgroundColor = UIColor.blueColor()
v3.translatesAutoresizingMaskIntoConstraints = false
mainview.addSubview(v1)
mainview.addSubview(v2)
mainview.addSubview(v3)

self.v1 = v1
self.v2 = v2
self.v3 = v3

let c1 = NSLayoutConstraint.constraintsWithVisualFormat(
    "H:|-(20)-[v(100)]", options: [], metrics: nil, views: ["v":v1])
let c2 = NSLayoutConstraint.constraintsWithVisualFormat(
    "H:|-(20)-[v(100)]", options: [], metrics: nil, views: ["v":v2])
let c3 = NSLayoutConstraint.constraintsWithVisualFormat(
    "H:|-(20)-[v(100)]", options: [], metrics: nil, views: ["v":v3])
let c4 = NSLayoutConstraint.constraintsWithVisualFormat(
    "V:|-(100)-[v(20)]", options: [], metrics: nil, views: ["v":v1])

let c5with = NSLayoutConstraint.constraintsWithVisualFormat(
    "V:[v1]-(20)-[v2(20)]-(20)-[v3(20)]", options: [], metrics: nil,
    views: ["v1":v1, "v2":v2, "v3":v3])

let c5without = NSLayoutConstraint.constraintsWithVisualFormat(
    "V:[v1]-(20)-[v3(20)]", options: [], metrics: nil,
    views: ["v1":v1, "v3":v3])

self.constraintsWith.appendContentsOf(c1)
self.constraintsWith.appendContentsOf(c2)
self.constraintsWith.appendContentsOf(c3)
self.constraintsWith.appendContentsOf(c4)
self.constraintsWith.appendContentsOf(c5with)

self.constraintsWithout.appendContentsOf(c1)
self.constraintsWithout.appendContentsOf(c3)
self.constraintsWithout.appendContentsOf(c4)
self.constraintsWithout.appendContentsOf(c5without)

NSLayoutConstraint.activateConstraints(self.constraintsWith)

这段代码很明显,先设置了c1、c2、c3、c4 设置了他们的水平宽度和距离superview左边的20 。

c5with 是有v2的情况
c5without是没有v2的情况

然后搞一个按钮在点击的时候进行切换:

if v2.superview != nil {
    v2.removeFromSuperview()
    NSLayoutConstraint.deactivateConstraints(self.constraintsWith)
    NSLayoutConstraint.activateConstraints(self.constraintsWithout)
} else {
    self.view.addSubview(v2)
    NSLayoutConstraint.deactivateConstraints(self.constraintsWithout)
    NSLayoutConstraint.activateConstraints(self.constraintsWith)
}

效果:

1、Views - AutoLayout_第2张图片
配图

Guides and margins

一个view紧挨着顶部或者底部,有时候这个顶部或者底部是可以隐藏和显示的,那么这个距离怎么算呢。为了解决这个问题,UIViewController提供了两个不可见的view在AutoLayout中使用起来很方便。topLayoutGuidebottomLayoutGuide , topLayoutGuide匹配最顶部的bar,bottomLayoutGuide匹配最底部的bar。这两个view会随环境的改变而改变。

我们在visual format语法中也能用

let arr = NSLayoutConstraint.constraintsWithVisualFormat(
"V:[tlg]-0-[v]", options: [], metrics: nil,
views: ["tlg":self.topLayoutGuide, "v":v])

在iOS 9中也可以这样:

let tlg = self.topLayoutGuide
let c = v.topAnchor.constraintEqualToAnchor(tlg.bottomAnchor)

在iOS 8 中,我们可以获取到UIView的layoutMargins是一个UIEdgeInsets,在VC中我获取到mainView的是:

UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0)

这个是你可能需要你的view距离superview边界的最小距离。
可以通过不指名距离来使用margin

let arr = NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-[v]", options: [], metrics: nil, views: ["v":v])

我自己写了个例子:

let mainview = self.view
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.redColor()
mainview.addSubview(v)
let c1 = NSLayoutConstraint.constraintsWithVisualFormat(
    "H:|-[v]-|", options: [], metrics: nil, views: ["v":v])
let c2 = NSLayoutConstraint.constraintsWithVisualFormat(
    "V:[tlg]-[v(200)]-[blg]", options: [], metrics: nil, views: ["v":v,"tlg":self.topLayoutGuide,"blg":self.bottomLayoutGuide])
NSLayoutConstraint.activateConstraints(
    [c1,c2].flatten().map{$0}
)

运行效果:

1、Views - AutoLayout_第3张图片
配图

一个view的margin可以用以下方式表示:

  • .TopMargin, .BottomMargin
  • .LeftMargin, .RightMargin, .LeadingMargin, .TrailingMargin
  • .CenterXWithinMargins, .CenterYWithinMargins

因此,我们还可以这样创建一个约束


let c = NSLayoutConstraint(item: v,
                           attribute: .Left,
                           relatedBy: .Equal,
                           toItem: mainview,
                           attribute: .LeftMargin,
                           multiplier: 1,
                           constant: 0)

或者 这样

let c = v.leftAnchor.constraintEqualToAnchor(
            mainview.layoutMarginsGuide.leftAnchor)

Intrinsic content size and alignment rects (内建大小和对其方式)

一些iOS内置的控件本身有内建大小(一个方向或者两个方向)。
如:

  • UIButton , 默认有个高度,宽度依赖于它的title
  • UIImageView , 默认会适应它image的大小
  • UILabel , 如果宽度固定,高度可以显示多行,高度自己根据文字适应

内建size会隐式产生约束。这个约束是一个低优先级的约束。如果没有别的相关约束阻止它,才会执行。

下面看两个view的方法:

  • contentHuggingPriorityForAxis:
    阻止它自己在某个方向变大,比如有两个label在同一行紧挨着。那么两个文字都过长的时候会显示这个优先级.一般默认值是250
v.contentHuggingPriorityForAxis(UILayoutConstraintAxis.Horizontal)
v.setContentHuggingPriority(1000, forAxis: UILayoutConstraintAxis.Horizontal)

第一个是获取值,第二个是设置。第二个参数是方向。(水平/垂直)

  • contentCompressionResistancePriorityForAxis:
    阻止变小,默认值750.跟上面那个相反,用法一样
v.contentHuggingPriorityForAxis(UILayoutConstraintAxis.Horizontal)
v.setContentCompressionResistancePriority(1000, forAxis: UILayoutConstraintAxis.Horizontal)

大概就这些,后面是Stack views暂时不打算看。通过interface Builder 拖拽约束,网上例子很多。

你可能感兴趣的:(1、Views - AutoLayout)