最近在使用snapkit过程中遇到一个问题,在github上搜索之后发现另外一个有趣的问题

frameImageContainer.snp.makeConstraints({ (make) in
make.width.equalTo().multipliedBy(0.2)
make.height.equalTo().multipliedBy(0.2)
make.top.equalToSuperview().offset(self.view.frame.height/)
make.centerX.equalToSuperview();
})

看起来很理所当然的,明显不可以这样写,但是具体是什么原因呢,明明没有报任何错误和警告,但是.multipliedBy()方法却没有效果,那我们来看一下snapkit源码。

1.首先点进equalTo()方法,代码是这样的:

@discardableResult
public func equalTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
return self.relatedTo(other, relation: .equal, file: file, line: line)
}

再点进relatedTo()方法:

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 <= ||
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
}

可以看到上面红色部分,此时other可以转换为ConstraintConstantTarget类型,设置related的target为nil,attributes为none,constant设置为other,最后将这些变量赋值给description属性保存。

2.multipliedBy()方法:

@discardableResult
public func multipliedBy(_ amount: ConstraintMultiplierTarget) -> ConstraintMakerEditable {
self.description.multiplier = amount
return self
}

可以看到,multipliedBy方法中只是简单的赋值,将amout值复制到description中保存。

3.再来看一下makeConstraints()方法:

internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
let maker = ConstraintMaker(item: item)
closure(maker)
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)
}
}

首先将要添加约束的对象封装成ConstraintMaker对象,再把它作为参数调用closure,开始添加约束。closure中的每条语句会先被解析ConstraintDescription对象,添加到maker的descriptions属性中。 在第一个for循环中可以看到,每次循环从descriptions中取出一条description,判断是否改description是否有constraint属性;

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
)
}()

先判断relation,related,sourceLocation的值是否为nil,若为nil则返回nil,否则就根据description属性创建一个Constraint对象并返回。

Constraint的构造方法:

    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 > {
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[]
}
} 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 > {
layoutToAttribute = layoutToAttributes[]
} 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)
}
}

重点看红色部分,遍历layoutAttributes,并根据layoutAttribute的值生成一个LayoutConstraint对象添加到layoutConstraints数组中。LayoutConstraint继承自系统类NSLayoutConstraint。

4.最后再看3中的第二个for循环,使用activeIfNeeded()方法激活约束:

 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 { $ == 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])
}
}

当updatingExisting为false时,进入else语句,使用的系统类NSLayoutConstraint的方法激活约束:

/* Convenience method that activates each constraint in the contained array, in the same manner as setting active=YES. This is often more efficient than activating each constraint individually. */
@available(iOS 8.0, *)
open class func activate(_ constraints: [NSLayoutConstraint])

并将设置过的约束添加到item的constraintSet这个私有属性中:

internal var constraints: [Constraint] {
return self.constraintsSet.allObjects as! [Constraint]
} internal func add(constraints: [Constraint]) {
let constraintsSet = self.constraintsSet
for constraint in constraints {
constraintsSet.add(constraint)
}
} internal func remove(constraints: [Constraint]) {
let constraintsSet = self.constraintsSet
for constraint in constraints {
constraintsSet.remove(constraint)
}
} private var constraintsSet: NSMutableSet {
let constraintsSet: NSMutableSet if let existing = objc_getAssociatedObject(self, &constraintsKey) as? NSMutableSet {
constraintsSet = existing
} else {
constraintsSet = NSMutableSet()
objc_setAssociatedObject(self, &constraintsKey, constraintsSet, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
return constraintsSet }

5.通过这个过程不难发现,使用make.width.equalTo(295).multipliedBy(0.2) 这种方式不能得到想要的结果。在3中Constraint的构造方法的红色部分,其实构造LayoutConstraint对象时调用的NSLayoutConstraint的便利构造器方法:

    /* Create constraints explicitly.  Constraints are of the form "view1.attr1 = view2.attr2 * multiplier + constant"
If your equation does not have a second view and attribute, use nil and NSLayoutAttributeNotAnAttribute.
*/
public convenience init(item view1: Any, attribute attr1: NSLayoutConstraint.Attribute, relatedBy relation: NSLayoutConstraint.Relation, toItem view2: Any?, attribute attr2: NSLayoutConstraint.Attribute, multiplier: CGFloat, constant c: CGFloat)

注意上面注释 view1.attr1 = view2.attr2 * multiplier + constant 如果只设置为数字,则相当于view2为nil,所以view1的属性值只能等于constant的值,不会乘以multiplier。

6.终于写完了,哈哈。 demo工程地址,对应的方法已打断点,可以跟着代码一步步调试,有助于理解。

疑问:在4中最后一部分红色字体的内容,私有属性constraintsSet为啥不直接使用,还要使用runtime给当前对象绑定一个同名的属性,每次使用时获取绑定的属性的值,不懂,希望知道的同学不吝赐教。


snapkit equalto和multipliedby方法的更多相关文章

  1. Swift - 自动布局库SnapKit的使用详解1(配置、使用方法、样例)

    为了适应各种屏幕尺寸,iOS 6后引入了自动布局(Auto Layout)的概念,通过使用各种 Constraint(约束)来实现页面自适应弹性布局. 在 StoryBoard 中使用约束实现自动布局 ...

  2. swift约束框架SnapKit使用

    一.Swift - 自动布局库SnapKit的使用详解1(配置.使用方法.样例)   为了适应各种屏幕尺寸,iOS 6后引入了自动布局(Auto Layout)的概念,通过使用各种 Constrain ...

  3. iOS SnapKit自动布局使用详解

    对于自动布局: 我们在 StoryBoard 中可以使用约束实现,简单明了,但如果用纯代码来设置约束就很麻烦了 OC里面,我们常用的有Masonry,SDAutoLayout Swift里,我们有Sn ...

  4. iOS SnapKit自动布局使用详解(Swift版Masonry)

    对于自动布局: 我们在 StoryBoard 中可以使用约束实现,简单明了,但如果用纯代码来设置约束就很麻烦了 OC里面,我们常用的有Masonry,SDAutoLayout Swift里,我们有Sn ...

  5. snapkit更新约束崩溃的问题

    最近在使用snapkit布局时,竟然发现更新约束会导致崩溃,为什么这样呢? checkButton.snp.makeConstraints { (make) in make.left.top.equa ...

  6. AutoLayout框架Masonry使用心得

    AutoLayout框架Masonry使用心得 字数1769 阅读1481 评论1 喜欢17 我们组分享会上分享了页面布局的一些写法,中途提到了AutoLayout,会后我决定将很久前挖的一个坑给填起 ...

  7. IOS自适应库---- Masonry的使用

    Masonry是一个轻量级的布局框架,拥有自己的描述语法,采用更优雅的链式语法封装自动布局,简洁明了并具有高可读性,而且同时支持 iOS 和 Max OS X.Masonry是一个用代码写iOS或OS ...

  8. iOS-屏幕适配-UI布局

    iOS 屏幕适配:autoResizing autoLayout和sizeClass 一.图片解说 -------------------------------------------------- ...

  9. Coding源码学习第四部分(Masonry介绍与使用(二))

    接上篇,本篇继续对Masonry 进行学习,接上篇示例: (6)Masonry 布局实现iOS 计算器 - (void)exp4 { WS(weakSelf); // 申明区域,displayView ...

随机推荐

  1. C#+SharpMap的相关代码

    //放大的代码: private void MapZoomIn(NameValueCollection queryString) { SharpMap.Map map = Session[" ...

  2. python的学习笔记之——time模块常用内置函数

    1.Python time time()方法 Python time time() 返回当前时间的时间戳(1970纪元后经过的浮点秒数). time()方法语法: time.time() 举例: #! ...

  3. 【Android】RxJava的使用(一)基本用法

    前言 最近经常看到RxJava这个字眼,也看到很多人在博客中推荐使用RxJava.好奇的我怎么能错过,于是Google了一下,说RxJava好用的和说RxJava难用的都有,于是自己也亲自尝试了一下( ...

  4. python处理excel(一):读

    功能:读取一个excel里的第2个sheet,将该sheet的内容全部输出. #coding=utf8 import xlrd def read_excel(): workbook = xlrd.op ...

  5. 探究MySQL MGR的读写分离

    1:现有环境 机器 MySQL 环境 172.16.128.240 MGR NODE1 MGR 172.16.128.241 MGR NODE2 MGR 172.16.128.242 MGR NODE ...

  6. Jmeter入门--安装教程

    jmeter简介 Apache JMeter是Apache组织开发的基于Java的压力测试工具.用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域. 它可以用于测试静态和动 ...

  7. RESTful 架构基础

    源自:https://mp.weixin.qq.com/s/wEr2jAVphzB1G_MISlLU0w REST(Representational State Transfer)架构风格是一种世界观 ...

  8. Python初学者第二十二天 函数进阶(1)

    22day 1.函数命名空间: 2.函数作用域的查找顺序:LEGB locals->enclosing function ->globals ->_builtins_ a.local ...

  9. Hadoop HBase概念学习系列之模式设计(十)

      Hbase与RDBMS的区别在于:HBase的Cell(每条数据记录中的数据项)是具有版本描述的(versioned),行是有序的,列(qualifier)在所属列簇(Column familie ...

  10. 最强自定义PHP集成环境,系统缺失dll和vc也能正常运行

    PHPWAMP支持iis.apache.nginx等web服务器,并全部支持php多版本同时运行,无限自定义mysql.php PHPWAMP支持32和64,支持自定义自动匹配系统所需dll和vc,纯 ...