Swift自動(dòng)布局SnapKit的詳細(xì)使用介紹

簡介

SnapKit,一個(gè)經(jīng)典的Swift版的第三方庫,專門用于項(xiàng)目的自動(dòng)布局,目前在github上的stars就高達(dá)9340顆星,這是一個(gè)不小的數(shù)字,亦足以證明它存在的非凡意義和作用。作者認(rèn)為,在iOS開發(fā)(swift)中,它是用于項(xiàng)目最優(yōu)秀的自動(dòng)布局的必選庫之一。它的作者仍然是寫Objective-C的第三方庫Masonry的大牛 - @Robert Payne,開門見山,本文將詳細(xì)介紹介紹SnapKit的詳細(xì)使用和安裝,相信對(duì)于初入門該庫的開發(fā)者或許會(huì)有一定的幫助,當(dāng)然,鑒于作者能力有限,如有不足之處,歡迎指點(diǎn)和批評(píng)。

Snapkit的安裝(CocoaPods)

環(huán)境配置要求:

  • iOS 8.0 / Mac OS X 10.11+
  • Xcode 8.0+
  • Swift 3.0+

安裝

在已經(jīng)安裝CocoaPods的前提下, 即可以進(jìn)行下列步驟。

  • 在你的項(xiàng)目工程里的Podfile文件里面添加
source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '10.0'

use_frameworks!

target '這里是你的工程名稱' do
    pod 'SnapKit', '~> 3.0'
end
  • 老生常談,運(yùn)行CocoaPods的如下命令
pod install

到此,不出意外的話,你已經(jīng)將SnapKit集成到你的項(xiàng)目中了。然后,就開始講怎么使用它了。

Snapkit的布局使用

1、 實(shí)現(xiàn)一個(gè)寬高為100,居于當(dāng)前視圖的中心的視圖布局,示例代碼如下

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let testView = UIView()
        testView.backgroundColor = UIColor.cyan
        view.addSubview(testView)
        testView.snp.makeConstraints { (make) in
            make.width.equalTo(100)         // 寬為100
            make.height.equalTo(100)        // 高為100
            make.center.equalTo(view)       // 位于當(dāng)前視圖的中心
        }

    }
}

更簡潔的寫法可以

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let testView = UIView()
        testView.backgroundColor = UIColor.cyan
        view.addSubview(testView)
        testView.snp.makeConstraints { (make) in
            make.width.height.equalTo(100)    // 鏈?zhǔn)秸Z法直接定義寬高
            make.center.equalToSuperview()    // 直接在父視圖居中
        }

    }
}

效果圖

1.png

2、View2位于View1內(nèi), view2位于View1的中心, 并且距離View的邊距的距離都為20

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

         // 黑色視圖作為父視圖
         let view1 = UIView()
         view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
         view1.center = view.center
         view1.backgroundColor = UIColor.black
         view.addSubview(view1)

         // 測試視圖
         let view2 = UIView()
         view2.backgroundColor = UIColor.magenta
         view1.addSubview(view2)
         view2.snp.makeConstraints { (make) in
               make.top.equalToSuperview().offset(20)      // 當(dāng)前視圖的頂部距離父視圖的頂部:20(父視圖頂部+20)
               make.left.equalToSuperview().offset(20)     // 當(dāng)前視圖的左邊距離父視圖的左邊:20(父視圖左邊+20)
              make.bottom.equalToSuperview().offset(-20)  // 當(dāng)前視圖的底部距離父視圖的底部:-20(父視圖底部-20)
              make.right.equalToSuperview().offset(-20)   // 當(dāng)前視圖的右邊距離父視圖的右邊:-20(父視圖右邊-20)
         }

    }
}

更簡潔的寫法

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

         // 黑色視圖作為父視圖
         let view1 = UIView()
         view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
         view1.center = view.center
         view1.backgroundColor = UIColor.black
         view.addSubview(view1)

         // 測試視圖
         let view2 = UIView()
         view2.backgroundColor = UIColor.magenta
         view1.addSubview(view2)
         view2.snp.makeConstraints { (make) in
            make.edges.equalToSuperview().inset(UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20))
         }

    }
}

效果圖

2.png

