SnapKit淺析,原理

SnapKit的原理是啥盹兢,或者說(shuō)是怎么實(shí)現(xiàn)自動(dòng)布局的?

  1. SnapKit是Swift開(kāi)發(fā)中常用的自動(dòng)布局的三方庫(kù)辰晕,雖然他讓我們寫(xiě)UI布局方便了很多蛤迎,但是他還是基于系統(tǒng)提供的API做的封裝,也就是說(shuō)含友,自動(dòng)布局是系統(tǒng)提供的方法替裆。系統(tǒng)提供的約束類(lèi)是NSLayoutConstraint校辩。

  2. 咱們?cè)谟米詣?dòng)布局的時(shí)候,其實(shí)就是在寫(xiě)一個(gè)公式 view1.attr1 = view2.attr2 * multiplier + constan 或者 view1.attr1 <= view2.attr2 * multiplier + constantt 或者 view1.attr1 >= view2.attr2 * multiplier + constant辆童,就是view1的某個(gè)邊宜咒,或者寬高,等于(或者大于等于把鉴,或者小于等于)另一個(gè)view2的某一個(gè)邊或者寬高故黑,然后倍數(shù)是多少,偏移量是多少庭砍,比如

    view1.snp.makeConstraints { (make) in
          make.centerY.equalTo(view2)
          make.height.equalTo(view2)
          make.left.equalTo(view2).offset(12)
          make.width.equalTo(40)
     }
    
  3. 看看系統(tǒng)的API是啥樣的呢场晶,NSLayoutConstraint有兩種寫(xiě)法,一種是用 VFL語(yǔ)言的規(guī)則,官方API如下

    /* Create an array of constraints using an ASCII-art-like visual format string.  The values of       the `metrics` dictionary should be NSNumber (or some other type that responds to -doubleValue and returns a double).  */
    @available(iOS 6.0, *)
    open class func constraints(withVisualFormat format: String, options opts: NSLayoutConstraint.FormatOptions = [], metrics: [String : Any]?, views: [String : Any]) -> [NSLayoutConstraint]
    
    

    寫(xiě)法呢就是咱們偶爾會(huì)見(jiàn)到的,如下

    V:|-0-[title]-0-|"
    

    語(yǔ)法規(guī)則就是這種

    • "H" 表示水平方向怠缸,"V"表示垂直方向诗轻;
    • "|" 表示superview的邊界;
    • "[]" 表示view揭北,"()"表示尺寸
    • "-" 表示間隙扳炬;
    • "@"表示優(yōu)先級(jí)

    第一種不是重點(diǎn),因?yàn)镾napKit不是基于這種寫(xiě)法實(shí)現(xiàn)的搔体,今天既然講的是SnapKit淺析恨樟,所以不會(huì)著重將第一種(其實(shí)是我不習(xí)慣這種寫(xiě)法,總是忘記每個(gè)符號(hào)代表啥??)

    第二種的API如下

    /* 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.
        Use of this method is not recommended. Constraints should be created using anchor objects on views and layout guides.
    */
       @available(iOS 6.0, *)
       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)
    

    個(gè)人感覺(jué)第二種簡(jiǎn)單易懂疚俱,明確表示啥關(guān)系劝术,不需要記住各種字符,只要遵循這個(gè)公式就可以:view1.attr1 = view2.attr2 * multiplier + constan 或者 view1.attr1 <= view2.attr2 * multiplier + constantt 或者 view1.attr1 >= view2.attr2 * multiplier + constant计螺,SnapKit就是對(duì)第二種用法的封裝夯尽,后面會(huì)詳細(xì)說(shuō)明

如果不用SnapKit,咱們?cè)趺磳?shí)現(xiàn)自動(dòng)布局呢

  1. 如果不用SnapKit登馒,咱們就直接用系統(tǒng)提供的API進(jìn)行自動(dòng)布局匙握,讓咱們?cè)囋囅到y(tǒng)API用法和效果咋樣

