本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!
本篇文章是从6月更文活动中提出的一个问题而来:
顺着掘友的提点,一语惊醒梦中人。
iOS的控件的布局依赖
iOS的UI布局一直都是独树一帜的,如果你尝试过前端的CSS、Flutter的UI编写,甚至是Android通过XML布局,你会发现iOS编写布局的思维逻辑和上面的几种完全没有任何参考意义,基本上属于另起炉灶。
就我个人比较喜欢的UI布局方式是纯代码布局,我曾经在Swift:布局库——SnapKit这篇文章整理一些其使用方式。
通过SnapKit编写的UI布局,都是建立控件与控件之间的依赖上的。
可以说,同一个页面的UI布局,都使用SnapKit编写,可能不同程序员的编码都有可能不同——依赖不同导致。
控件的依赖不同,就会让布局的写法产生微妙的不同,进而影响更多的组件布局。
好了,下面让我们先从一个简单的例子入手吧。
简单按钮的布局
updateConstraints的使用
我们先上一段代码,然后通过Gif操作来进行说明:
import UIKit
import SnapKit
import RxCocoa
class SnapKitLayoutController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
title = "SnapKit的简单布局"
let redButton = UIButton(type: .custom)
redButton.backgroundColor = .red
view.addSubview(redButton)
redButton.snp.makeConstraints { (make) in
make.top.equalTo(view).offset(100)
make.leading.equalTo(view).offset(16)
make.trailing.equalTo(view).offset(-16)
make.height.equalTo(44)
}
/// 按钮的点击事件,更新布局
redButton.rx.tap.subscribe { [weak self, weak redButton] (_) in
/// 避免循环引用使用了weak修饰,guard一把
guard let self = self, let redButton = redButton else { return }
/// 注意这里使用的是update
redButton.snp.updateConstraints { (make) in
make.top.equalTo(self.view).offset(200)
}
}.disposed(by: rx.disposeBag)
let blueButton = UIButton(type: .custom)
blueButton.backgroundColor = .blue
view.addSubview(blueButton)
/// blue的布局完全都是依赖与redButton
blueButton.snp.makeConstraints { (make) in
make.top.equalTo(redButton.snp.bottom).offset(10)
make.leading.trailing.height.equalTo(redButton)
}
}
}
上面这段代码干了两件事情:
-
生成子控件redButton和blueButton,其中blueButton的布局是完全依赖与redButton的。
-
redButton有一个按钮点击事件,点击后,redButton的top相对view的top
数值
会发生变化。
下面,我们看看效果:
redButton向下偏移了,从100通过updateConstraints函数,变成了200。
由于blueButton,是依赖于redButton的底部,它相对于redButton的bottom一直都是10,所以blueButton是跟随redButton相对移动。
我们可以更加细化一下redButton按钮的点击事件,让点击之后,不仅top变化,leading、trailing也随之变化。
关键代码:
/// 按钮的点击事件,更新布局
redButton.rx.tap.subscribe { [weak self, weak redButton] (_) in
/// 避免循环引用使用了weak修饰,guard一把
guard let self = self, let redButton = redButton else { return }
/// 注意这里使用的是update
redButton.snp.updateConstraints { (make) in
make.top.equalTo(self.view).offset(200)
make.leading.equalTo(self.view).offset(50)
make.trailing.equalTo(self.view).offset(-50)
}
}.disposed(by: rx.disposeBag)
点击之后的效果:
redButton的top、leading、trailing的数值都随之变化,因为blueButton的布局是完全依赖redButton的,所以也随之发生变化。
在这一节的最后我们一起看看SnapKit的官方文档中对于updateConstraints的解释:
注意标红的位置:仅更新constant的值,什么意思?
本代码中指redButton对于控制器的self.view的依赖——top、leading、trailing都不会变化,仅仅对其equalTo(self.view)之后的函数offset(value)中的value做了改变。
这也是掘友在我提问的时候的回答——更新约束的常量值。
remakeConstraints的使用
有了之前updateConstraints之前解释,现在remakeConstraints的说明就变得简单多了。这次我们先看官方文档:
remakeConstraints更像makeConstraints,只不过remake会在make之前先移除掉之前存在的布局依赖,然后在重新进行布局。
简单点说就是:砍掉重炼。
我们可以把之前的redButton按钮的点击事件做稍许改动updateConstraints=>remakeConstraints
,看看效果:
/// 按钮的点击事件,更新布局
redButton.rx.tap.subscribe { [weak self, weak redButton] (_) in
/// 避免循环引用使用了weak修饰,guard一把
guard let self = self, let redButton = redButton else { return }
/// 注意这里使用的是update
redButton.snp.remakeConstraints { (make) in
make.top.equalTo(self.view).offset(200)
make.leading.equalTo(self.view).offset(50)
make.trailing.equalTo(self.view).offset(-50)
}
}.disposed(by: rx.disposeBag)
点击之后的效果:
是不是感觉和使用updateConstraints没有什么区别?
记住:remake是移除之前所有的布局,再进行重构,redButton被重构了top、leading、trailing,但是有一个参数没有重构——height。
我们可以看看初始代码:
redButton.snp.makeConstraints { (make) in
make.top.equalTo(view).offset(100)
make.leading.equalTo(view).offset(16)
make.trailing.equalTo(view).offset(-16)
make.height.equalTo(44)
}
所以,如果仔细观察调用remakeConstraints后的效果图,会发现按钮的高度不再是44了。我通过图层也得到了验证:
由于height为44的布局设置被删除了,而在使用remakeConstraints并没有重新设置,于是乎系统给我们设置了34,至于为何是34,其实我还没有想明白。
不过通过这个例子验证了remakeConstraints的效果。
如果想要在使用remakeConstraints函数后,按钮的高度也不改变,那么就在remakeConstraints的闭包中加上这段即可——make.height.equalTo(44)
,将之前的height布局砍掉,然后再重新布局一次。
updateConstraints与remakeConstraints两者的区别
-
updateConstraints不会更改之前控件与其他控件的依赖关系,而仅仅是对其他控件依赖关系的数值进行更新。
-
remakeConstraints就是先进行移除,之前与其他控件的依赖关系全部不要了,然后在重新建立新的依赖关系。
就以上的描述,我个人感觉从性能上updateConstraints应该是优于remakeConstraints的,另外remakeConstraints在进行重新构建的时候需要注意那些不变的依赖,也要重新写进去。
使用updateConstraints的地方可以通过remakeConstraints进行构建,反之则不行。
总结
SnapKit用了这么久,我们总是希望UI的布局不会改变,一蹴而就。
但是实际上业务不会成就我们的一厢情愿,在使用SnapKit的时候合理的使用updateConstraints与remakeConstraints会让UI布局的动态化更简单容易一些。
我也在updateConstraints与remakeConstraints之间傻傻分不清太久,如果不是掘友的提醒,我也不会继续深入研究,当然我厚着脸皮问这种问题也算是自己的优点吧,哈哈。
不懂,多读官方文档是最快最直接寻找答案的方式,我自己也牢牢记住与持续实践。
有关updateConstraints与remakeConstraints更多的例子,大家可以看我开源项目wanandroid客户端中的InfoViewCell.swift
文件,里面有两个不同的Cell,分别通过updateConstraints和remakeConstraints实现布局变化,以达到同样的功能。
RxSwift编写wanandroid客户端现已开源
目前RxSwift编写wanandroid客户端已经开源了——项目链接。记得给个star喔!
附上一张效果图片: