经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
使用SnapKit遇到的问题
来源:cnblogs  作者:小雨37  时间:2018/9/26 19:09:58  对本文有异议

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

  1. frameImageContainer.snp.makeConstraints({ (make) in
  2. make.width.equalTo(295).multipliedBy(0.2)
  3. make.height.equalTo(355).multipliedBy(0.2)
  4. make.top.equalToSuperview().offset(self.view.frame.height/8)
  5. make.centerX.equalToSuperview();
  6. })

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

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

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

再点进relatedTo()方法:

  1. internal func relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable {
  2. let related: ConstraintItem
  3. let constant: ConstraintConstantTarget
  4. if let other = other as? ConstraintItem {
  5. guard other.attributes == ConstraintAttributes.none ||
  6. other.attributes.layoutAttributes.count <= 1 ||
  7. other.attributes.layoutAttributes == self.description.attributes.layoutAttributes ||
  8. other.attributes == .edges && self.description.attributes == .margins ||
  9. other.attributes == .margins && self.description.attributes == .edges else {
  10. fatalError("Cannot constraint to multiple non identical attributes. (\(file), \(line))");
  11. }
  12. related = other
  13. constant = 0.0
  14. } else if let other = other as? ConstraintView {
  15. related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
  16. constant = 0.0
  17. } else if let other = other as? ConstraintConstantTarget {
  18. related = ConstraintItem(target: nil, attributes: ConstraintAttributes.none)
  19. constant = other
  20. } else if #available(iOS 9.0, OSX 10.11, *), let other = other as? ConstraintLayoutGuide {
  21. related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
  22. constant = 0.0
  23. } else {
  24. fatalError("Invalid constraint. (\(file), \(line))")
  25. }
  26. let editable = ConstraintMakerEditable(self.description)
  27. editable.description.sourceLocation = (file, line)
  28. editable.description.relation = relation
  29. editable.description.related = related
  30. editable.description.constant = constant
  31. return editable
  32. }

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

2.multipliedBy()方法:

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

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

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

  1. internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
  2. let maker = ConstraintMaker(item: item)
  3. closure(maker)
  4. var constraints: [Constraint] = []
  5. for description in maker.descriptions {
  6. guard let constraint = description.constraint else {
  7. continue
  8. }
  9. constraints.append(constraint)
  10. }
  11. for constraint in constraints {
  12. constraint.activateIfNeeded(updatingExisting: false)
  13. }
  14. }

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

constraint属性使用的懒加载,值为:

  1. internal lazy var constraint: Constraint? = {
  2. guard let relation = self.relation,
  3. let related = self.related,
  4. let sourceLocation = self.sourceLocation else {
  5. return nil
  6. }
  7. let from = ConstraintItem(target: self.item, attributes: self.attributes)
  8. return Constraint(
  9. from: from,
  10. to: related,
  11. relation: relation,
  12. sourceLocation: sourceLocation,
  13. label: self.label,
  14. multiplier: self.multiplier,
  15. constant: self.constant,
  16. priority: self.priority
  17. )
  18. }()

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

