簡介
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() // 直接在父視圖居中
}
}
}
效果圖
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))
}
}
}
效果圖
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)
}
}
}
效果圖
視圖的屬性說明
通過上面的大致簡單布局我們對SnapKit有了一個基本的了解,那么, 它的布局屬性是怎么來的呢?和原生的布局類有什么關聯? 下面看一個SnapKit的布局屬性表
從表中,我們知道,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)
}
}
}
效果圖
其實,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()
}
接著,我們來看一下效果圖
很明顯,最后的寬度是以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()
}
效果圖
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()
}
效果圖
從上面我們可以知道, 我們設置了兩個優先級: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
}
- 更新約束(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
,其他三個約束保持不變, 見下圖效果:
顯而易見, 除了top約束, 其他都沒有改變! 也就是說,約束被更新(相當于系統升級一樣,是一個道理)
現在,我們通過UIButton的點擊事件來證明一下制作約束makeConstraints
和updateConstraints
具體的區別在哪里?
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):
點擊事件發生后(圖2)
圖3
圖4
上面,我們知道,視圖 blackView一開始是由四個約束確定位置和大小,在點擊事情響應后,我們又給 blackView 制作(記住,是制作,而不是重做,兩者有明確的區別)了3個約束,分別是
width、height、top
, 那么這樣做問題出現在哪里呢? 第一, 點擊事情發生前(圖1), 在點擊事件發生后(見圖2), 我們發現,blackView
的width、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
圖6
圖7
發現沒有,在將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)
效果圖
圖(2)
圖(3)
我們看到,
blackView
重做了約束, 之前的約束不起任何作用,由于它在重做約束后只有 3 個約束分別是width、height、top
, 但是這里有一個問題,就是這 3 個約束只能確定大小,無法確定視圖的位置, 所以在水平方向上或者左右缺少一個布局條件, 故 blackView整體視圖的x緊靠左邊(默認)! 另外我們發現, 在圖(3)中,右上角出現了一個感嘆號“!”, 那是因為告訴你缺少了一個約束條件:x-xcode-debug-views://7f81fcbc7900: runtime: Layout Issues: Horizontal position is ambiguous for UIView.
小結
通過以上學習,我們或深或淺地學習了布局三方庫SnapKit
的使用, 我相信,只要將上述布局會使用,并且懂得布局的原則和道理,基本上就可以“高枕無憂”了,至于涉及動態布局、動畫布局等知識,后續有時間會更新文檔。