簡(jiǎn)介
SnapKit
撬陵,一個(gè)經(jīng)典的Swift
版的第三方庫(kù)江耀,專門(mén)用于項(xiàng)目的自動(dòng)布局,目前在github
上的stars
就高達(dá)9340顆星昂利,這是一個(gè)不小的數(shù)字,亦足以證明它存在的非凡意義和作用铁坎。作者認(rèn)為蜂奸,在iOS開(kāi)發(fā)(swift)中,它是用于項(xiàng)目最優(yōu)秀的自動(dòng)布局的必選庫(kù)之一硬萍。它的作者仍然是寫(xiě)Objective-C
的第三方庫(kù)Masonry
的大牛 - @Robert Payne
窝撵,開(kāi)門(mén)見(jiàn)山,本文將詳細(xì)介紹介紹SnapKit的詳細(xì)使用和安裝襟铭,相信對(duì)于初入門(mén)該庫(kù)的開(kāi)發(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)目中了盖呼。然后,就開(kāi)始講怎么使用它了化撕。
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)前視圖的中心
}
}
}
更簡(jiǎn)潔的寫(xiě)法可以
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(yǔ)法直接定義寬高
make.center.equalToSuperview() // 直接在父視圖居中
}
}
}
效果圖
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)
// 測(cè)試視圖
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)
}
}
}
更簡(jiǎn)潔的寫(xiě)法
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)
// 測(cè)試視圖
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憾朴、布局一個(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)
// 測(cè)試視圖
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)
}
}
}
效果圖
視圖的屬性說(shuō)明
通過(guò)上面的大致簡(jiǎn)單布局我們對(duì)SnapKit有了一個(gè)基本的了解,那么做祝, 它的布局屬性是怎么來(lái)的呢砾省?和原生的布局類有什么關(guān)聯(lián)? 下面看一個(gè)SnapKit的布局屬性表
從表中剖淀,我們知道纯蛾,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)
// 測(cè)試視圖
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)
}
}
}
效果圖
其實(shí),greaterThanOrEqualTo這個(gè)屬性有點(diǎn)多余眯杏,比如上面這行代碼 make.left.greaterThanOrEqualTo(view1) 夜焦, 我們可以換成 make.left.equalToSuperview()或make.left.equalTo(view1.snp.left), 效果是一樣的岂贩,也就是說(shuō)茫经,一般情況下 >= 或 <= 我們都可以直接用equalTo來(lái)代替!
SnapKit的greaterThanOrEqualTo和lessThanOrEqualTo聯(lián)合使用
當(dāng)我們想要讓某個(gè)視圖的width或height大于等于某個(gè)特定的值,小于等于某個(gè)特定的值的時(shí)候卸伞,一般而言抹镊,Snapkit會(huì)以greaterThanOrEqualTo為準(zhǔn),這里舉一個(gè)width的例子荤傲,為了方便垮耳,這里指貼出了viewDidLoad中的代碼(其他沒(méi)必要)
// 黑色視圖作為父視圖
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)
// 測(cè)試視圖
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()
}
接著,我們來(lái)看一下效果圖
很明顯弃酌,最后的寬度是以make.width.greaterThanOrEqualTo(200)為標(biāo)準(zhǔn)的氨菇,也可以這樣的儡炼,在同時(shí)使用兩者的情況下妓湘,greaterThanOrEqualTo的優(yōu)先級(jí)略比lessThanOrEqualTo的優(yōu)先級(jí)高。值得一提的是乌询, 在上面的例子中榜贴,如果我們只設(shè)置make.width.lessThanOrEqualTo(300),那么view2是不會(huì)顯示出來(lái)的妹田,因?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)
// 測(cè)試視圖
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布局靈活性很強(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)
我們來(lái)看一下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)
// 測(cè)試視圖
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()
}
效果圖
從上面我們可以知道, 我們?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的過(guò)程中坎弯,有時(shí)我們可以使用優(yōu)先級(jí)priority來(lái)設(shè)置我們的約束, 另外,值得一提的是抠忘,SnapKit的優(yōu)先級(jí)最大值只能是 1000撩炊, 如果優(yōu)先級(jí)的數(shù)值超過(guò)1000,則運(yùn)行時(shí)就會(huì)Crash崎脉!這里要尤其注意拧咳。
- 更新約束(引用約束)
我們可以通過(guò)保存某一個(gè)約束布局來(lái)更新相應(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)
// 測(cè)試視圖
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)
說(shuō)起這個(gè)updateConstraints, 我也懵逼過(guò)灶体,那么它到底有何作用呢阅签?又怎么用呢?它和一開(kāi)始就使用的makeConstraints又有什么明確的區(qū)別呢蝎抽?請(qǐng)繼續(xù)往下看
說(shuō)明1:如果你這是更新某個(gè)約束或某幾個(gè)約束的常量值政钟,你就可以使用
updateConstraints
而不是makeConstraints
。說(shuō)明2:這個(gè)也是蘋(píng)果推薦用來(lái)添加或更新約束的方式
說(shuō)明3:這個(gè)方法可以調(diào)用多次樟结,會(huì)相應(yīng)
setNeedsUpdateConstraints
, 在控制器中养交,可以寫(xiě)在override func updateViewConstraints()
方法里面(當(dāng)然也可以寫(xiě)在你想要什么時(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ù)蘋(píng)果,調(diào)用父類應(yīng)該放在末尾
super.updateViewConstraints()
}
}
注意:
從上面的代碼中我們很明確地知道瓢宦, blackView
通過(guò)width碎连、height、top驮履、centerX
確定了它本身的大小和位置鱼辙, 但是, 在run
出來(lái)之后疲吸,它的top
改變了距離座每, 從 10
變成了 300
,其他三個(gè)約束保持不變摘悴, 見(jiàn)下圖效果:
顯而易見(jiàn)峭梳, 除了top約束, 其他都沒(méi)有改變蹂喻! 也就是說(shuō)葱椭,約束被更新(相當(dāng)于系統(tǒng)升級(jí)一樣,是一個(gè)道理)
現(xiàn)在口四,我們通過(guò)UIButton的點(diǎn)擊事件來(lái)證明一下制作約束makeConstraints
和updateConstraints
具體的區(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):
點(diǎn)擊事件發(fā)生后(圖2)
圖3
圖4
上面,我們知道蔓彩,視圖 blackView一開(kāi)始是由四個(gè)約束確定位置和大小治笨,在點(diǎn)擊事情響應(yīng)后驳概,我們又給 blackView 制作(記住,是制作旷赖,而不是重做顺又,兩者有明確的區(qū)別)了3個(gè)約束,分別是
width等孵、height稚照、top
, 那么這樣做問(wèn)題出現(xiàn)在哪里呢? 第一俯萌, 點(diǎn)擊事情發(fā)生前(圖1)果录, 在點(diǎn)擊事件發(fā)生后(見(jiàn)圖2), 我們發(fā)現(xiàn)咐熙,blackView
的width弱恒、height
約束改變了,但是top
卻沒(méi)有改變糖声,還是原來(lái)的距離父視圖頂部50
的距離斤彼, 原因在于分瘦,我們?cè)谠瓉?lái)的約束基礎(chǔ)上又添加了多余的約束蘸泻, 也就是說(shuō),約束從4個(gè)變成了7個(gè)(見(jiàn)圖3左邊constraints
)嘲玫, 這樣就產(chǎn)生了約束不明確悦施,進(jìn)而導(dǎo)致snapkit
的警告(見(jiàn)圖4), 這樣布局顯然是不可取的去团,在項(xiàng)目中這樣做極其危險(xiǎn)抡诞,甚至可能會(huì)導(dǎo)致異常奔潰!M僚恪V绾埂!
現(xiàn)在鬼雀, 我們?cè)搶Ⅻc(diǎn)擊事件中的約束布局從makeConstraints
改變成updateConstraints
來(lái)試試兩者有什么區(qū)別(下面只添加了點(diǎn)擊事件的代碼顷窒,其他事重復(fù)的就不多此一舉了)
func buttonAction() {
// 注意這里是updateConstraints, 而不是makeConstraints
blackView.snp.updateConstraints { (make) in
make.width.height.equalTo(20)
make.top.equalTo(300)
}
print("這里試試snapkit有沒(méi)有報(bào)警告")
}
接著看點(diǎn)擊事件后的效果圖
圖5
圖6
圖7
發(fā)現(xiàn)沒(méi)有源哩,在將makeConstraints改變成updateConstraints之后鞋吉,約束還是4個(gè),snapkit沒(méi)有報(bào)警告励烦,點(diǎn)擊事件中的width谓着、height、top全部起了作用坛掠,而這就是兩者的本質(zhì)區(qū)別:makeConstraints是制作約束赊锚,在原來(lái)的基礎(chǔ)上再添加另外的約束治筒,也就是畫(huà)蛇添足,約束增加舷蒲,視圖布局就有不確定性矢炼,從而有些約束起作用,有些不起作用(如上面的top)阿纤,snapkit報(bào)警告>涔唷!欠拾!而updateConstraints是更新約束胰锌,改變?cè)屑s束,約束不會(huì)增加藐窄,沒(méi)經(jīng)過(guò)updateConstraints處理的保持原有約束资昧,經(jīng)過(guò)處理就更新約束,約束不會(huì)減少荆忍,snapkit不會(huì)產(chǎn)生警告格带,這是正常標(biāo)準(zhǔn)的更新約束的正確方式!I餐鳌叽唱!
- 重做約束(remakeConstraints)
重做約束的本質(zhì)就是:去掉已有的所有約束, 重新做約束微宝,記住棺亭,是做約束, 也就是說(shuō)蟋软, 使用了remakeConstraints后镶摘,重做的約束必須要能確定相應(yīng)視圖的大小和位置, 之前makeConstraints的約束已經(jīng)不會(huì)存在了,完全銷毀T朗亍F喔摇!
// 點(diǎn)擊更新/制作約束
func buttonAction() {
// 注意這里是 remakeConstraints
blackView.snp.remakeConstraints { (make) in
make.width.height.equalTo(20)
make.top.equalTo(300)
}
print("這里試試snapkit有沒(méi)有報(bào)警告")
}
效果圖
圖(1)
效果圖
圖(2)
圖(3)
我們看到湿痢,
blackView
重做了約束涝缝, 之前的約束不起任何作用,由于它在重做約束后只有 3 個(gè)約束分別是width蒙袍、height俊卤、top
, 但是這里有一個(gè)問(wèn)題,就是這 3 個(gè)約束只能確定大小害幅,無(wú)法確定視圖的位置消恍, 所以在水平方向上或者左右缺少一個(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é)
通過(guò)以上學(xué)習(xí)佣赖,我們或深或淺地學(xué)習(xí)了布局三方庫(kù)SnapKit
的使用恰矩, 我相信,只要將上述布局會(huì)使用憎蛤,并且懂得布局的原則和道理外傅,基本上就可以“高枕無(wú)憂”了,至于涉及動(dòng)態(tài)布局俩檬、動(dòng)畫(huà)布局等知識(shí)萎胰,后續(xù)有時(shí)間會(huì)更新文檔。