接下來(lái)咱們就開(kāi)始用第二種開(kāi)發(fā)了,如第二種API中寫(xiě)的:如果view1的布局和view2的布局有關(guān)系陈轿,則就遵循view1.attr1 = view2.attr2 * multiplier + constant這個(gè)規(guī)則圈纺,但是如果兩個(gè)沒(méi)關(guān)系,比如view1的寬就是固定值100麦射,是和view2沒(méi)關(guān)系的蛾娶,那就將view2賦值成nil,attr2賦值成NSLayoutAttributeNotAnAttribute

我創(chuàng)建一個(gè)UILabel潜秋,添加到view上蛔琅,label的頂部距view的距離是100钠至,label的左邊距view的距離是30挂脑,label的寬是小于等于200调俘,那么約束可以這樣寫(xiě)

let lable = UILabel()
lable.backgroundColor = .red
lable.numberOfLines = 0
lable.text = "The constraint must involve only views that are within scope of the receiving view. Specifically, any views involved must be either the receiving view itself, or a subview of the receiving view."
self.view.addSubview(lable)
     
let topConstraint = NSLayoutConstraint(item: lable, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 100)
     
let leadingConstraint = NSLayoutConstraint(item: lable, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1, constant: 30)

//label的寬度和view沒(méi)關(guān)系時(shí)奖恰,可以傳nil和NSLayoutConstraint.Attribute.notAnAttribute
let widthConstraint = NSLayoutConstraint(item: lable, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.lessThanOrEqual, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 200)

然后怎么讓這些約束生效呢,方法就是把這些約束添加到對(duì)應(yīng)的視圖上寨躁,添加到哪個(gè)視圖上穆碎,哪個(gè)視圖就是接受約束的視圖,也就是接受視圖职恳,那么規(guī)則就是\color{red}{約束中所涉及的任何視圖都必須是接收約束視圖的本身或接收視圖的子視圖}所禀。假如一個(gè)約束涉及到兩個(gè)view,view1和view2放钦,如果view1是view2的父視圖色徘,那么接收視圖就是view1,因?yàn)関iew1和約束中涉及到的視圖的關(guān)系不是本身就是子視圖。假如view1和view2層級(jí)不是父子關(guān)系操禀,則接收約束的視圖贺氓,就是他倆的公共視圖,只有他倆的公共視圖床蜘,才滿(mǎn)足條件如果你接收約束的視圖不滿(mǎn)足這個(gè)條件會(huì)怎么樣呢,很簡(jiǎn)單就是\color{red}{崩潰}蔑水,還記得你忘記把被約束的view添加到父視圖上邢锯,然后崩潰的場(chǎng)景了嗎,如下:沒(méi)添加到父視圖,但是添加了約束(因?yàn)檫@會(huì)導(dǎo)致找不到滿(mǎn)足條件的接收視圖搀别,或者接收視圖不滿(mǎn)足條件丹擎,就會(huì)導(dǎo)致崩潰)

let blueView = UIView()
     blueView.tag = 666;
     blueView.translatesAutoresizingMaskIntoConstraints = false
     blueView.backgroundColor = .blue
     //self.view.addSubview(blueView)
     blueView.snp.makeConstraints { (make) in
         make.leading.equalTo(100)
         make.top.equalTo(200)
         make.width.height.equalTo(200)
     }
image.png
  1. 按照官方的規(guī)則,滿(mǎn)足條件的就是self.view歇父,所以就有了將約束添加到self.view上
self.view.addConstraint(topConstraint)
self.view.addConstraint(leadingConstraint)
self.view.addConstraint(widthConstraint)

然后就生效了嗎蒂培,答案是沒(méi)有,因?yàn)槟J(rèn)情況下一個(gè)view創(chuàng)建后榜苫,會(huì)根據(jù)當(dāng)前的frame自動(dòng)創(chuàng)建NSLayoutConstraint护戳,所以再一次添加約束,就可能會(huì)導(dǎo)致約束沖突垂睬,所以需要讓被約束的view不自動(dòng)創(chuàng)建約束媳荒,所以還需要設(shè)置一下translatesAutoresizingMaskIntoConstraints

//If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set this property to false

lable.translatesAutoresizingMaskIntoConstraints = false

這樣就大功告成了,完整的代碼如下