Constraint的构造方法:

  1. internal init(from: ConstraintItem,
  2. to: ConstraintItem,
  3. relation: ConstraintRelation,
  4. sourceLocation: (String, UInt),
  5. label: String?,
  6. multiplier: ConstraintMultiplierTarget,
  7. constant: ConstraintConstantTarget,
  8. priority: ConstraintPriorityTarget) {
  9. self.from = from
  10. self.to = to
  11. self.relation = relation
  12. self.sourceLocation = sourceLocation
  13. self.label = label
  14. self.multiplier = multiplier
  15. self.constant = constant
  16. self.priority = priority
  17. self.layoutConstraints = []
  18. // get attributes
  19. let layoutFromAttributes = self.from.attributes.layoutAttributes
  20. let layoutToAttributes = self.to.attributes.layoutAttributes
  21. // get layout from
  22. let layoutFrom = self.from.layoutConstraintItem!
  23.  
  24. // get relation
  25. let layoutRelation = self.relation.layoutRelation
  26. for layoutFromAttribute in layoutFromAttributes {
  27. // get layout to attribute
  28. let layoutToAttribute: LayoutAttribute
  29. #if os(iOS) || os(tvOS)
  30. if layoutToAttributes.count > 0 {
  31. if self.from.attributes == .edges && self.to.attributes == .margins {
  32. switch layoutFromAttribute {
  33. case .left:
  34. layoutToAttribute = .leftMargin
  35. case .right:
  36. layoutToAttribute = .rightMargin
  37. case .top:
  38. layoutToAttribute = .topMargin
  39. case .bottom:
  40. layoutToAttribute = .bottomMargin
  41. default:
  42. fatalError()
  43. }
  44. } else if self.from.attributes == .margins && self.to.attributes == .edges {
  45. switch layoutFromAttribute {
  46. case .leftMargin:
  47. layoutToAttribute = .left
  48. case .rightMargin:
  49. layoutToAttribute = .right
  50. case .topMargin:
  51. layoutToAttribute = .top
  52. case .bottomMargin:
  53. layoutToAttribute = .bottom
  54. default:
  55. fatalError()
  56. }
  57. } else if self.from.attributes == self.to.attributes {
  58. layoutToAttribute = layoutFromAttribute
  59. } else {
  60. layoutToAttribute = layoutToAttributes[0]
  61. }
  62. } else {
  63. if self.to.target == nil && (layoutFromAttribute == .centerX || layoutFromAttribute == .centerY) {
  64. layoutToAttribute = layoutFromAttribute == .centerX ? .left : .top
  65. } else {
  66. layoutToAttribute = layoutFromAttribute
  67. }
  68. }
  69. #else
  70. if self.from.attributes == self.to.attributes {
  71. layoutToAttribute = layoutFromAttribute
  72. } else if layoutToAttributes.count > 0 {
  73. layoutToAttribute = layoutToAttributes[0]
  74. } else {
  75. layoutToAttribute = layoutFromAttribute
  76. }
  77. #endif
  78.  
  79. // get layout constant
  80. let layoutConstant: CGFloat = self.constant.constraintConstantTargetValueFor(layoutAttribute: layoutToAttribute)
  81. // get layout to
  82. var layoutTo: AnyObject? = self.to.target
  83. // use superview if possible
  84. if layoutTo == nil && layoutToAttribute != .width && layoutToAttribute != .height {
  85. layoutTo = layoutFrom.superview
  86. }
  87. // create layout constraint
  88. let layoutConstraint = LayoutConstraint(
  89. item: layoutFrom,
  90. attribute: layoutFromAttribute,
  91. relatedBy: layoutRelation,
  92. toItem: layoutTo,
  93. attribute: layoutToAttribute,
  94. multiplier: self.multiplier.constraintMultiplierTargetValue,
  95. constant: layoutConstant
  96. )
  97. // set label
  98. layoutConstraint.label = self.label
  99. // set priority
  100. layoutConstraint.priority = LayoutPriority(rawValue: self.priority.constraintPriorityTargetValue)
  101. // set constraint
  102. layoutConstraint.constraint = self
  103. // append
  104. self.layoutConstraints.append(layoutConstraint)
  105. }
  106. }

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

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

  1. internal func activateIfNeeded(updatingExisting: Bool = false) {
  2. guard let item = self.from.layoutConstraintItem else {
  3. print("WARNING: SnapKit failed to get from item from constraint. Activate will be a no-op.")
  4. return
  5. }
  6. let layoutConstraints = self.layoutConstraints
  7. if updatingExisting {
  8. var existingLayoutConstraints: [LayoutConstraint] = []
  9. for constraint in item.constraints {
  10. existingLayoutConstraints += constraint.layoutConstraints
  11. }
  12. for layoutConstraint in layoutConstraints {
  13. let existingLayoutConstraint = existingLayoutConstraints.first { $0 == layoutConstraint }
  14. guard let updateLayoutConstraint = existingLayoutConstraint else {
  15. fatalError("Updated constraint could not find existing matching constraint to update: \(layoutConstraint)")
  16. }
  17. let updateLayoutAttribute = (updateLayoutConstraint.secondAttribute == .notAnAttribute) ? updateLayoutConstraint.firstAttribute : updateLayoutConstraint.secondAttribute
  18. updateLayoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: updateLayoutAttribute)
  19. }
  20. } else {
  21. NSLayoutConstraint.activate(layoutConstraints)
  22. item.add(constraints: [self])
  23. }
  24. }

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

  1. /* 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. */
  2. @available(iOS 8.0, *)
  3. open class func activate(_ constraints: [NSLayoutConstraint])

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

  1. internal var constraints: [Constraint] {
  2. return self.constraintsSet.allObjects as! [Constraint]
  3. }
  4. internal func add(constraints: [Constraint]) {
  5. let constraintsSet = self.constraintsSet
  6. for constraint in constraints {
  7. constraintsSet.add(constraint)
  8. }
  9. }
  10. internal func remove(constraints: [Constraint]) {
  11. let constraintsSet = self.constraintsSet
  12. for constraint in constraints {
  13. constraintsSet.remove(constraint)
  14. }
  15. }
  16. private var constraintsSet: NSMutableSet {
  17. let constraintsSet: NSMutableSet
  18. if let existing = objc_getAssociatedObject(self, &constraintsKey) as? NSMutableSet {
  19. constraintsSet = existing
  20. } else {
  21. constraintsSet = NSMutableSet()
  22. objc_setAssociatedObject(self, &constraintsKey, constraintsSet, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  23. }
  24. return constraintsSet
  25. }

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

  1. /* Create constraints explicitly. Constraints are of the form "view1.attr1 = view2.attr2 * multiplier + constant"
  2. If your equation does not have a second view and attribute, use nil and NSLayoutAttributeNotAnAttribute.
  3. */
  4. 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给当前对象绑定一个同名的属性,每次使用时获取绑定的属性的值,不懂,希望知道的同学不吝赐教。

 

 

 

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号