Swift自動布局SnapKit的詳細使用介紹

簡介

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

Snapkit的安裝(CocoaPods)

環境配置要求:

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

安裝

在已經安裝CocoaPods的前提下, 即可以進行下列步驟。

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

platform :ios, '10.0'

use_frameworks!

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

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

Snapkit的布局使用

1、 實現一個寬高為100,居于當前視圖的中心的視圖布局,示例代碼如下

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)       // 位于當前視圖的中心
        }

    }
}

更簡潔的寫法可以

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)    // 鏈式語法直接定義寬高
            make.center.equalToSuperview()    // 直接在父視圖居中
        }

    }
}

效果圖

1.png

2、View2位于View1內, 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)      // 當前視圖的頂部距離父視圖的頂部:20(父視圖頂部+20)
               make.left.equalToSuperview().offset(20)     // 當前視圖的左邊距離父視圖的左邊:20(父視圖左邊+20)
              make.bottom.equalToSuperview().offset(-20)  // 當前視圖的底部距離父視圖的底部:-20(父視圖底部-20)
              make.right.equalToSuperview().offset(-20)   // 當前視圖的右邊距離父視圖的右邊:-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、布局一個視圖view2, 讓它的水平中心線小于等于另一個視圖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)
            // 設置寬、高
            make.width.height.equalTo(100)
            // 水平中心線<=view1的左邊
            make.centerX.lessThanOrEqualTo(view1.snp.leading)
         }

    }
}

效果圖

3.png

視圖的屬性說明

通過上面的大致簡單布局我們對SnapKit有了一個基本的了解,那么, 它的布局屬性是怎么來的呢?和原生的布局類有什么關聯? 下面看一個SnapKit的布局屬性表

4.png

從表中,我們知道,Snapkit的布局屬性都是源自于系統的NSLayoutAttribute,那么,NSLayoutAttribute是個什么呢?其實,它在swift中是一個枚舉,內部列舉了很多布局屬性諸如top、left、leading、centerX等,Snapkit的布局屬性與它們都存在一一的對應關系。

Snapkit 的 greaterThanOrEqualTo 屬性

如果想讓視圖View2的左邊>=父視圖View1的左邊, 這時我們就可以用到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)
            // 設置寬、高
            make.width.height.equalTo(100)
            // 水平中心線<=view1的左邊
            make.left.greaterThanOrEqualTo(view1)

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

效果圖

5.png

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

SnapKit的greaterThanOrEqualTo和lessThanOrEqualTo聯合使用

當我們想要讓某個視圖的width或height大于等于某個特定的值,小于等于某個特定的值的時候,一般而言,Snapkit會以greaterThanOrEqualTo為準,這里舉一個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)為標準的,也可以這樣的,在同時使用兩者的情況下,greaterThanOrEqualTo的優先級略比lessThanOrEqualTo的優先級高。值得一提的是, 在上面的例子中,如果我們只設置make.width.lessThanOrEqualTo(300),那么view2是不會顯示出來的,因為view2不知道你要表達的是要顯示多少,小于等于300,到底是100還是200呢?(這里指針對width和height)所以它不能確定這個約束的值,但是,如果我們單獨設置make.width.greaterThanOrEqualTo(200),那么就和上面的效果一樣,因為它會以200為標準布局約束!

lessThanOrEqualTo的用于上、下、左、右

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

 // 黑色視圖作為父視圖
 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布局靈活性很強, 我們看下面的例子, 他們的效果是一樣的
make.left.equalToSuperview().offset(10)
make.left.equalTo(10)
make.left.equalTo(view1.snp.left).offset(10)
  • 設置視圖的大小(width,height),他們效果是一樣的
make.width.height.equalTo(100)
或
make.width.equalTo(100)
make.height.equalTo(100)
或
make.size.equalTo(CGSize(width: 100, height: 100))
  • 優先級(priority)

我們來看一下Snapkit的優先級設置, 優先級都是附加在約束鏈的末尾處,看下面的使用方法

// 黑色視圖作為父視圖
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

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

  • 更新約束(引用約束)

我們可以通過保存某一個約束布局來更新相應的約束,或者保存一組約束布局到一個數組中更新約束, 具體看下面代碼

// 保存約束(引用約束)
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)

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

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

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

  • 說明3:這個方法可以調用多次,會相應setNeedsUpdateConstraints, 在控制器中,可以寫在override func updateViewConstraints()方法里面(當然也可以寫在你想要什么時候更新的點擊事件里面)

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

            // 四個約束確定位置和大小
            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)
        }

        // 根據蘋果,調用父類應該放在末尾
        super.updateViewConstraints()
    }
}

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

10.png

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

現在,我們通過UIButton的點擊事件來證明一下制作約束makeConstraintsupdateConstraints具體的區別在哪里?

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)

}

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

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

}

先看效果圖

點擊事件發生前(圖1):

![Uploading 12_315832.png . . .]

點擊事件發生后(圖2)

12.png

圖3

13.png

圖4

14.png

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

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

接著看點擊事件后的效果圖

圖5

15.png

圖6

16.png

圖7

17.png

發現沒有,在將makeConstraints改變成updateConstraints之后,約束還是4個,snapkit沒有報警告,點擊事件中的width、height、top全部起了作用,而這就是兩者的本質區別:makeConstraints是制作約束,在原來的基礎上再添加另外的約束,也就是畫蛇添足,約束增加,視圖布局就有不確定性,從而有些約束起作用,有些不起作用(如上面的top),snapkit報警告!!!而updateConstraints是更新約束,改變原有約束,約束不會增加,沒經過updateConstraints處理的保持原有約束,經過處理就更新約束,約束不會減少,snapkit不會產生警告,這是正常標準的更新約束的正確方式!!!

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

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

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

效果圖

圖(1)

18.png

效果圖

圖(2)

19.png

圖(3)

20.png

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

小結

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容