当前位置: 移动技术网 > IT编程>移动开发>IOS > 使用SnapKit遇到的问题

使用SnapKit遇到的问题

2018年09月26日  | 移动技术网IT编程  | 我要评论

爱神奔驰主题曲,江苏卫视的非诚勿扰,安华坐便

最近在使用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给当前对象绑定一个同名的属性,每次使用时获取绑定的属性的值,不懂,希望知道的同学不吝赐教。

 

 

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网