AutoLayout 是什么?
首先看字面意思就是自动布局,恩,对,AutoLayout讲的就是 iOS 中对于视图布局的自动布局。那么视图如何选择使用autolayout呢? 简单地说,它是通过约束来实现的。 约束就告诉自动布局引擎,我们希望它在此视图上执行布局以及布置视图的方式。 那么现在我们了解一下什么是约束。
Constraints(约束)
NSLayoutConstraint是继承自NSObject的一个类,也就是用来做约束的一个类。约束是对于视图而言的。 视图可以有许多约束,那么我们怎么给视图添加或者移除约束呢?
现在来说两种方法给视图添加或者移除约束:
**第一种方法:**通过UIView的实例方法来添加和移除自身的约束:(具体看下边 UIView 的扩展)
extension UIView {
@available(iOS 6.0, *)
open var constraints: [NSLayoutConstraint] { get }
@available(iOS 6.0, *)
open func addConstraint(_ constraint: NSLayoutConstraint)
@available(iOS 6.0, *)
open func addConstraints(_ constraints:
[NSLayoutConstraint])
@available(iOS 6.0, *)
open func removeConstraint(_ constraint:
NSLayoutConstraint)
@available(iOS 6.0, *)
open func removeConstraints(_ constraints:
[NSLayoutConstraint])
}
复制代码
第二种方法: 通过NSLayoutConstraint的一个类方法来给视图添加约束,具体如下: 在看NSLayoutConstraint的实例化方法的时候,会发现他还有类方法,我们看下面两个方法:
// 添加约束
@available(iOS 8.0, *)
open class func activate(_ constraints:
[NSLayoutConstraint])
// 移除约束
@available(iOS 8.0, *)
open class func deactivate(_ constraints:
[NSLayoutConstraint])
复制代码
实例化NSLayoutConstraint
我们可以看到以上两种方法都需要一个NSLayoutConstraint类型的参数,如何实例化一个NSLayoutConstraint对象呢? 那么我们来看一下 NSLayoutConstraint 的初始化方法:
public convenience init( item view1: Any,
attribute attr1: NSLayoutAttribute,
relatedBy relation: NSLayoutRelation,
toItem view2: Any?, attribute
attr2: NSLayoutAttribute,
multiplier: CGFloat,
constant c: CGFloat)
复制代码
初始化方法中有很多参数,接下来我们来讲一下各个参数的意义:
item: 可以看到他后边还有一个 view1 ,个人认为一般的他就是一个 UIView 或者其子类对象,是要进行约束的那个视图
attribute: 是一个NSLayoutAttribute枚举,可以看到他的枚举值有 left、right、bottom、top 等,这个参数就是第一个参数中 view 所要进行的约束的位置
relatedBy: 是一个NSLayoutRelation枚举,他的枚举值有 lessThanOrEqual(小于等于)、equal(等于)、 greaterThanOrEqual(大于等于)。 这个参数是用来指定 view1和接下来那个参数 view2两个视图之 间的约束关系的
toItem: 和第一个参数一样,这个主要就是来和第一个 view 做参照的那个 视图
attribute: 和第二个参数一样,是来表示第一个视图对第二个视图的 参考位置 ,上下左右 还是 center等
multiplier: 乘数的意思,CGFloat类型。是来计算两个视图之间位置关系的 一个重要因素
constant: 常数, CGFloat类型。也是计算两个视图位置关系的重要因素
这几个参数所构造出来的NSLayoutConstraint实例,添加到视图之后的位置关系到底是怎样的呢,可以用下面 这个公式 来计算得出:
item的attribute relatedBy toItem的attribute * multiplier + constant
简化之后就是:
A 视图 = B视图 * multiplier + constant;
复制代码
公式中的等于号可以根据 relatedBy 参数来变为 >= 或者 <=
接下来我们来实现添加约束
第一种方法view 的实例方法
let viewItem = UIView()
// 一定要禁止 autoresize
viewItem.translatesAutoresizingMaskIntoConstraints = false
viewItem.backgroundColor = UIColor.brown
// 必须先添加到 View上 然后再去做约束 因为约束如果要用到父视图 不提前添加 怎么知道谁是父视图
view.addSubview(viewItem)
// 添加和父视图view X 中心对齐
let centerXConstrains = NSLayoutConstraint( item: viewItem,
attribute: .centerX,
relatedBy: .equal,
toItem: view,
attribute: .centerX,
multiplier: 1, constant: 0)
// 添加和父视图 Y 中心对其的约束
let centerYConstrains = NSLayoutConstraint( item: viewItem,
attribute: .centerY,
relatedBy: .equal,
toItem: view,
attribute: .centerY,
multiplier: 1,
constant: 0)
// 添加和父视图 宽的关系 为1:2约束
let heightConstrains = NSLayoutConstraint( item: viewItem,
attribute: .width ,
relatedBy: .equal,
toItem: view,
attribute: .width,
multiplier: 1 / 2,
constant: 0)
// 添加自身视图宽 高的关系 为1:1 的约束
let widthConstrains = NSLayoutConstraint( item: viewItem,
attribute: .height ,
relatedBy: .equal,
toItem: viewItem,
attribute: .width,
multiplier: 1,
constant: 0)
// 一定要分清楚约束是相对于谁加的 加给谁的
// view.addConstraint(centerXConstrains)
// view.addConstraint(centerYConstrains)
// view.addConstraint(heightConstrains)
// 此处可以向上边注释的那样一个一个添加,也可以以一个数组来一起添加
view.addConstraints([centerXConstrains,
centerYConstrains,
heightConstrains])
viewItem.addConstraint(widthConstrains)
复制代码
以上代码实现了一个中心点在父视图中心,宽等于父视图宽一半的一个正方形。细心的人会看到 addConstraint方法有的是 view 调用,有的是 viewItem 调用。的确约束具体加给谁也是有原则的。
具体总结如下:
1.如果两个视图(也就是参数 item 和 toItem)是父子关系,设置子控件的约束,约束添加到父控件上
2.如果两个视图(也就是参数 item 和 toItem)是兄弟关系,设置两兄弟的约束,约束会添加到第一个共同的父控件上
3.如果两个视图(也就是参数 item 和 toItem)是同一个视图,约束会添加到自己上
使用第二种方法 :NSLayoutConstraint的类方法实现约束 在上边我们看了NSLayoutConstraint的类方法,很明显他是 iOS8 之后才适用的,这个方法参数是一个NSLayoutConstraint数组。 那么NSLayoutConstraint的实例化和上边一样,只是最终加给视图约束的方法不同,具体实现如下:
let view1 = UIView()
view1.backgroundColor = UIColor.blue
view1.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view1)
// view1 和 viewItem 的 Y 中心对称
let topContrians = NSLayoutConstraint(item: view1,
attribute: .top,
relatedBy: .equal,
toItem: viewItem,
attribute: .centerY,
multiplier: 1,
constant: 0)
// view1 和 viewItem 的 bottom 对其
let bottomContrians = NSLayoutConstraint(item: view1,
attribute: .bottom,
relatedBy: .equal,
toItem: viewItem,
attribute: .bottom,
multiplier: 1,
constant: 0)
// view1 和 viewItem 的 width 相等
let widthContrains = NSLayoutConstraint(item: view1,
attribute: .width,
relatedBy: .equal,
toItem: viewItem,
attribute: .width,
multiplier: 1,
constant: 0)
// view1 和 viewItem 的 leading 对齐
let leadingContrains = NSLayoutConstraint(item: view1,
attribute: .leading,
relatedBy: .equal,
toItem: viewItem,
attribute: .leading,
multiplier: 1,
constant: 0)
// iOS8 以后 NSLayoutConstraint 的类方法 也可以把约束添加到视图上,而且省掉了判断添加到那个视图上的问题,避免了上面例子中因为视图添加错误而导致的崩溃
NSLayoutConstraint.activate([topContrians,bottomContrians,widthContrains,leadingContrains])
复制代码
最终添加给视图约束的方法就是这个类方法。
Anchor notation 做约束
从iOS 9开始,又一种新的方式来做约束。 其 本质也是通过上边两种方法给视图添加约束,只不过是获取NSLayoutConstraint的实例使用了Anchor,就是通过 Anchor 来实现。
以下几个属性就是 View 的 Anchor notation扩展,主要也是视图的上下左右等约束点的Anchor
extension UIView {
@available(iOS 9.0, *)
open var leadingAnchor: NSLayoutXAxisAnchor { get }
@available(iOS 9.0, *)
open var trailingAnchor: NSLayoutXAxisAnchor { get }
@available(iOS 9.0, *)
open var leftAnchor: NSLayoutXAxisAnchor { get }
@available(iOS 9.0, *)
open var rightAnchor: NSLayoutXAxisAnchor { get }
@available(iOS 9.0, *)
open var topAnchor: NSLayoutYAxisAnchor { get }
@available(iOS 9.0, *)
open var bottomAnchor: NSLayoutYAxisAnchor { get }
@available(iOS 9.0, *)
open var widthAnchor: NSLayoutDimension { get }
@available(iOS 9.0, *)
open var heightAnchor: NSLayoutDimension { get }
@available(iOS 9.0, *)
open var centerXAnchor: NSLayoutXAxisAnchor { get }
@available(iOS 9.0, *)
open var centerYAnchor: NSLayoutYAxisAnchor { get }
@available(iOS 9.0, *)
open var firstBaselineAnchor: NSLayoutYAxisAnchor { get }
@available(iOS 9.0, *)
open var lastBaselineAnchor: NSLayoutYAxisAnchor { get }
}
复制代码
可以看到 View 的 Anchor 属性都是 NSLayoutYAxisAnchor类型的,那么NSLayoutYAxisAnchor是什么呢?
@available(iOS 9.0, *)
open class NSLayoutAnchor<AnchorType : AnyObject> : NSObject {
open func constraint(equalTo anchor:
NSLayoutAnchor<AnchorType>) -> NSLayoutConstraint
open func constraint(greaterThanOrEqualTo anchor:
NSLayoutAnchor<AnchorType>) -> NSLayoutConstraint
open func constraint(lessThanOrEqualTo anchor:
NSLayoutAnchor<AnchorType>) -> NSLayoutConstraint
open func constraint(equalTo anchor:
NSLayoutAnchor<AnchorType>,
constant c: CGFloat) -> NSLayoutConstraint
open func constraint(greaterThanOrEqualTo anchor:
NSLayoutAnchor<AnchorType>,
constant c: CGFloat) -> NSLayoutConstraint
open func constraint(lessThanOrEqualTo anchor:
NSLayoutAnchor<AnchorType>,
constant c: CGFloat) -> NSLayoutConstraint
}
@available(iOS 9.0, *)
open class NSLayoutXAxisAnchor:
NSLayoutAnchor<*NSLayoutXAxisAnchor*> {
}
复制代码
NSLayoutXAxisAnchor就是NSLayoutAnchor的一个子类,NSLayoutAnchor有6个constraint的方法,具体看一下参数就知道有什么区别了,这里就不一一赘述了。每个方法都可以返回一个 NSLayoutConstraint 类型的实例。那么我们就可以使用返回的实例,利用上边的两种方法给视图添加约束了,具体如下:(此处用的 第二种类方法)
let view2 = UIView()
view.addSubview(view2)
view2.translatesAutoresizingMaskIntoConstraints = false
view2.backgroundColor = UIColor.cyan NSLayoutConstraint.activate(
[view2.leadingAnchor.constraint(equalTo:
view1.leadingAnchor),
view2.trailingAnchor.constraint(equalTo:
view1.trailingAnchor),
view2.topAnchor.constraint(equalTo:
view1.bottomAnchor),
view2.heightAnchor.constraint(equalTo:
view1.heightAnchor)])
复制代码
Visual format natation 来做约束
visual format 是一种基于文本来缩略的创建约束的速记(简写)方法 ,具有 允许同时描述多个约束的优点,并且特别适合于当水平或垂直地布置一系列视图的情况。 例子:"V:|[v2(10)]"
各个符号的意义:
V:表示正在的垂直维度; 如果是H:水平维度
|:竖线(|)表示父视图
v2: 视图的名称显示在方括号中,v2就是视图的名称
10:视图名称之后的小括号中的数字,表示视图的大小。 V的话就是高,H 就是宽,根据视图的排列方向而定
那么怎么样使用visual format呢? 我们来看一下NSLayoutConstraint的另一个类方法:
open class func constraints(withVisualFormat
format: String,
options opts: NSLayoutFormatOptions = [],
metrics: [String : Any]?,
views: [String : Any])-> [NSLayoutConstraint]
复制代码
这个类方法最终返回了一个NSLayoutConstraint数组,那么我们又可以使用上边的两个方法给视图添加约束了
要使用visual format,必须提供一个字典,将visual format 字符串提到的每个视图的字符串名称对应到实际视图,例如:
创建两个视图v1和 v2:
let v1 = UIView()
v1.backgroundColor = UIColor.blue
v1.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v1)
let v2 = UIView()
v2.backgroundColor = UIColor.cyan
v2.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v2)
复制代码
然后就创建所谓的字典:
// 字典d 就是将字符串 v1对应实际的视图 v1 ,"v2"对应 v2.了解字典的就很
容易懂什么意思
let d = ["v1":v1,"v2": v2]
复制代码
具体使用我们通过一段代码来看:(以下代码中注释是对方法的第一个String 类型的参数做出的解释)
NSLayoutConstraint.activate([
//左右两边都有 | 就是说水平方向父视图的两边 距离都为零
NSLayoutConstraint.constraints(withVisualFormat:
"H:|[v1]|", metrics: nil, views: d),
// 左边有 | 就是说竖直方向父视图的上方 距离为0 高度为100
NSLayoutConstraint.constraints(withVisualFormat:
"V:|[v1(100)]", metrics: nil, views: d),
// 左边有 | 就是说水平方向 距离父视图的左边为0 且宽度为140
NSLayoutConstraint.constraints(withVisualFormat:
"H:|[v2(40)]", metrics: nil, views: d),
// 右边有 | 就是说竖直方向距离父视图下方为40 高度为120
NSLayoutConstraint.constraints(withVisualFormat:
"V:[v2]-(40)-|", metrics: nil, views: d),
// 没有 |(竖线),也就是说这个约束和父视图没有关系了。
是[v1]-0-[v2]这样的一个形式,这说的是V(竖直)方向上v1和v2相聚为20
NSLayoutConstraint.constraints(withVisualFormat:
"V:[v1]-20-[v2]", metrics: nil, views: d)].flatMap{$0})
复制代码
上边这种方法不常用,也就不做太多的解释了
总结
AutoLayout 就是要用约束来实现的,既然要用约束就离不开 NSLayoutConstraint这个类,最终得到NSLayoutConstraint的类实例,然后通过上边的两种方法任何一种就可以把约束添加到视图上,完成自动布局了。
建议还是用类方法,出错的概率更低一些。
布局约束的重要计算公式:
View1 = View2 * multiplier + constant
复制代码
如果你会 StoryBoard随便拖进去一个控件做约束: 可以看到约束的这个界面,一目了然:
哪两个视图(ViewItem 和 SuperView)在做约束, 参考的是视图的哪个位置(CenterY), 两个视图之间的关系(Equal) 常量关系(Constant) 优先级 (Priority) 倍数关系 (Multiplier)
计算公式:
First Item = Second Item * Multiplier + Constant复制代码