let lable = UILabel()
lable.translatesAutoresizingMaskIntoConstraints = false
lable.backgroundColor = .red
lable.numberOfLines = 0
lable.text = "The constraint must involve only views that are within scope of the receiving view. Specifically, any views involved must be either the receiving view itself, or a subview of the receiving view."
self.view.addSubview(lable)
     
let topConstraint = NSLayoutConstraint(item: lable, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 100)
     
let leadingConstraint = NSLayoutConstraint(item: lable, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.left, multiplier: 1, constant: 30)
     
let widthConstraint = NSLayoutConstraint(item: lable, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.lessThanOrEqual, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 200)
     
self.view.addConstraint(topConstraint)
self.view.addConstraint(leadingConstraint)
self.view.addConstraint(widthConstraint)
label.png

從iOS8以后驹饺,添加約束的方法變的很方便了钳枕,可以直接將約束的isActive設(shè)置為T(mén)rue,但是實(shí)質(zhì)上還是上面說(shuō)的。說(shuō)addConstraint的方式就是為了說(shuō)明約束添加的規(guī)則赏壹,以及為啥不添加父視圖上就會(huì)崩潰

topConstraint.isActive = true
leadingConstraint.isActive = true
widthConstraint.isActive = true

NSLayoutConstraint還有哪些用法

  1. bottom鱼炒、lastBaseline、bottomMargin有啥區(qū)別
    下圖創(chuàng)建了黃蝌借、藍(lán)昔瞧、綠三個(gè)View,大小是一樣的指蚁,區(qū)別就在于他們的頂部約束是不一樣的,
    黃色視圖的top等于label的bottom硬爆,藍(lán)色視圖的top等于label的lastBaseline欣舵,綠色視圖等于label的bottomMargin,代碼如下

    let yellowViewTopConstraint = NSLayoutConstraint(item: yellowView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: label, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1, constant: 0)
    
    let blueViewTopConstraint = NSLayoutConstraint(item: blueView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: label, attribute: NSLayoutConstraint.Attribute.lastBaseline, multiplier: 1, constant: 0)
    
    let greenViewTopConstraint = NSLayoutConstraint(item: greenView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: label, attribute: NSLayoutConstraint.Attribute.bottomMargin, multiplier: 1, constant: 0)
    

    效果如下

    黃缀磕、藍(lán)缘圈、綠三個(gè)View.png

    結(jié)論:left、right袜蚕、top糟把、bottom都是代表視圖的邊界
    firstBaseline 、lastBaseline 代表Label或Button上文字頂部和文字底部的邊界牲剃,并不是label或Button的邊界
    bottomMargin代表視圖邊緣空白處遣疯,大部分視圖默認(rèn)是8,但是也會(huì)根據(jù)安全區(qū)域變化凿傅,想了解更深的可以自己找資料看下

  2. 約束優(yōu)先級(jí)
    比如布局了黃藍(lán)綠三個(gè)View,藍(lán)色View的left距離黃色View右邊20,綠色view的左邊距離藍(lán)色View的右邊20缠犀,布局如下


    約束優(yōu)先級(jí).png

    用原生的NSLayoutConstraint寫(xiě)法是

    let yellowView = UIView()
    yellowView.translatesAutoresizingMaskIntoConstraints = false
    yellowView.backgroundColor = .yellow
    self.view.addSubview(yellowView)
         
    let yellowViewTopConstraint = NSLayoutConstraint(item: yellowView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 100)
         
    let yellowViewLeadingConstraint = NSLayoutConstraint(item: yellowView, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.left, multiplier: 1, constant: 30)
         
    let yellowViewWidthConstraint = NSLayoutConstraint(item: yellowView, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 100)
         
    let yellowViewHeightConstraint = NSLayoutConstraint(item: yellowView, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 100)
         
    yellowViewTopConstraint.isActive = true
    yellowViewLeadingConstraint.isActive = true
    yellowViewWidthConstraint.isActive = true
    yellowViewHeightConstraint.isActive = true
         
    let blueView = UIView()
    blueView.tag = 666;
    blueView.translatesAutoresizingMaskIntoConstraints = false
    blueView.backgroundColor = .blue
    self.view.addSubview(blueView)
         
    let blueViewTopConstraint = NSLayoutConstraint(item: blueView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: yellowView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0)
         
    let blueViewLeadingConstraint = NSLayoutConstraint(item: blueView, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: yellowView, attribute: NSLayoutConstraint.Attribute.right, multiplier: 1, constant: 20)
         
    let blueViewWidthConstraint = NSLayoutConstraint(item: blueView, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 100)
         
    let blueViewHeightConstraint = NSLayoutConstraint(item: blueView, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 100)
         
    blueViewTopConstraint.isActive = true
    blueViewLeadingConstraint.isActive = true
    blueViewWidthConstraint.isActive = true
    blueViewHeightConstraint.isActive = true
         
    let greenView = UIView()
    greenView.translatesAutoresizingMaskIntoConstraints = false
    greenView.backgroundColor = .green
    self.view.addSubview(greenView)
         
    let greenViewTopConstraint = NSLayoutConstraint(item: greenView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: blueView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0)
         
    let greenViewLeadingConstraint = NSLayoutConstraint(item: greenView, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: blueView, attribute: NSLayoutConstraint.Attribute.right, multiplier: 1, constant: 20)
         
    let greenViewWidthConstraint = NSLayoutConstraint(item: greenView, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 100)
         
    let greenViewHeightConstraint = NSLayoutConstraint(item: greenView, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 100)
         
    greenViewTopConstraint.isActive = true
    greenViewLeadingConstraint.isActive = true
    greenViewWidthConstraint.isActive = true
    greenViewHeightConstraint.isActive = true
         
    let button = UIButton(frame: CGRect(x: 100, y: 400, width: 200, height: 50))
    button.setTitle("刪除藍(lán)色視圖", for: UIControl.State.normal)
    button.backgroundColor = .red
    button.setTitleColor(.black, for: UIControl.State.highlighted)
    self.view.addSubview(button)
    button.addTarget(self, action: #selector(test3Action), for: UIControl.Event.touchUpInside)
    
    • 假如這個(gè)時(shí)候我想點(diǎn)擊按鈕刪除藍(lán)色View,那么在點(diǎn)擊事件里應(yīng)該怎么做聪舒?常規(guī)做法是:首先將藍(lán)色View移除辨液,然后重寫(xiě)綠色View的約束。但是如果利用約束優(yōu)先級(jí)的屬性箱残,就只需要將藍(lán)的View移除就可以了

    • 說(shuō)到優(yōu)先級(jí)咱們先說(shuō)一下約束沖突滔迈,比如你給一個(gè)View添加了一個(gè)寬度為100的約束,然后又添加了一個(gè)寬度為200的約束被辑,這個(gè)時(shí)候系統(tǒng)就不知道以哪個(gè)為準(zhǔn)了燎悍,然后就會(huì)報(bào)一個(gè)咱們常見(jiàn)的錯(cuò)誤

      Constraint[41416:11124704] [LayoutConstraints] Unable to simultaneously satisfy constraints.
       Probably at least one of the constraints in the following list is one you don't want. 
       Try this: 
           (1) look at each constraint and try to figure out which you don't expect; 
           (2) find the code that added the unwanted constraint or constraints and fix it. 
      

      但是每個(gè)約束都是支持設(shè)置優(yōu)先級(jí)的,文檔如下盼理,優(yōu)先級(jí)是1-1000谈山,默認(rèn)值是1000

       /* If a constraint's priority level is less than required, then it is optional.  Higher priority constraints are met before lower priority constraints.
      Constraint satisfaction is not all or nothing.  If a constraint 'a == b' is optional, that means we will attempt to minimize 'abs(a-b)'.
      This property may only be modified as part of initial set up or when optional.  After a constraint has been added to a view, an exception will be thrown if the priority is changed from/to NSLayoutPriorityRequired.
      */
        open var priority: UILayoutPriority
      

      假如你第一個(gè)添加的寬度為100的約束時(shí)高優(yōu)先級(jí),第二個(gè)寬度為200的約束時(shí)低優(yōu)先級(jí)宏怔,這時(shí)系統(tǒng)發(fā)現(xiàn)兩個(gè)約束有沖突時(shí)勾哩,就會(huì)優(yōu)先以高優(yōu)先級(jí)的約束為準(zhǔn),當(dāng)高優(yōu)先級(jí)的約束被移除后举哟,低優(yōu)先級(jí)的約束會(huì)生效

    • 基于這種特性思劳,咱們可以給綠色View的left添加兩個(gè)約束,第一個(gè)約束時(shí)距離藍(lán)色View右邊20妨猩,優(yōu)先級(jí)是1000,第二個(gè)約束時(shí)距離黃色View右邊20潜叛,優(yōu)先級(jí)是750,這時(shí)綠色View優(yōu)先是在藍(lán)色View后面,當(dāng)藍(lán)色View被移除時(shí)威兜,備用約束生效销斟,綠色View會(huì)自動(dòng)跟在黃色View后面。在之前代碼基礎(chǔ)上添加備用約束

      let greenViewTopConstraint2 = NSLayoutConstraint(item: greenView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: yellowView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0)
      greenViewTopConstraint2.priority = UILayoutPriority(rawValue: 750)
      
      let greenViewLeadingConstraint2 = NSLayoutConstraint(item: greenView, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: yellowView, attribute: NSLayoutConstraint.Attribute.right, multiplier: 1, constant: 20)
      greenViewLeadingConstraint2.priority = UILayoutPriority(rawValue: 750)
      
      greenViewTopConstraint2.isActive = true
      greenViewLeadingConstraint2.isActive = true 
      
       @objc func test3Action() {
           let blueView = self.view.viewWithTag(666)
           blueView?.removeFromSuperview()
       }
      

      如下面GIF點(diǎn)擊按鈕椒舵,移除藍(lán)色View后蚂踊,綠色View會(huì)自動(dòng)跟上

刪除一個(gè)View.gif
  1. 假如cell上橫向布局兩個(gè)label,一個(gè)是標(biāo)題笔宿,一個(gè)是內(nèi)容犁钟,不能換行,如果文案過(guò)長(zhǎng)泼橘,導(dǎo)致不能將標(biāo)題和內(nèi)容全部顯示全的情況下涝动,怎么保證標(biāo)題顯示全,或者內(nèi)容顯示全炬灭,如圖


    兩個(gè)Label并列.png

    普通布局的代碼如下

    let titleLabel = UILabel()
    titleLabel.translatesAutoresizingMaskIntoConstraints = false
    titleLabel.backgroundColor = .red
    titleLabel.text = "我是左邊標(biāo)題醋粟,我很長(zhǎng)很長(zhǎng),我顯示全了"
    self.view.addSubview(titleLabel)
    
    let topConstraint = NSLayoutConstraint(item: titleLabel, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 150)
    
    let leadingConstraint = NSLayoutConstraint(item: titleLabel, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.left, multiplier: 1, constant: 10)
    
    topConstraint.isActive = true
    leadingConstraint.isActive = true
    
    let contentLabel = UILabel()
    contentLabel.translatesAutoresizingMaskIntoConstraints = false
    contentLabel.backgroundColor = .red
    contentLabel.text = "右邊的內(nèi)容重归,我顯示全了"
    self.view.addSubview(contentLabel)
    
    let contentTopConstraint = NSLayoutConstraint(item: contentLabel, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 150)
    
    let contentLeadingConstraint = NSLayoutConstraint(item: contentLabel, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: titleLabel, attribute: NSLayoutConstraint.Attribute.right, multiplier: 1, constant: 10)
    
    let contenttrailingConstraint = NSLayoutConstraint(item: contentLabel, attribute: NSLayoutConstraint.Attribute.right, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.right, multiplier: 1, constant: -10)
    
    
    
    contentTopConstraint.isActive = true
    contentLeadingConstraint.isActive = true
    contenttrailingConstraint.isActive = true
    
    

咱們知道米愿,在不設(shè)置label的寬高只設(shè)置原點(diǎn)的情況下,label也會(huì)把自己撐起來(lái)顯示出文字鼻吮,但是UIView就不行吗货。原因在于Lable自帶一個(gè)把自己撐起來(lái)的屬性。當(dāng)兩個(gè)Label遇到一起狈网,文案都很長(zhǎng),如上圖所示笨腥,那哪個(gè)顯示全拓哺,是不確定的,可能和添加順序有關(guān)等等脖母,怎么才能按照咱們的想法來(lái)呢士鸥,答案還是 優(yōu)先級(jí)
Label自帶把自己撐起來(lái)的屬性,這個(gè)撐的能力是可以設(shè)置強(qiáng)弱的谆级,這就需要熟悉下面兩個(gè)方法

open func contentHuggingPriority(for axis: NSLayoutConstraint.Axis) -> UILayoutPriority
open func setContentCompressionResistancePriority(_ priority: UILayoutPriority, for axis: NSLayoutConstraint.Axis)

第一個(gè)方法contentHuggingPriority,這個(gè)可以設(shè)置Lable的拒絕變?yōu)榇笥谄涔逃写笮〉膬?yōu)先級(jí)烤礁。
第二個(gè)方法setContentCompressionResistancePriority這個(gè)可以設(shè)置Label拒絕小于其固有大小的優(yōu)先級(jí)。
他們默認(rèn)的優(yōu)先級(jí)是UILayoutPriorityDefaultLow 或者 UILayoutPriorityDefaultHigh肥照,反正不是最高脚仔,有了這個(gè)屬性,那就可以隨意操作舆绎,想讓哪個(gè)Label顯示全鲤脏,就讓哪個(gè)顯示全

