前言
SnapKit库的使用非常简单,它受到很多开发者的喜爱和使用。那么,我们知道了怎么使用它后,有没有想过它的源码是什么样子呢?现在,就让我们来看一下SnapKit的“内心世界”到底是怎样的五彩斑斓,才能够如此引起开发者们的垂涎和追捧?
SnapKit的github链接: 猛戳这里
SnapKit文件结构
SnapKit总共有34个文件, 每个文件都有各自的功能,文件之间又相互关联,起到链接的作用。它主要是基于系统API的NSLayoutConstraint类
的一个封装, 将系统API的类、方法、属性、协议、结构体、枚举等整合起来,形成一条条独立、简洁、清爽、易用的布局链。
从上面图片中的列表可以看到,总的有34个文件,现在,你是否会一下子就感觉到头疼,这么多文件,那是不是很复杂?代码是不是特别多?一想到这里就头疼死了。
实际上,尽管SnapKit有那么多的文件,但是它们都相对来说比较简单,文件结构十分清晰,除了Constraint.swift
文件超过了200行代码,其他基本都是几行到几十行不等,有些文件只有一个协议方法,甚至有好几个文件都只是定义一个别名而已,例如:ConstraintInsets.swift
、ConstraintLayoutGuide.swift
、ConstraintLayoutSupport.swift
、ConstraintView.swift
等。
以上两张图几乎是SnapKit牵扯到的Class
、Enum
、Struct
、protocol
等结构,至于继承系统类或者器别名的没有写出来。事实上,为了更好地增强SnapKit的可读性和关联性,作者几乎对用到的系统API都做了封装。
SnapKit之snp浅析
为什么要说snp
? 它有何特别之处?对于SnapKit布局来说,有什么作用?
因为在SnapKit布局中,我们一定且必须使用到snp
, 先看下面的布局代码
let subview = UIView()
subview.backgroundColor = .purple
view.addSubview(subview)
subview.snp.makeConstraints { (make) in
make.width.equalTo(100)
make.height.equalTo(200)
make.center.equalToSuperview()
}
在布局中,snp
是当前所要布局view
的一个延展属性, 在SnapKit源码中有
说明:在
ConstraintView
的extension
中,还有很多已经废弃了的方法、属性,作者用了上面这个snp
来代替,主要是因为实现代码更加swift化
,也更加方便管理。
从本文中的文件结构的图片中可以看出,snp
是一个结构体实例对象,结构体ConstraintViewDSL
继承于ConstraintAttributesDSL协议
。而在snp
所属的结构体中,有着几个我们制作约束时所需要的实例方法和属性, 通过view.snp.
的形式,我们就可以开始进行调用ConstraintViewDSL结构体
的方法进行布局了,这是布局开始的第一步,也很好理解。
SnapKit之makeConstraints{}方法的浅析
继上面一个分析后,我们知道,在布局开始时,制作约束,必须调用ConstraintViewDSL结构体
的方法来对视图进行布局,那我们就来看一下这个方法(已经把其他方法屏蔽,便于分析)
public struct ConstraintViewDSL: ConstraintAttributesDSL {
public func makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.makeConstraints(item: self.view, closure: closure)
}
internal let view: ConstraintView
internal init(view: ConstraintView) {
self.view = view
}
}
在拿到ConstraintViewDSL结构体
实例后,从上面代码段中可以看出,我们就可以调用makeConstraints
方法了。
在调用ConstraintViewDSL结构体
的实例方法makeConstraints
的时候,我们实质上是对ConstraintMaker 类
的静态方法makeConstraints
的调用,这里主要是做了一个回调而已。
这里面有一个ConstraintViewDSL结构体
的初始化方法,用来保存当前的视图
,也就是在我们调用subview.snp
的时候, 就已经初始化结构体并且引入当前的视图
了
public var snp: ConstraintViewDSL {
return ConstraintViewDSL(view: self)
}
那么为何要引入当前的视图
呢?原因是我们在调用制作约束makeConstraints
的时候需要使用到当前的视图
, 也就是下面这句代码中的view
ConstraintMaker.makeConstraints(item: self.view, closure: closure)
调用方式就是
subview.snp.makeConstraints { (make) in
}
既然调用subview.snp.makeConstraints实质上是调用ConstraintMaker类的静态方法makeConstraints来进行布局,那么现在,我们进入到ConstraintMaker类文件中来分析它的静态方法makeConstraints,看看它是如何操作布局的
ConstraintMaker类的makeConstraints布局浅析
在ConstraintMaker类
中:
然后我们可以传入参数第一个是item
, 是一个LayoutConstraintItem
, 它是一个类协议。因为ConstraintView
继承这个协议,所以刚才在ConstraintViewDSL结构体
的makeConstraints
方法中,我们传入了当前的视图
, 即在这里, item就是我们传入的当前的view
。
我们再来看makeConstraints
中, 有这两行代码
let maker = ConstraintMaker(item: item)
closure(maker)
这里为何要使用let maker = ConstraintMaker(item: item)
, 原因是我们在调用当前类的makeConstraints
方法时,直接直接对当前类进行初始化,在获取到ConstraintMaker
的实例的同时,也对ConstraintMaker
的私有属性private let item: LayoutConstraintItem
进行了初始化,如下
private let item: LayoutConstraintItem
private var descriptions = [ConstraintDescription]()
internal init(item: LayoutConstraintItem) {
self.item = item
self.item.prepare()
}
为何要获取ConstraintMaker
的实例的原因是我们需要closure(maker)
, 也就是实例对象,这样的话,我们就可以在外面布局的时候通过make.width
、make.right
等来获取到ConstraintMaker
的width
、height
、left
、right
等属性。
接着看makeConstraints
中, 有
var constraints: [Constraint] = []
for description in maker.descriptions {
guard let constraint = description.constraint else {
continue
}
constraints.append(constraint)
}
for constraint in constraints {
constraint.activateIfNeeded(updatingExisting: false)
}
首先是定义了约束的数组constraints
, 数组中的元素Constraint
是一个约束类,你先别管里面有什么,怎么实现,你现在只要知道它是约束,并且是一个类,现在被放到一个数组中,其他的我们后面会讲到。
接着,遍历我们刚刚初始化的类实例的属性descriptions
, 也许有人就有疑问了,private var descriptions = [ConstraintDescription]()
我根本就没有初始化过,哪来的值呢?好的,别急,等下再告诉你,你只要知道它是一个ConstraintDescription约束描述类
, 遍历之后我们就可以拿到ConstraintDescription约束描述类
的约束属性constraint
了,但是它是一个可选类型,所以我们必须拆包处理,故用guard
语句判断,如果约束存在,则往所定义的约束数组中添加约束, 不存在,退出本次循环,继续判断约束是否存在,直到所有的约束都添加完毕为止。
最后,遍历刚刚添加的约束数组,并且激活所有的约束。这个activateIfNeeded(updatingExisting: false)
是约束类Constraint
中的激活约束的方法,为什么这个里面的参数updatingExisting
为何传的是false
而不是true
呢?因为这是一个makeConstraints
方法,你给一个视图才开始布局,就需要更新已经存在的约束吗?才刚开始制作约束,哪来的“已经存在”的约束呢?所以传true
的时候是针对updateConstraints
方法的。 具体细节待我一一道来。
现在我们在刚才的分析中,存在三个疑点
疑点一,
maker.descriptions
是约束描述类,但未初始化,我们却使用了?疑点二,
Constraint
是一个约束类,怎么实现的,为何要在这里创建、添加、激活?疑点三,
activateIfNeeded
怎么实现?这里为何要使用它来激活约束?
ConstraintDescription约束描述类的布局浅析
为了解释上面我们提到的几个疑点,我们首先来看一下这个ConstraintDescription约束描述类
是一个什么东西。为何不讲Constraint类
呢?不是不讲,现在我们先知道它就是一个约束类
,在刚才的makeConstraints
方法中,它是一个数组的元素,我们现在是在添加元素,而添加的元素恰好是对maker.descriptions
的一个遍历,所以,我们会先来看ConstraintDescription约束描述类
,再看Constraint约束类
。
先来看其源码
public class ConstraintDescription {
internal let item: LayoutConstraintItem
internal var attributes: ConstraintAttributes
internal var relation: ConstraintRelation? = nil
internal var sourceLocation: (String, UInt)? = nil
internal var label: String? = nil
internal var related: ConstraintItem? = nil
internal var multiplier: ConstraintMultiplierTarget = 1.0
internal var constant: ConstraintConstantTarget = 0.0
internal var priority: ConstraintPriorityTarget = 1000.0
internal lazy var constraint: Constraint? = {
guard let relation = self.relation, let related = self.related, let sourceLocation = self.sourceLocation else {
return nil
}
let from = ConstraintItem(target: self.item, attributes: self.attributes)
return Constraint(from: from, to: related, relation: relation, sourceLocation: sourceLocation, label: self.label, multiplier:self.multiplier, constant: self.constant, priority: self.priority
)
}()
// MARK: Initialization
internal init(item: LayoutConstraintItem, attributes: ConstraintAttributes) {
self.item = item
self.attributes = attributes
}
}
好,刚才在ConstraintMaker
中是不是有一个private var descriptions = [ConstraintDescription]()
私有属性呢?是的,但是,我们使用它了,却未见将它初始化,没有初始化,意味着没有值。实际上它在ConstraintMaker
中已经初始化了,我们来看里面的这个方法
internal func makeExtendableWithAttributes(_ attributes: ConstraintAttributes) -> ConstraintMakerExtendable {
let description = ConstraintDescription(item: self.item, attributes: attributes)
self.descriptions.append(description)
return ConstraintMakerExtendable(description)
}
首先是对ConstraintDescription约束描述类
进行了初始化,然后得到实例对象description
, 然后self.descriptions.append(description)
。至于这个makeExtendableWithAttributes
是干嘛的?别急,下面会讲到,这里只要知道ConstraintDescription约束描述类
进行了初始化并且maker.descriptions
有值了就好了。
从ConstraintDescription约束描述类
的源码中可以看出,它有一个constraint
属性,也就是Constraint约束类
的一个实例对象,好了,这样就清楚了为何这里可以遍历,并且放进一个刚创建的var constraints: [Constraint] = []
数组中。
分析一下ConstraintDescription约束描述类
的初始化方法,即
internal init(item: LayoutConstraintItem, attributes: ConstraintAttributes) {
self.item = item
self.attributes = attributes
}
传入了参数item
, 就是之前提到的当前view
, 另外一个是布局属性,也就是常常使用的width
、height
、leading
、right
等等。
分析一下ConstraintDescription约束描述类
的属性constraint
, 即
internal lazy var constraint: Constraint? = {
guard let relation = self.relation, let related = self.related, let sourceLocation = self.sourceLocation else {
return nil
}
let from = ConstraintItem(target: self.item, attributes: self.attributes)
return Constraint(from: from, to: related, relation: relation, sourceLocation: sourceLocation, label: self.label, multiplier:self.multiplier, constant: self.constant, priority: self.priority
)
}()
这个是一个懒加载方法,并且是可选的,开始用guard
语句判断了三个可选类型,分别是self.relation
、self.related
、self.sourceLocation
。这里可能有人又有疑惑了,这三个属性并未看见其初始化,怎么又直接使用了呢?并且,他们是什么东西?有什么作用呢?好,其实这个又跟ConstraintMaker
类中的方法makeExtendableWithAttributes
返回值(实例对象)ConstraintMakerExtendable
有关,暂且不说,只要知道这些属性只要我们调用make.width.equalTo
的时候就已经被初始化就好,因为用户不一定会正确地传入值,所以,有可能有值,有可能没值,故用可选类型定义,并且判断三个条件必须符合之后,才能制作约束。解释一下三个属性的作用:
self.relation
枚举类型,就是对系统的NSLayoutRelation的封装。即枚举值是
public enum NSLayoutRelation : Int {
case lessThanOrEqual
case equal
case greaterThanOrEqual
}
self.related
是一个ConstraintItem
类实例对象,其实就是我们传入的所要布局的目标视图。
self.sourceLocation
资源定位。是一个元组,(String,Int)
类型,也就是出错的时候定位在(#file,#line)
哪个文件,哪一行。
接着,在判断之后,创建了开始布局的视图from
, 返回Constraint约束类
的初始化后的实例对象。
到此为止,刚才牵扯到的疑问已经解决其中之一,还有两个疑问,实际上是和Constraint约束类
相关。那么接下来,我们就来讲一下Constraint约束类
。
Constraint约束类的布局浅析
这个类是非常重要的一个类,我把它当做SnapKit
的核心类,因为布局就牵扯到约束,没有约束的布局一切都是虾扯蛋的。
我们先来回答刚才的一个疑问,就是在调用makeConstraints
制作约束的时候,为何要调用
constraint.activateIfNeeded(updatingExisting: false)
为了快速回答这个问题,我们直接定位到Constraint约束类
的方法
internal func activateIfNeeded(updatingExisting: Bool = false) {
guard let item = self.from.layoutConstraintItem else {
print("WARNING: SnapKit failed to get from item from constraint. Activate will be a no-op.")
return
}
let layoutConstraints = self.layoutConstraints
if updatingExisting {
var existingLayoutConstraints: [LayoutConstraint] = []
for constraint in item.constraints {
existingLayoutConstraints += constraint.layoutConstraints
}
for layoutConstraint in layoutConstraints {
let existingLayoutConstraint = existingLayoutConstraints.first { $0 == layoutConstraint }
guard let updateLayoutConstraint = existingLayoutConstraint else {
fatalError("Updated constraint could not find existing matching constraint to update: \(layoutConstraint)")
}
let updateLayoutAttribute = (updateLayoutConstraint.secondAttribute == .notAnAttribute) ? updateLayoutConstraint.firstAttribute : updateLayoutConstraint.secondAttribute
updateLayoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: updateLayoutAttribute)
}
} else {
NSLayoutConstraint.activate(layoutConstraints)
item.add(constraints: [self])
}
}
因为之前我们穿的是Bool值是false
, 所以我们直接定位到else
部分
NSLayoutConstraint.activate(layoutConstraints)
item.add(constraints: [self])
这样就很显而易见了,万变不离其宗, 调用系统API激活约束。那么又问又有疑惑了,激活约束后怎么还有item.add(constraints: [self])
呢?这很奇怪呀!!!好,我来回答你的疑惑,这句代码目的是为了保存约束,方便你可以做移除约束的操作,如果你不信,你可以在源码中将这行代码删掉,你的约束也是正常的,并且视图也会正确显示!但是,当你调用snp.removeConstraints()
方法的时候,不起任何作用!!!调用snp.remakeConstraints
就会造成约束冲突,视图变形!
最后一个疑问,Constraint约束类的作用
,这个我们直接来看初始化方法
// MARK: Initialization
internal init(from: ConstraintItem,
to: ConstraintItem,
relation: ConstraintRelation,
sourceLocation: (String, UInt),
label: String?,
multiplier: ConstraintMultiplierTarget,
constant: ConstraintConstantTarget,
priority: ConstraintPriorityTarget) {
self.from = from
self.to = to
self.relation = relation
self.sourceLocation = sourceLocation
self.label = label
self.multiplier = multiplier
self.constant = constant
self.priority = priority
self.layoutConstraints = []
// get attributes
let layoutFromAttributes = self.from.attributes.layoutAttributes
let layoutToAttributes = self.to.attributes.layoutAttributes
// get layout from
let layoutFrom = self.from.layoutConstraintItem!
// get relation
let layoutRelation = self.relation.layoutRelation
for layoutFromAttribute in layoutFromAttributes {
// get layout to attribute
let layoutToAttribute: LayoutAttribute
#if os(iOS) || os(tvOS)
if layoutToAttributes.count > 0 {
if self.from.attributes == .edges && self.to.attributes == .margins {
switch layoutFromAttribute {
case .left:
layoutToAttribute = .leftMargin
case .right:
layoutToAttribute = .rightMargin
case .top:
layoutToAttribute = .topMargin
case .bottom:
layoutToAttribute = .bottomMargin
default:
fatalError()
}
} else if self.from.attributes == .margins && self.to.attributes == .edges {
switch layoutFromAttribute {
case .leftMargin:
layoutToAttribute = .left
case .rightMargin:
layoutToAttribute = .right
case .topMargin:
layoutToAttribute = .top
case .bottomMargin:
layoutToAttribute = .bottom
default:
fatalError()
}
} else if self.from.attributes == self.to.attributes {
layoutToAttribute = layoutFromAttribute
} else {
layoutToAttribute = layoutToAttributes[0]
}
} else {
if self.to.target == nil && (layoutFromAttribute == .centerX || layoutFromAttribute == .centerY) {
layoutToAttribute = layoutFromAttribute == .centerX ? .left : .top
} else {
layoutToAttribute = layoutFromAttribute
}
}
#else
if self.from.attributes == self.to.attributes {
layoutToAttribute = layoutFromAttribute
} else if layoutToAttributes.count > 0 {
layoutToAttribute = layoutToAttributes[0]
} else {
layoutToAttribute = layoutFromAttribute
}
#endif
// get layout constant
let layoutConstant: CGFloat = self.constant.constraintConstantTargetValueFor(layoutAttribute: layoutToAttribute)
// get layout to
var layoutTo: AnyObject? = self.to.target
// use superview if possible
if layoutTo == nil && layoutToAttribute != .width && layoutToAttribute != .height {
layoutTo = layoutFrom.superview
}
// create layout constraint
let layoutConstraint = LayoutConstraint(
item: layoutFrom,
attribute: layoutFromAttribute,
relatedBy: layoutRelation,
toItem: layoutTo,
attribute: layoutToAttribute,
multiplier: self.multiplier.constraintMultiplierTargetValue,
constant: layoutConstant
)
// set label
layoutConstraint.label = self.label
// set priority
layoutConstraint.priority = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)
// set constraint
layoutConstraint.constraint = self
// append
self.layoutConstraints.append(layoutConstraint)
}
}
其实这个也没多少需要详细讲得,无非就是先初始化属性,然后判断所要布局的视图和对应的布局视图的位置关系,这里举一个与父视图的关系,比方说,现在我刚创建一个Xcode工程,并且创建了一个子视图,我们想要让所要布局的子视图的edges
等于父视图的margins
, 那么我们可以这样写:
subview = UIView()
subview.backgroundColor = .purple
view.addSubview(subview)
subview.snp.makeConstraints { (make) in
make.edges.equalTo(view.snp.margins)
}
这非常简单,然后约束调用的是这个判断
if self.from.attributes == .edges && self.to.attributes == .margins {
switch layoutFromAttribute {
case .left:
layoutToAttribute = .leftMargin
case .right:
layoutToAttribute = .rightMargin
case .top:
layoutToAttribute = .topMargin
case .bottom:
layoutToAttribute = .bottomMargin
default:
fatalError()
}
}
为什么呢?原因是self.from.attributes == .edges && self.to.attributes == .margins
条件满足,即布局视图的attributes
就是edges
, 等于父视图的margins
, 从这句代码make.edges.equalTo(view.snp.margins)
可以很好地看出来。不清楚的话,可以将代码“照葫芦画瓢”试试。一定会调用的。
最后就添加布局约束self.layoutConstraints.append(layoutConstraint)
就搞定了。
一切基本讲得差不多了,但是,我们还有一个东西要讲,那就是关系属性make.top.right.left.trailing.leading.width.edges等等
, 往下看
ConstraintMakerExtendable类的布局浅析
要讲这个的目的是,在SnapKit
中,所有布局我们都要使用make.width
、make.right
等的形式,而后面的这些宽、高、左边、右边、前边、后边、边缘等都是ConstraintMakerExtendable
类的实例对象。
ConstraintMakerExtendable类
继承于ConstraintMakerRelatable类
,在ConstraintMakerExtendable类
中,都是这种形式
public class ConstraintMakerExtendable: ConstraintMakerRelatable {
public var left: ConstraintMakerExtendable {
self.description.attributes += .left
return self
}
public var top: ConstraintMakerExtendable {
self.description.attributes += .top
return self
}
.............................................................................
.............................................................................
.............................................................................
}
这里就是创建了一个自身的实例对象,返回自身的一个实例。关键点在于self.description.attributes += .top
, description
是ConstraintMakerRelatable
的一个属性,因为继承的关系,所以这里有self.description
, attributtes
又是ConstraintDescription
的属性,至于符号+=是作者对系统集合API的一个封装,见下面
internal func +=(left: inout ConstraintAttributes, right: ConstraintAttributes) {
left.formUnion(right)
}
也即是将相应的布局约束属性放到self.description.attributes
中, 因为在我们制作约束的时候需要用到Constraint类
(上面已经讲过了),添加约束、激活约束、甚至移除约束都离不开改属性的存在 或者说离不开 结构体ConstraintAttributes
的存在。
好,现在该穿越了,回到我们的最初的ConstraintMaker类
中:
public var left: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.left)
}
public var top: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.top)
}
.......................................................等等
可以看到,我们经常使用的形式make.left.equalTo
(还有很多)中的left
就是一个ConstraintMakerExtendable
的实例对象, 返回的是调用方法makeExtendableWithAttributes
的结果,这个方法文章中已经写过,不再重复,不懂或忘记的回过头看看。如此,布局就“万事俱备只欠东风”了,最后一步就是equalTo
,这也是必不可少的那么equalTo
究竟又是什么呢?
接着往下看你就知道了.............................
ConstraintMakerEditable类的布局浅析
在这里,equalTo()
是ConstraintMakerRelatable类
的实例方法返回的ConstraintMakerEditable类
的实例,在文章开头的文件结构中我们已经知道,ConstraintMakerEditable
继承于ConstraintMakerPriortizable
, 而ConstraintMakerPriortizable
继承于ConstraintMakerFinalizable
。 之前说过width
、height
、right
等等其实就是ConstraintMakerExtendable
类的实例,所以有make.width.equalTo()
的形式,而ConstraintMakerExtendable类
又继承于ConstraintMakerRelatable类
。这样的话,我们来看一下ConstraintMakerRelatable类
的equalTo()
方法:
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
}
@discardableResult
public func equalTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
return self.relatedTo(other, relation: .equal, file: file, line: line)
}
其中,other
就是我们传入的目标值或对象,它是一个ConstraintRelatableTarget协议
,比如我传入一个常量100
, 约束就是:
make.width.equalTo(100)
我们来看一下ConstraintRelatableTarget协议
里有什么:
public protocol ConstraintRelatableTarget {
}
extension Int: ConstraintRelatableTarget {
}
extension UInt: ConstraintRelatableTarget {
}
extension Float: ConstraintRelatableTarget {
}
extension Double: ConstraintRelatableTarget {
}
extension CGFloat: ConstraintRelatableTarget {
}
extension CGSize: ConstraintRelatableTarget {
}
extension CGPoint: ConstraintRelatableTarget {
}
extension ConstraintInsets: ConstraintRelatableTarget {
}
extension ConstraintItem: ConstraintRelatableTarget {
}
extension ConstraintView: ConstraintRelatableTarget {
}
@available(iOS 9.0, OSX 10.11, *)
extension ConstraintLayoutGuide: ConstraintRelatableTarget {
}
这样就非常明确了,为什么equalTo()
里面既可以是某个特定的值,也可以是一个对象,因为你可以传入Int
、Double
等类型的值,也可以传一个ConstraintView
类型的实例对象,这样,所有的基本工作几乎都做好了。那么,你就可以通过目标视图对象、位移、位置关系等来确定两个视图的位置关系,从而制作符合需求的相应的约束。
SnapKit的应用建议
尽量采用简洁的写法,不要画蛇添足,尽管SnapKit布局很是灵活,但是你也不能随心所欲,毕竟,团队写的代码还是需要有一个统一标准的,至少,你写的布局,要让别人能够清晰可见。
下面简洁的布局写法
- 宽和高相等,并且等于某个具体得值,比如100
建议:
make.size.equalTo(100)
不建议写成:
make.width.height.equalTo(100)
不建议写成
make.width.equalTo(100)
make.height.equalTo(100)
- 宽和高不相等,并且等于某个具体值, 比如宽=100,高=200
建议写成
make.size.equalTo(CGSize(width: 100, height: 200))
不建议写成:
make.width.equalTo(100)
make.height.equalTo(200)
- 约束关系与某个视图相等
建议写成
make.left.equalTo(view2)
make.right.equalTo(view2)
make.top.equalTo(view2)
...............................................
如果同时有几个约束关系相等,建议用链式语法,增强可读性,比如
make.left.right.top.bottom.width.height.size.equalTo(view2)
不建议写成
make.left.equalTo(view.snp.left)
make.right.equalTo(view.snp.right)
make.top.equalTo(view.snp.top)
- 如果等于父视图,直接使用equalToSuperview()
比如,view2是父视图,所要布局视图的顶部等于父视图的顶部
建议这样写
make.top.equalToSuperview()
不建议这样写:
make.top.equalTo(view2)
make.top.equalTo(view2.snp.top)
- 距离父视图某个边缘多少距离 (同一性质)
前提:所布局视图的关系和父视图的关系是同一性质的,什么叫同一性质,比如top等于父视图的top,left等于父视图的left等等
假设view2为父视图, 距离父视图顶部100dp
的距离
注:left、right、bottom等同下。
建议写成
make.top.equalTo(100)
不建议写成:
make.top.equalToSuperview().offset(100)
make.top.equalTo(view2).offset(100)
- 距离父视图某个边缘多少距离 (不同性质)
如: left 距离父视图的right为 200
make.left.equalTo(view2.snp.right).offset(200)
SnapKit源码浅析总结
该库的源码逻辑清晰,封装得很好,善于将class
、struct
、protoco
结合起来使用,文件结构也很清晰,尽管分为这么多的文件,但是每个文件相互独立有相互依赖,都实现特定的功能,层级简单,如作者用四五个文件单独为特定的class
、protocol
、enum
定义别名。
另外就是代码十分简洁,每个文件你仔细去看,34个文件只有一个文件超过200行代码。
但是,个人觉得文件太多,对类、协议等的分离太多,对于新手来说,有时候很难弄清楚功能的实现,因为你研究一个功能,就会牵扯到很多很多文件,很多类,很多协议,可能一时会有点懵逼。但是好的地方就是,你研究清楚结构后,弄清楚每个文件的作用后,就会发现比较简单,这也是为何这么多文件,这么多类和协议,但是最终我们布局的时候,几行代码就搞定了布局。
就好比你寒窗苦读,努力工作, 成为了一个很有才华的人,你的一言一行别人都觉得是名言,时刻用来提醒自己。
但是,谁又知道,你惊人的才华的外表背后,都是你对多年的血与泪的努力的一个封装。
欢迎加入 iOS(swift)开发互助群:QQ群号: 558179558, 相互讨论和学习!