Swift自动布局SnapKit的进阶篇

前言

在上篇文章中,我们初步学习了SnapKit的基础使用方法,文章:Swift自动布局SnapKit的详细使用介绍。一般来说,掌握了那些基本方法的使用,基本上在项目中布局就没多大问题了,你基本可以达到自己想要的效果。但是,技术这东西,永远都不嫌知道的多,嫌多只能证明你懂得真的太少了。闲言一句,在写上篇文章的时候,SnapKitstars9340颗星,而现在,已经达到11608颗星,足足增长了2000多颗星,历时大概7个月GitHub。另外,SnapKit也升级到了4.0版本,在布局上有些许改进,比如适配了最新的装逼神器iPhone XsafeAreaLayoutGuide, 极大的方便我们的开发工作。

回顾

  • 创建一个视图,大小为100, 居中父视图?

在前一篇文章中,我们也介绍过怎么去布局一个视图,包括大小和位置,事实上,有时候采用更加便利的方法是及其明智的,我们可以写成下面这样

class ViewController: UIViewController {
    
    lazy var redView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        redView.backgroundColor = UIColor.red
        view.addSubview(redView)
        redView.snp.makeConstraints { (make) in
            // 宽高相等时,我们直接写把size写成一遍就行,即 width = height = 100
            make.size.equalTo(100)
            make.center.equalToSuperview()
        }
        
    }
}

这样,一个简单的布局就完成了:

Swift自动布局SnapKit的进阶篇_第1张图片
photo
  • SnapKit的 dividedBy、multipliedBy、offset、inset 使用

首先来看一下源代码截图:

Swift自动布局SnapKit的进阶篇_第2张图片
photo

在上篇文章中,我们没细讲dividedBymultipliedBy,主要是我几乎在开发中不用它,其实很多人应该都很少用到它,在特殊情况下或许也能用得到

比如看一下 dividedBy

class ViewController: UIViewController {
    
    lazy var redView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        redView.backgroundColor = UIColor.red
        view.addSubview(redView)
        redView.snp.makeConstraints { (make) in
            // width 是父视图宽的一半
            make.width.equalToSuperview().dividedBy(2)
            // height 是父视图高的三分之一
            make.height.equalToSuperview().dividedBy(3)
            make.center.equalToSuperview()
        }
        
    }
}
Swift自动布局SnapKit的进阶篇_第3张图片
photo

multipliedBy也是同样的道理:

class ViewController: UIViewController {
    
    lazy var redView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let greenView = UIView()
        greenView.backgroundColor = .green
        view.addSubview(greenView)
        greenView.snp.makeConstraints { (make) in
            // 距离父视图顶部64 (一会我们会讲到 iPhone X 的安全区域布局)
            make.top.equalTo(64)
            // 设置视图大小
            make.size.equalTo(CGSize(width: 20, height: 30))
            // x轴方向上居中
            make.centerX.equalToSuperview()
        }
        
        redView.backgroundColor = UIColor.red
        view.addSubview(redView)
        redView.snp.makeConstraints { (make) in
            // width 是绿色视图宽的10倍
            make.width.equalTo(greenView).multipliedBy(10)
            // height 是绿色视图高的5倍
            make.height.equalTo(greenView).multipliedBy(5)
            make.center.equalToSuperview()
        }
        
    }
}
Swift自动布局SnapKit的进阶篇_第4张图片
photo

至于offset(偏移量,也可以叫位移)、inset(插入)其实在之前我们也提到过了,用法也比较容易,我们再来温故一下

class ViewController: UIViewController {
    
    lazy var redView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
      
        redView.backgroundColor = UIColor.red
        view.addSubview(redView)
        redView.snp.makeConstraints { (make) in
            // 距离(偏移)父视图顶部 64
            make.top.equalToSuperview().offset(64)
            // 距离(偏移)父视图左边 150
            make.left.equalToSuperview().offset(150)
            // 距离(偏移)父视图右边 150
            make.right.equalToSuperview().offset(-150)
            // 距离(偏移)父视图底部 500
            make.bottom.equalToSuperview().offset(-500)
        }
        
        
    }
}
Swift自动布局SnapKit的进阶篇_第5张图片
photo

对于上面这个例子,我们使用 inset来代替offset的写法,将会更加简洁明了:

make.edges.equalToSuperview().inset(UIEdgeInsets(top: 64, left: 150, bottom: 500, right: 150))

上面例子说明,布局还需要取巧,一行代码就能搞定的就不要写两行代码,不要蛮横布局,要学会化复杂为简单,化腐朽为神奇,写出诗一般的代码。

进阶

“进阶”前,首先我们会先看一下一个问题,看什么问题呢?

在上一篇文章中,我曾经说过,在SnapKit库里面,有几个容易弄混淆,令部分使用该库的开发者百思不得其的问题,当时我也是其中之一的迷惑者。

在布局的时候,我们经常会看到这样几个属性:

  • equalTo()

  • lessThanOrEqualTo()

  • greaterThanOrEqualTo()

它们之间到底有什么区别?有什么样的作用?在上篇文章中,我说一般情况下我们只需要使用equalTo()就行了,似乎lessThanOrEqualTo()greaterThanOrEqualTo()并没有多大的作用,甚至没有作用。好了,为了弄清楚这个疑惑,我向作者@robertjpayne做了提问, 而他的回复是

Swift自动布局SnapKit的进阶篇_第6张图片
picture
this is managed by Auto Layout from Apple, equalTo will always force a constraint to be 
exactly equal to. greaterThanOrEqualTo will allow the constraint to be at least that value 
or higher, this can happen when you have competing constraints that would otherwise conflict.

什么意思呢?

作者表明

这是由苹果的自动布局机制管理,equalTo()始终强制一个约束恰好等于某一个特定值,由用户来控制。而对于 
greaterThanOrEqualTo 来说,它允许这个约束至少为那个值或者更大的值,这会发生的情况是你的布局约束
互相之间有冲突的时候。

从作者的话也很好简单理解,也就是当你的布局存在冲突或者矛盾的时候,而你恰好使用了lessThanOrEqualTo()或者greaterThanOrEqualTo()的时候,苹果的Auto Layout会在适当的时候给你补齐约束或者可以说优化约束,使你的布局不至于显示错误或者甚至导致程序奔溃。

为了更好说明这个问题,我们举一个例子

class ViewController: UIViewController {
    
    lazy var orangeView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
      
        orangeView.backgroundColor = .orange
        view.addSubview(orangeView)
        orangeView.snp.makeConstraints { (make) in
            // width >= 200
            make.width.greaterThanOrEqualTo(200)
            // width < 100
            make.width.lessThanOrEqualTo(100)
            make.height.equalTo(100)
            make.center.equalToSuperview()
        }
        
    }
}
Swift自动布局SnapKit的进阶篇_第7张图片
photo

好,视图几乎如愿以偿地显示在屏幕上,但是我们来看一下输出log

Swift自动布局SnapKit的进阶篇_第8张图片
picture

恩?布局成功,视图显示正确。日志输出警告,回过头看代码,哪里不对

// width >= 200
make.width.greaterThanOrEqualTo(200)
// width < 100
make.width.lessThanOrEqualTo(100)

显然是宽度约束存在矛盾或冲突,既想要宽度至少为200,又要宽度小于100,告诉我,你小学数学老师是谁?

但是,为什么布局如此之恶劣却又显示出来了呢?好,这就是作者所说的苹果自动布局机制管理了,当我们约束冲突不确定的时候,Apple会根据适当管理一些布局,推敲哪个约束更为合理,忽略不合理的约束。

尽管如此,但是我个人还是提倡使用明确的布局约束,这会让我们很确定,我们想要做什么?正在做什么?这样做是否正确?能不能达到我预期的效果?

SnapKit进阶之 topLayoutGuide 和 bottomLayoutGuide

这两个是UIViewController的属性,之所以要放在这里讲,是因为在SnapKit中,我们也可以使用这两个属性进行布局,至于怎么样布局?我会详细地给大家说明。但是必须要提的是,这两个属性在iOS 11的时候已经被废弃了,有兴趣者可以在你的Xcode中点进去看一下,而废弃的原因是:在iPhone X出来之后,系统也随之升级,增加了安全区域(SafeArea)的概念,我们一会还会继续讲到这个问题, 先来看 topLayoutGuidebottomLayoutGuide

  • topLayoutGuide

代码

class ViewController: UIViewController {
    