假如咱們想讓contentLabel顯示全,則加上

contentLabel.setContentCompressionResistancePriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.horizontal)

結(jié)果肯定是


content顯示全了.png

假如咱們想讓titleLabel顯示全,則加上

titleLabel.setContentCompressionResistancePriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.horizontal)

結(jié)果就是
title顯示全了.png

left right 和 leading trailing有啥區(qū)別

  1. 全世界所有的人對(duì)于左和右都是統(tǒng)一的猎醇,也就是說(shuō)窥突,假如讓全世界的人舉起左手,那么全世界的人舉起的都是同一根胳膊硫嘶,咱們大部分國(guó)家的閱讀習(xí)慣也是從做到右阻问,所以咱們?cè)O(shè)計(jì)的UI,一般都是從左向右布局沦疾,寧愿右邊空著称近,也不會(huì)左邊空著,例如:
    習(xí)慣從左向又閱讀.jpg
  1. 但是有特殊情況的國(guó)家,比如阿拉伯曹鸠,假如你的APP不僅給中國(guó)人使用煌茬,想給全球人使用,包含阿拉伯等從右往左閱讀習(xí)慣的人彻桃,那么坛善,當(dāng)你用left布局時(shí),就違背了阿拉伯人的閱讀習(xí)慣邻眷,就好比微信布局從右往左眠屎,會(huì)讓人別扭。但是使用leading肆饶、trailing就不會(huì)出現(xiàn)改衩,leading、trailing代表前后驯镊,可以理解為閱讀的順序葫督,系統(tǒng)會(huì)根據(jù)當(dāng)前手機(jī)的語(yǔ)言,將leading板惑、trailing轉(zhuǎn)換成left或者right橄镜。用中文,系統(tǒng)就把leading轉(zhuǎn)換成left,用阿拉伯語(yǔ)就把leading轉(zhuǎn)換成Right