3、布局一個(gè)視圖view2, 讓它的水平中心線小于等于另一個(gè)視圖view2的左邊,可以這樣布局

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

         // 黑色視圖作為父視圖
         let view1 = UIView()
         view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
         view1.center = view.center
         view1.backgroundColor = UIColor.black
         view.addSubview(view1)

         // 測試視圖
         let view2 = UIView()
         view2.backgroundColor = UIColor.magenta
         view1.addSubview(view2)
         view2.snp.makeConstraints { (make) in
            // 讓頂部距離view1的底部為10的距離
            make.top.equalTo(view1.snp.bottom).offset(10)
            // 設(shè)置寬、高
            make.width.height.equalTo(100)
            // 水平中心線<=view1的左邊
            make.centerX.lessThanOrEqualTo(view1.snp.leading)
         }

    }
}

效果圖

3.png

視圖的屬性說明

通過上面的大致簡單布局我們對(duì)SnapKit有了一個(gè)基本的了解,那么, 它的布局屬性是怎么來的呢?和原生的布局類有什么關(guān)聯(lián)? 下面看一個(gè)SnapKit的布局屬性表

4.png

從表中,我們知道,Snapkit的布局屬性都是源自于系統(tǒng)的NSLayoutAttribute,那么,NSLayoutAttribute是個(gè)什么呢?其實(shí),它在swift中是一個(gè)枚舉,內(nèi)部列舉了很多布局屬性諸如top、left、leading、centerX等,Snapkit的布局屬性與它們都存在一一的對(duì)應(yīng)關(guān)系。

Snapkit 的 greaterThanOrEqualTo 屬性

如果想讓視圖View2的左邊>=父視圖View1的左邊, 這時(shí)我們就可以用到greaterThanOrEqualTo

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

         // 黑色視圖作為父視圖
         let view1 = UIView()
         view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
         view1.center = view.center
         view1.backgroundColor = UIColor.black
         view.addSubview(view1)

         // 測試視圖
         let view2 = UIView()
         view2.backgroundColor = UIColor.magenta
         view1.addSubview(view2)
         view2.snp.makeConstraints { (make) in
            // 讓頂部距離view1的底部為10的距離
            make.top.equalTo(view1.snp.bottom).offset(10)
            // 設(shè)置寬、高
            make.width.height.equalTo(100)
            // 水平中心線<=view1的左邊
            make.left.greaterThanOrEqualTo(view1)

            // 或者, 和上面一行代碼一樣的效果
//            make.left.greaterThanOrEqualTo(view1.snp.left)
         }
    }
}

效果圖

5.png

其實(shí),greaterThanOrEqualTo這個(gè)屬性有點(diǎn)多余,比如上面這行代碼 make.left.greaterThanOrEqualTo(view1) , 我們可以換成 make.left.equalToSuperview()或make.left.equalTo(view1.snp.left), 效果是一樣的,也就是說,一般情況下 >= 或 <= 我們都可以直接用equalTo來代替!

SnapKit的greaterThanOrEqualTo和lessThanOrEqualTo聯(lián)合使用

當(dāng)我們想要讓某個(gè)視圖的width或height大于等于某個(gè)特定的值,小于等于某個(gè)特定的值的時(shí)候,一般而言,Snapkit會(huì)以greaterThanOrEqualTo為準(zhǔn),這里舉一個(gè)width的例子,為了方便,這里指貼出了viewDidLoad中的代碼(其他沒必要)

 // 黑色視圖作為父視圖
 let view1 = UIView()
 view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
 view1.center = view.center
 view1.backgroundColor = UIColor.black
 view.addSubview(view1)

 // 測試視圖
 let view2 = UIView()
 view2.backgroundColor = UIColor.magenta
 view1.addSubview(view2)
 view2.snp.makeConstraints { (make) in
    make.width.lessThanOrEqualTo(300)
    make.width.greaterThanOrEqualTo(200)
    make.height.equalTo(100)
    make.center.equalToSuperview()
 }

接著,我們來看一下效果圖

6.png