    lazy var orangeView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        orangeView.backgroundColor = .orange
        view.addSubview(orangeView)
        orangeView.snp.makeConstraints { (make) in
            make.top.equalTo(topLayoutGuide.snp.top) //  注意这里,topLayoutGuide的顶部
            make.left.equalTo(10)
            make.right.equalTo(-250)
            make.bottom.equalTo(bottomLayoutGuide.snp.bottom)
        }
        
        let blueView = UIView()
        blueView.backgroundColor = .blue
        view.addSubview(blueView)
        blueView.snp.makeConstraints { (make) in
            make.top.equalTo(topLayoutGuide.snp.bottom) //  注意这里,topLayoutGuide的底部
            make.right.equalTo(-10)
            make.left.equalTo(250)
            make.bottom.equalTo(bottomLayoutGuide.snp.bottom)
        }
        
    }
}

iPhone X之前的设备,topLayoutGuide代表的是状态栏status bar)区域,也就高度是 20dp. 然而,iPhone X中,topLayoutGuide代表的也是类似状态栏所在的区域,不过在iPhone X中,这个高度是 44dp, 可能有人已经注意到了,是的,就是一个导航栏的高度,只不过,和iPhone X之前所谓的导航栏(64dp)相比,少了一个状态栏的区域高度,这是iPhone X所带来的安全区域导致。看下面上下截图中的红线绿线这个区域 (注:橙色视图的顶部topLayoutGuide的顶部,蓝色视图的顶部topLayoutGuide的底部

Swift自动布局SnapKit的进阶篇_第9张图片
iPhone X 之前的设备

|
Swift自动布局SnapKit的进阶篇_第10张图片
iPhone X
  • bottomLayoutGuide

代码

class ViewController: UIViewController {
    
    lazy var orangeView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        orangeView.backgroundColor = .orange
        view.addSubview(orangeView)
        orangeView.snp.makeConstraints { (make) in
            make.top.equalTo(topLayoutGuide.snp.top) 
            make.left.equalTo(10)
            make.right.equalTo(-250)
            make.bottom.equalTo(bottomLayoutGuide.snp.top) // 注意这里,bottomLayoutGuide的顶部
        }
        
        let blueView = UIView()
        blueView.backgroundColor = .blue
        view.addSubview(blueView)
        blueView.snp.makeConstraints { (make) in
            make.top.equalTo(topLayoutGuide.snp.top)
            make.right.equalTo(-10)
            make.left.equalTo(250)
            make.bottom.equalTo(bottomLayoutGuide.snp.bottom) // 注意这里,bottomLayoutGuide的底部
        }
        
    }
}

既然在iPhone XiPhone X之前的设备上,topLayoutGuide是有差异的,那么,对于bottomLayoutGuide呢?我们继续来看一下在这个装逼神器出来前后的一个对比(注:橙色视图的底部bottomLayoutGuide的顶部,蓝色视图的底部bottomLayoutGuide的底部

Swift自动布局SnapKit的进阶篇_第11张图片
iPhone X 之前的设备

|
Swift自动布局SnapKit的进阶篇_第12张图片
iPhone X

从上面对比可以看出,对于bottomLayoutGuide来说,在iPhone X之前的设备,它的顶部和底部都是代表在当前控制器View底部; 而对于iPhone X来说,它的底部代表在当前控制器View底部, 但是它的顶部不在和底部一样在当前控制器View的底部,而是向上偏移了一段距离,这段距离为 34dp, 恩?为啥会多出这么一个高度呢?其实这还是和iPhone X所涉及的安全区域有着本质的联系。

小结:SnapKit可以使用topLayoutGuidebottomLayoutGuide对视图进行布局,并且在iPhone X设备上可以避开所谓的安全区域,但是要提醒的是,这两个属性在iOS 11已经被废弃了,尽管现在还可以使用,但也是兼容而已,后期逐渐会被直接从API中移除,所以只是作为了解,不推荐使用。

问题来了,既然不推荐使用该属性,那有使用什么呢?好的,随着Apple海哥iPhone X)的到来,我们可以使用新特性safeAreaLayoutGuide来代替topLayoutGuidebottomLayoutGuide布局,在SnapKit中同样可以用来自动布局,这是SnapKit升级到4.0的时候更新兼容的,所以,对于使用SnapKit4.0一下版本的伙伴,如有需要,请自行升级使用。