Simulator Screen Shot - iPhone 12 - 2021-06-01 at 23.55.32.png
  1. 所以國(guó)際化的APP,用自動(dòng)布局的時(shí)候冯乘,最好用leading洽胶、trailing代替left right

回到SnapKit

  1. 前面說(shuō)了那么多NSLayoutConstraint,是因?yàn)镾napKit是基于NSLayoutConstraint封裝的裆馒,想了解自動(dòng)布局姊氓,必須得熟悉NSLayoutConstraint

  2. 為啥我用SnapKit時(shí)沒(méi)寫(xiě)過(guò)translatesAutoresizingMaskIntoConstraints = false
    因?yàn)橛肧napKit添加約束的時(shí)候,SnapKit會(huì)自動(dòng)給咱們?cè)O(shè)置,源碼如下

    internal func prepare() {
         if let view = self as? ConstraintView {
             view.translatesAutoresizingMaskIntoConstraints = false
         }
     }
    
  1. 用SnapKit添加約束時(shí)用makeConstraints喷好,那updateConstraints怎么用
    • 從源碼可以看出翔横,如果之前沒(méi)有添加過(guò)約束,也就是item.constraints.count <= 0, 那實(shí)際調(diào)用的就是makeConstraints梗搅,所以給視圖第一次添加約束時(shí)棕孙,makeConstraints和updateConstraints是一樣的
    internal static func updateConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
            guard item.constraints.count > 0 else {
                self.makeConstraints(item: item, closure: closure)
                return
         }
         
         let constraints = prepareConstraints(item: item, closure: closure)
         for constraint in constraints {
             constraint.activateIfNeeded(updatingExisting: true)
         }
     }
    
    • 如果之前已經(jīng)添加過(guò)約束了,那啥時(shí)候用updateConstraints?蟀俊,咱們?cè)倩氐絅SLayoutConstraint官方API上說(shuō)的钦铺,也就是說(shuō)只有偏移量是可以更新的

      /* Unlike the other properties, the constant may be modified after constraint creation.  Setting the constant on an existing constraint performs much better than removing the constraint and adding a new one that's just like the old but for having a new constant.
      */
       open var constant: CGFloat
      

      也就是說(shuō)下面的代碼是沒(méi)問(wèn)題的,因?yàn)橹桓铝似屏?/p>

       //這樣更新時(shí)沒(méi)問(wèn)題的
       blueView.snp.makeConstraints { (make) in
             make.leading.equalTo(100)
             make.top.equalTo(200)
        }
      
       blueView.snp.updateConstraints { (make) in
             make.leading.equalTo(200)
         }
      

      但是假如我更新的不是偏移量肢预,是view1.attr1 = view2.attr2 * multiplier + constan里面的view或者attr或者multiplier都會(huì)報(bào)錯(cuò)矛洞,如下代碼

       //這樣會(huì)報(bào)錯(cuò)
      blueView.snp.makeConstraints { (make) in
           make.leading.equalTo(self.view.snp.leading)
           make.top.equalTo(200)
       }
      
      blueView.snp.updateConstraints { (make) in
           make.leading.equalTo(self.view.snp.trailing)
      }
      

      就會(huì)出現(xiàn)下面錯(cuò)誤