很明顯,最后的寬度是以make.width.greaterThanOrEqualTo(200)為標(biāo)準(zhǔn)的,也可以這樣的,在同時(shí)使用兩者的情況下,greaterThanOrEqualTo的優(yōu)先級(jí)略比lessThanOrEqualTo的優(yōu)先級(jí)高。值得一提的是, 在上面的例子中,如果我們只設(shè)置make.width.lessThanOrEqualTo(300),那么view2是不會(huì)顯示出來的,因?yàn)関iew2不知道你要表達(dá)的是要顯示多少,小于等于300,到底是100還是200呢?(這里指針對(duì)width和height)所以它不能確定這個(gè)約束的值,但是,如果我們單獨(dú)設(shè)置make.width.greaterThanOrEqualTo(200),那么就和上面的效果一樣,因?yàn)樗鼤?huì)以200為標(biāo)準(zhǔn)布局約束!

lessThanOrEqualTo的用于上、下、左、右

如果我們想要視圖view2的左邊 <= view1.left + 10, 那么就可以直接用到lessThanOrEqualTo布局了,看下面這個(gè)例子

 // 黑色視圖作為父視圖
 let view1 = UIView()
 view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
 view1.center = view.center
 view1.backgroundColor = UIColor.black
 view.addSubview(view1)

 // 測試視圖
 let view2 = UIView()
 view2.backgroundColor = UIColor.magenta
 view1.addSubview(view2)
 view2.snp.makeConstraints { (make) in
    make.left.lessThanOrEqualTo(20)        // <= 父視圖的左邊+20
    make.right.equalTo(-40)                // = 父視圖的右邊-40
    make.height.equalTo(100)
    make.center.equalToSuperview()
 }

效果圖

7.png

Snapkit布局的靈活性

  • Snapkit布局靈活性很強(qiáng), 我們看下面的例子, 他們的效果是一樣的
make.left.equalToSuperview().offset(10)
make.left.equalTo(10)
make.left.equalTo(view1.snp.left).offset(10)
  • 設(shè)置視圖的大小(width,height),他們效果是一樣的
make.width.height.equalTo(100)
或
make.width.equalTo(100)
make.height.equalTo(100)
或
make.size.equalTo(CGSize(width: 100, height: 100))
  • 優(yōu)先級(jí)(priority)

我們來看一下Snapkit的優(yōu)先級(jí)設(shè)置, 優(yōu)先級(jí)都是附加在約束鏈的末尾處,看下面的使用方法

// 黑色視圖作為父視圖
let view1 = UIView()
view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
view1.center = view.center
view1.backgroundColor = UIColor.black
view.addSubview(view1)

// 測試視圖
let view2 = UIView()
view2.backgroundColor = UIColor.magenta
view1.addSubview(view2)
view2.snp.makeConstraints { (make) in
    make.width.equalTo(100).priority(666)
    make.width.equalTo(250).priority(999)
    make.height.equalTo(111)
    make.center.equalToSuperview()
}

效果圖

8.png

從上面我們可以知道, 我們?cè)O(shè)置了兩個(gè)優(yōu)先級(jí):make.width.equalTo(100).priority(666) 和 make.width.equalTo(250).priority(999), 那運(yùn)行結(jié)果是一個(gè)哪個(gè)為準(zhǔn)呢?顯然是以優(yōu)先級(jí)為 999的為準(zhǔn),因?yàn)?priority(999)>priotity(666), 所以在使用Snapkit的過程中,有時(shí)我們可以使用優(yōu)先級(jí)priority來設(shè)置我們的約束, 另外,值得一提的是,SnapKit的優(yōu)先級(jí)最大值只能是 1000, 如果優(yōu)先級(jí)的數(shù)值超過1000,則運(yùn)行時(shí)就會(huì)Crash!這里要尤其注意。

  • 更新約束(引用約束)

我們可以通過保存某一個(gè)約束布局來更新相應(yīng)的約束,或者保存一組約束布局到一個(gè)數(shù)組中更新約束, 具體看下面代碼

// 保存約束(引用約束)
var updateConstraint: Constraint?

