iOS布局从Frame到Autosizing,再到Autolayout。Storyboard上使用Autolayout对于层级不深的页面来说比较方便,大大提升了开发速度,但App体积也会增加不少,用代码写Autolayout就不是那么方便了。如1
2
3
4
5
6
7
8
9
10
11
12// view1已经使用Autolayout布好局了
let view0 = UIView()
view0.backgroundColor = UIColor.gray
view0.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view0)
let leftConstraint = NSLayoutConstraint(item: view0, attribute: NSLayoutAttribute.left, relatedBy: NSLayoutRelation.equal, toItem: view1, attribute: NSLayoutAttribute.left, multiplier: 1, constant: 100)
let topConstraint = NSLayoutConstraint(item: view0, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 20)
let widthConstraint = NSLayoutConstraint(item: view0, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.width, multiplier: 0.5, constant: 0)
let heightConstraint = NSLayoutConstraint(item: view0, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: view0, attribute: NSLayoutAttribute.width, multiplier: 1, constant: 0)
let constraints = [leftConstraint, topConstraint, widthConstraint, heightConstraint]
NSLayoutConstraint.activate(constraints)
布好一个控件就要写很长串,还怕写错,比较麻烦。
SnapKit是对Autolayout的封装,是Objective-C Masonry的同一个团体Swift版本。
使用Swift先进语法以及拟多态的方法极大地简化了代码写Autolayout约束,如:
1 | let view1 = UIView() |
阅读起来很简单且使用方便
声明ConstraintConstantTarget(ConstraintOffsetTarget、ConstraintInsetTarget),然后通过extension对一些已经存在的或者自定义的类型通过NSLayoutAttribute来转换成NSLayoutConstraint的constant,ConstraintMultiplierTarget通过extension来改变NSLayoutConstraint的multiplier,ConstraintPriorityTarget通过extension来改变NSLayoutConstraint的priority等。
这种编程方法值得学习。
声明ConstraintAttributes,将所有的属性都定义下来
1 | internal static var none: ConstraintAttributes { return self.init(0) } |
可以看到每个属性都是2的n次方
通过 var layoutAttributes:[LayoutAttribute] 来获取NSLayoutAttribute集合,这里用到Swift OptionSet类的union、formUnion、subtract和contains语法
ConstraintView的extensions中snp变量返回ConstraintViewDSL对象,ConstraintLayoutGuideDSL是它的子类,提供prepareConstraints、makeConstraints、remakeConstraints、updateConstraints、removeConstraints等方法。
可以看到这些方法都是通过使用工厂类ConstraintMaker来管理约束的。
1 | internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) { |
ConstraintMaker的init方法:1
2
3
4internal init(item: LayoutConstraintItem) {
self.item = item
self.item.prepare()
}
以left为例(同以上举例 make.left.top.right.bottom.equalTo(0).offset(0).multipliedBy(1) )
1 | public var left: ConstraintMakerExtendable { |
ConstraintDescription是对NSLayoutConstraint的描述,生成Constraint对象,Constraint对象最终生成约束。
而之所以可以连着调用top、right、bottom得益于ConstraintMakerExtendable类: return self.makeExtendableWithAttributes(.left) 得到ConstraintMakerExtendable类,后面top1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51//ConstraintMakerExtendable
public var top: ConstraintMakerExtendable {
self.description.attributes += .top
return self
}
//ConstraintAttributes
internal func +=(left: inout ConstraintAttributes, right: ConstraintAttributes) {
left.formUnion(right)
}
//ConstraintMakerExtendable
public func equalTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
return self.relatedTo(other, relation: .equal, file: file, line: line)
}
internal func relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable {
let related: ConstraintItem
let constant: ConstraintConstantTarget
if let other = other as? ConstraintItem {
guard other.attributes == ConstraintAttributes.none ||
other.attributes.layoutAttributes.count <= 1 ||
other.attributes.layoutAttributes == self.description.attributes.layoutAttributes ||
other.attributes == .edges && self.description.attributes == .margins ||
other.attributes == .margins && self.description.attributes == .edges else {
fatalError("Cannot constraint to multiple non identical attributes. (\(file), \(line))");
}
related = other
constant = 0.0
} else if let other = other as? ConstraintView {
related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
constant = 0.0
} else if let other = other as? ConstraintConstantTarget {
related = ConstraintItem(target: nil, attributes: ConstraintAttributes.none)
constant = other
} else if #available(iOS 9.0, OSX 10.11, *), let other = other as? ConstraintLayoutGuide {
related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
constant = 0.0
} else {
fatalError("Invalid constraint. (\(file), \(line))")
}
let editable = ConstraintMakerEditable(self.description)
editable.description.sourceLocation = (file, line)
editable.description.relation = relation
editable.description.related = related
editable.description.constant = constant
return editable
}
1 | item1.attribute1 = multiplier × item2.attribute2 + constant |
relatedTo这个方法生成related关系、“to”和“to”的属性和constant
接下来的offset和multipliedBy都是ConstraintMakerEditable类的实例方法
maker.descriptions 的每一个描述都生成Constraint对象,在Constraint的init方法中1
2
3
4
5
6
7
8
9let layoutConstraint = LayoutConstraint(
item: layoutFrom,
attribute: layoutFromAttribute,
relatedBy: layoutRelation,
toItem: layoutTo,
attribute: layoutToAttribute,
multiplier: self.multiplier.constraintMultiplierTargetValue,
constant: layoutConstant
)
生成了NSLayoutConstraint对象了,在Constraint类的方法activateIfNeeded中我们也发现了 NSLayoutConstraint.activate(layoutConstraints) 方法了。