使用SnapKit遇到的问题
最近在使用snapkit过程中遇到一个问题,在github上搜索之后发现另外一个有趣的问题 问题链接
frameimagecontainer.snp.makeconstraints({ (make) in make.width.equalto(295).multipliedby(0.2) make.height.equalto(355).multipliedby(0.2) make.top.equaltosuperview().offset(self.view.frame.height/8) 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 <= 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 }
可以看到上面红色部分,此时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 > 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) } }
重点看红色部分,遍历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 { $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]) } }
当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给当前对象绑定一个同名的属性,每次使用时获取绑定的属性的值,不懂,希望知道的同学不吝赐教。
推荐阅读
-
Vue 项目中遇到的跨域问题及解决方法(后台php)
-
解决vue单页使用keep-alive页面返回不刷新的问题
-
Word与PPT进行快速转换的方法及可能遇到的问题
-
如何使用驱动人生解决手机连不上wifi热点的问题
-
解决vuejs 使用value in list 循环遍历数组出现警告的问题
-
Win10环境下安装Mysql5.7.23问题及遇到的坑
-
解决vue中使用Axios调用接口时出现的ie数据处理问题
-
关于sqlserver 2005 使用临时表的问题( Invalid object name #temptb)
-
JS中遇到的../或者./找文件的问题讲解
-
Python使用循环神经网络解决文本分类问题的方法详解