override func viewDidLoad() {
    super.viewDidLoad()

    // 黑色視圖作為父視圖
    let view1 = UIView()
    view1.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
    view1.center = view.center
    view1.backgroundColor = UIColor.black
    view.addSubview(view1)

    // 測試視圖
    let view2 = UIView()
    view2.backgroundColor = UIColor.magenta
    view1.addSubview(view2)
    view2.snp.makeConstraints { (make) in
        make.width.height.equalTo(100)  // 寬高為100
        self.updateConstraint = make.top.left.equalTo(10).constraint   // 距離父視圖上、左為10
    }

    let updateButton = UIButton(type: .custom)
    updateButton.backgroundColor = UIColor.brown
    updateButton.frame = CGRect(x: 100, y: 80, width: 50, height: 30)
    updateButton.setTitle("更新", for: .normal)
    updateButton.addTarget(self, action: #selector(updateConstraintMethod), for: .touchUpInside)
    view.addSubview(updateButton)
}

// 更新約束
func updateConstraintMethod() {
    self.updateConstraint?.update(offset: 50)   // 更新距離父視圖上、左為50
}
9
  • 更新約束(snp.updateConstraints)

說起這個(gè)updateConstraints, 我也懵逼過,那么它到底有何作用呢?又怎么用呢?它和一開始就使用的makeConstraints又有什么明確的區(qū)別呢?請(qǐng)繼續(xù)往下看

  • 說明1:如果你這是更新某個(gè)約束或某幾個(gè)約束的常量值,你就可以使用updateConstraints而不是makeConstraints

  • 說明2:這個(gè)也是蘋果推薦用來添加或更新約束的方式

  • 說明3:這個(gè)方法可以調(diào)用多次,會(huì)相應(yīng)setNeedsUpdateConstraints, 在控制器中,可以寫在override func updateViewConstraints()方法里面(當(dāng)然也可以寫在你想要什么時(shí)候更新的點(diǎn)擊事件里面)

import UIKit
import SnapKit

class ViewController: UIViewController {

    lazy var blackView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()

        blackView.backgroundColor = UIColor.black
        view.addSubview(blackView)
        blackView.snp.makeConstraints { (make) in

            // 四個(gè)約束確定位置和大小
            make.width.equalTo(100)
            make.height.equalTo(150)
            make.top.equalTo(10)
            make.centerX.equalToSuperview()
        }

    }

    override func updateViewConstraints() {

        blackView.snp.updateConstraints { (make) in
            // 更新距離父視圖頂部的約束(從 10 ---> 300 )
            make.top.equalTo(300)
        }

        // 根據(jù)蘋果,調(diào)用父類應(yīng)該放在末尾
        super.updateViewConstraints()
    }
}

注意:從上面的代碼中我們很明確地知道, blackView通過width、height、top、centerX確定了它本身的大小和位置, 但是, 在run出來之后,它的top改變了距離, 從 10變成了 300,其他三個(gè)約束保持不變, 見下圖效果:

10.png

顯而易見, 除了top約束, 其他都沒有改變! 也就是說,約束被更新(相當(dāng)于系統(tǒng)升級(jí)一樣,是一個(gè)道理)

現(xiàn)在,我們通過UIButton的點(diǎn)擊事件來證明一下制作約束makeConstraintsupdateConstraints具體的區(qū)別在哪里?

lazy var blackView = UIView()

override func viewDidLoad() {
    super.viewDidLoad()

    blackView.backgroundColor = UIColor.black
    view.addSubview(blackView)
    blackView.snp.makeConstraints { (make) in

        make.width.equalTo(100)
        make.height.equalTo(150)
        make.top.equalTo(50)
        make.centerX.equalToSuperview()
    }

    let btn = UIButton(type: .custom)
    btn.backgroundColor = UIColor.brown
    btn.frame = CGRect(x: 100, y: 200, width: 60, height: 30)
    btn.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
    view.addSubview(btn)

}

// 點(diǎn)擊更新/制作約束
func buttonAction() {

    blackView.snp.makeConstraints { (make) in
        make.width.height.equalTo(20)
        make.top.equalTo(300)
    }

}

先看效果圖

點(diǎn)擊事件發(fā)生前(圖1):

![Uploading 12_315832.png . . .]

點(diǎn)擊事件發(fā)生后(圖2)

12.png

圖3

13.png

圖4

14.png

