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被多3NSLayoutConstraint(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都会自己适应。
效果:
配图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)
}
效果:
配图Guides and margins
一个view紧挨着顶部或者底部,有时候这个顶部或者底部是可以隐藏和显示的,那么这个距离怎么算呢。为了解决这个问题,UIViewController提供了两个不可见的view在AutoLayout中使用起来很方便。topLayoutGuide
和 bottomLayoutGuide
, 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}
)
运行效果:
配图一个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 拖拽约束,网上例子很多。
网友评论