下面我们将进入使用SnapKitiPhone X的安全区域的自动布局了。看到这里累了吗?好的,眼神疲劳,我们来活动一下

Swift自动布局SnapKit的进阶篇_第13张图片
girl

SnapKit进阶之 safeAreaLayoutGuide

safeAreaLayoutGuide 的概念

safeAreaLayoutGuide, 安全区域布局对象或属性,在iOS 11开始被引入,主要是用于日常开发中自动布局。必须注意的是,之前我们说的topLayoutGuide 和 bottomLayoutGuideViewController的属性,但是这里的safeAreaLayoutGuide不再是ViewController的属性,而是View的属性。对于我个人来说,我觉得这是一个很合理的改变,毕竟,我们布局针对的是视图,而非控制器,那对于布局对象或者属性,应该被视图所持有。

代码

class ViewController: UIViewController {
    
    lazy var magentaView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        magentaView.backgroundColor = .magenta
        view.addSubview(magentaView)
        magentaView.snp.makeConstraints { (make) in
            make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
            make.left.right.equalToSuperview()
            make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom)
        }
        
    }
}
Swift自动布局SnapKit的进阶篇_第14张图片
iPhone X 之前的设备

|
Swift自动布局SnapKit的进阶篇_第15张图片
iPhone X

结论如下

  • 所谓安全区域,就是截图中品红色区域

  • iPhone X之前, view.safeAreaLayoutGuide(即安全区域)的顶部距离当前窗口顶部一个状态栏的高度(20dp)

  • 对于iPhone X来说,view.safeAreaLayoutGuide(即安全区域)的顶部距离当前窗口顶部一个导航栏的高度(44dp)

  • iPhone X之前, view.safeAreaLayoutGuide(即安全区域)的底部距离就是当前窗口(当前视图)的底部

  • 对于iPhone X来说,view.safeAreaLayoutGuide(即安全区域)的底部距离当前窗口底部 34dp

安全区域小结:

iPhone X在以前设备的基础上,顶部使状态栏增加了24dp的高度,底部直接强制增加了34dp的高度,如果在适配iPhone X上,我们除了可以使用SnapKit通过view.safeAreaLayoutGuide进行自动布局,还可以根据状态栏(status bar)的高度判断是否大于20来设置相应的高度,保证当前视图的布局在安全区域之内。

补充

Snap的优先级

在上篇文章中,我们说过,SnapKit可以通过属性priority来设置优先级,当时我们是通过具体的数字来设置优先级的,事实上,SnapKit有一个内置的优先级,它是一个结构体,在优先级不是需要设置很多的时候,我们完全可以使用SnapKit内置的便利方法来进行优先级的设置

首先我们来看一下内置优先级的源码

Swift自动布局SnapKit的进阶篇_第16张图片

很明显,优先级顺序是:required > high > medium > low

例子:

class ViewController: UIViewController {
    
    lazy var brownView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        brownView.backgroundColor = .brown
        view.addSubview(brownView)
        brownView.snp.makeConstraints { (make) in
            // 设置优先级的4中内置快捷方式
            make.width.equalTo(50).priority(.required)
            make.width.equalTo(100).priority(.high)
            make.width.equalTo(200).priority(.medium)
            make.width.equalTo(300).priority(.low)
            make.height.equalTo(250)
            make.center.equalToSuperview()
        }
        
    }
}
Swift自动布局SnapKit的进阶篇_第17张图片
photo

分别设置了4个优先级,但是required的优先级最高,所以所布局的视图的width50

总结

SnapKit受欢迎程度越来越高,包括我自己也很喜欢使用其来自动布局,屏幕适配,这给我们带来了很大的方便。另外,SnapKit作者也一直在维护这个库,由于Swift现在并非完全成熟和稳定,版本的升级,API的迭代更改都势必会造成开发的困扰,开心和感恩的是,改库一直被热情的开发者所维护,保证我们在新旧项目中都可以版本兼容。下一章,有时间的话,我们会一起走进SnapKit的源码世界,在那里,我们将了解和学习这库的伟大之处。

欢迎加入 iOS(swift)开发互助群:QQ群号: 558179558, 相互讨论和学习!

你可能感兴趣的