image.png
  • SnapKit會(huì)判斷你調(diào)用updateConstraints時(shí),更新的是不是偏移量constant烫映,如果不是就會(huì)主動(dòng)報(bào)錯(cuò)沼本,那他是怎么判斷的呢
    //首先判斷你調(diào)用updateConstraints時(shí)判斷更新的約束之前存不存在,如果不存在就報(bào)錯(cuò)锭沟,存在就更新成新的值抽兆,存不存在通過(guò)(updateLayoutConstraint = existingLayoutConstraint)判斷
    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)")
                 }
    
    
    //怎么判斷存不存在,判斷的條件就是
     internal func ==(lhs: LayoutConstraint, rhs: LayoutConstraint) -> Bool {
     // If firstItem or secondItem on either constraint has a dangling pointer
     // this comparison can cause a crash. The solution for this is to ensure
     // your layout code hold strong references to things like Views, LayoutGuides
     // and LayoutAnchors as SnapKit will not keep strong references to any of these.
     guard lhs.firstAttribute == rhs.firstAttribute &&
           lhs.secondAttribute == rhs.secondAttribute &&
           lhs.relation == rhs.relation &&
           lhs.priority == rhs.priority &&
           lhs.multiplier == rhs.multiplier &&
           lhs.secondItem === rhs.secondItem &&
           lhs.firstItem === rhs.firstItem else {
         return false
     }
     return true
    }
    
    所以在已添加約束后調(diào)用updateConstraints時(shí)族淮,里面寫(xiě)的約束要保證之前已經(jīng)添加過(guò)對(duì)應(yīng)的只有偏移量不一樣的約束辫红,否則就會(huì)報(bào)錯(cuò)\
  1. 如果我多次調(diào)用makeConstraints,添加約束會(huì)怎樣

    • makeConstraints是不會(huì)檢查之前是否已存在相同的約束祝辣,只要調(diào)用就會(huì)添加贴妻,所以如果添加了多次相同的約束,那么都會(huì)被添加上蝙斜,只是因?yàn)榧s束一樣不會(huì)報(bào)約束沖突
    • 但是如果多次添加的約束不一樣就會(huì)有約束沖突名惩。但是約束沖突不會(huì)崩潰,只是可能出現(xiàn)布局錯(cuò)亂和你想要的結(jié)果不一樣
  2. 如果我多次調(diào)用makeConstraints添加相同的約束孕荠,然后再用updateConstraints更新約束娩鹉,會(huì)將之前存在的相同約束都更新嗎?
    答案是不會(huì)稚伍,只會(huì)更新一個(gè)已存在的約束弯予,因?yàn)樵创a是如下這樣寫(xiě)的, layoutConstraints是updateConstraints方法添加的約束,existingLayoutConstraints是之前makeConstraints的所有約束槐瑞,外面的循環(huán)是layoutConstraints,所以只要從已存在的約束里找到一個(gè)相同的阁苞,就會(huì)進(jìn)入下一次循環(huán)更新下一個(gè)約束了困檩,不會(huì)繼續(xù)從已存在的約束里據(jù)需找

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

    所以當(dāng)報(bào)約束沖突時(shí),有可能確實(shí)是寫(xiě)的約束有問(wèn)題那槽,也有可能是多次添加了不一樣的約束悼沿,也有可能是多次添加了一樣的約束,但只更新了一個(gè)約束

  3. lessThanOrEqualTo 骚灸、greaterThanOrEqualTo
    平時(shí)用的比較多的是equalTo糟趾,但是lessThanOrEqualTo、greaterThanOrEqualTo也是很有用的,再以前面剛說(shuō)過(guò)的title和content兩個(gè)Label舉例义郑,假如要求title最寬是200蝶柿,當(dāng)文字寬度小于200時(shí)把余下的空間給content,那就可以寫(xiě)成類(lèi)似如下

    titleLabel.snp.makeConstraints { (maker) in
       maker.leading.equalToSuperview()
       maker.top.equalToSuperview()
       maker.width.lessThanOrEqualTo(200)
     }
    
    contentLabel.snp.makeConstraints { (maker) in
        maker.top.equalTo(titleLabel)
        maker.leading.equalTo(titleLabel.snp.trailing)
     }
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市非驮,隨后出現(xiàn)的幾起案子交汤,更是在濱河造成了極大的恐慌,老刑警劉巖劫笙,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芙扎,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡填大,警方通過(guò)查閱死者的電腦和手機(jī)戒洼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)允华,“玉大人圈浇,你說(shuō)我怎么就攤上這事±瘢” “怎么了汉额?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)榨汤。 經(jīng)常有香客問(wèn)我蠕搜,道長(zhǎng),這世上最難降的妖魔是什么收壕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任妓灌,我火速辦了婚禮,結(jié)果婚禮上蜜宪,老公的妹妹穿的比我還像新娘虫埂。我一直安慰自己,他們只是感情好圃验,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布掉伏。 她就那樣靜靜地躺著,像睡著了一般澳窑。 火紅的嫁衣襯著肌膚如雪斧散。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天摊聋,我揣著相機(jī)與錄音鸡捐,去河邊找鬼。 笑死麻裁,一個(gè)胖子當(dāng)著我的面吹牛箍镜,可吹牛的內(nèi)容都是我干的源祈。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼色迂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼香缺!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起脚草,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赫悄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后馏慨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體埂淮,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年写隶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了倔撞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡慕趴,死狀恐怖痪蝇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情冕房,我是刑警寧澤躏啰,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站耙册,受9級(jí)特大地震影響给僵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜详拙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一帝际、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧饶辙,春花似錦蹲诀、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至矿微,卻和暖如春痕慢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冷冗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工守屉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惑艇,地道東北人蒿辙。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓拇泛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親思灌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子俺叭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容