上面,我們知道,視圖 blackView一開始是由四個(gè)約束確定位置和大小,在點(diǎn)擊事情響應(yīng)后,我們又給 blackView 制作(記住,是制作,而不是重做,兩者有明確的區(qū)別)了3個(gè)約束,分別是 width、height、top, 那么這樣做問題出現(xiàn)在哪里呢? 第一, 點(diǎn)擊事情發(fā)生前(圖1), 在點(diǎn)擊事件發(fā)生后(見圖2), 我們發(fā)現(xiàn),blackViewwidth、height約束改變了,但是 top卻沒有改變,還是原來的距離父視圖頂部 50 的距離, 原因在于,我們?cè)谠瓉淼募s束基礎(chǔ)上又添加了多余的約束, 也就是說,約束從4個(gè)變成了7個(gè)(見圖3左邊constraints), 這樣就產(chǎn)生了約束不明確,進(jìn)而導(dǎo)致snapkit的警告(見圖4), 這樣布局顯然是不可取的,在項(xiàng)目中這樣做極其危險(xiǎn),甚至可能會(huì)導(dǎo)致異常奔潰!!!!
現(xiàn)在, 我們?cè)搶Ⅻc(diǎn)擊事件中的約束布局從makeConstraints改變成updateConstraints來試試兩者有什么區(qū)別(下面只添加了點(diǎn)擊事件的代碼,其他事重復(fù)的就不多此一舉了)

func buttonAction() {
    // 注意這里是updateConstraints, 而不是makeConstraints
    blackView.snp.updateConstraints { (make) in
        make.width.height.equalTo(20)
        make.top.equalTo(300)
    }
    print("這里試試snapkit有沒有報(bào)警告")
}

接著看點(diǎn)擊事件后的效果圖

圖5

15.png

圖6

16.png

圖7

17.png

發(fā)現(xiàn)沒有,在將makeConstraints改變成updateConstraints之后,約束還是4個(gè),snapkit沒有報(bào)警告,點(diǎn)擊事件中的width、height、top全部起了作用,而這就是兩者的本質(zhì)區(qū)別:makeConstraints是制作約束,在原來的基礎(chǔ)上再添加另外的約束,也就是畫蛇添足,約束增加,視圖布局就有不確定性,從而有些約束起作用,有些不起作用(如上面的top),snapkit報(bào)警告!!!而updateConstraints是更新約束,改變?cè)屑s束,約束不會(huì)增加,沒經(jīng)過updateConstraints處理的保持原有約束,經(jīng)過處理就更新約束,約束不會(huì)減少,snapkit不會(huì)產(chǎn)生警告,這是正常標(biāo)準(zhǔn)的更新約束的正確方式!!!

  • 重做約束(remakeConstraints)
    重做約束的本質(zhì)就是:去掉已有的所有約束, 重新做約束,記住,是做約束, 也就是說, 使用了remakeConstraints后,重做的約束必須要能確定相應(yīng)視圖的大小和位置, 之前makeConstraints的約束已經(jīng)不會(huì)存在了,完全銷毀!!!
// 點(diǎn)擊更新/制作約束
func buttonAction() {

    // 注意這里是 remakeConstraints
    blackView.snp.remakeConstraints { (make) in
        make.width.height.equalTo(20)
        make.top.equalTo(300)
    }

    print("這里試試snapkit有沒有報(bào)警告")
}

效果圖

圖(1)

18.png

效果圖

圖(2)

19.png

圖(3)

20.png

我們看到, blackView重做了約束, 之前的約束不起任何作用,由于它在重做約束后只有 3 個(gè)約束分別是 width、height、top, 但是這里有一個(gè)問題,就是這 3 個(gè)約束只能確定大小,無法確定視圖的位置, 所以在水平方向上或者左右缺少一個(gè)布局條件, 故 blackView整體視圖的x緊靠左邊(默認(rèn))! 另外我們發(fā)現(xiàn), 在圖(3)中,右上角出現(xiàn)了一個(gè)感嘆號(hào)“!”, 那是因?yàn)楦嬖V你缺少了一個(gè)約束條件:x-xcode-debug-views://7f81fcbc7900: runtime: Layout Issues: Horizontal position is ambiguous for UIView.

小結(jié)

通過以上學(xué)習(xí),我們或深或淺地學(xué)習(xí)了布局三方庫SnapKit的使用, 我相信,只要將上述布局會(huì)使用,并且懂得布局的原則和道理,基本上就可以“高枕無憂”了,至于涉及動(dòng)態(tài)布局、動(dòng)畫布局等知識(shí),后續(xù)有時(shí)間會(huì)更新文檔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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