SnapKit中updateConstraints与remakeConstraints使用与区别

3,892 阅读6分钟

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

本篇文章是从6月更文活动中提出的一个问题而来:

image.png image.png

顺着掘友的提点,一语惊醒梦中人。

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数值会发生变化。

下面,我们看看效果:

RPReplay_Final1626660544.2021-07-19 10_10_45.gif

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)

点击之后的效果:

Screen Shot 2021-07-19 at 10.24.02.png

redButton的top、leading、trailing的数值都随之变化,因为blueButton的布局是完全依赖redButton的,所以也随之发生变化。

在这一节的最后我们一起看看SnapKit的官方文档中对于updateConstraints的解释:

image.png

注意标红的位置:仅更新constant的值,什么意思?

本代码中指redButton对于控制器的self.view的依赖——top、leading、trailing都不会变化,仅仅对其equalTo(self.view)之后的函数offset(value)中的value做了改变。

这也是掘友在我提问的时候的回答——更新约束的常量值。

remakeConstraints的使用

有了之前updateConstraints之前解释,现在remakeConstraints的说明就变得简单多了。这次我们先看官方文档:

image.png

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)

点击之后的效果:

Screen Shot 2021-07-19 at 10.55.08.png

是不是感觉和使用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了。我通过图层也得到了验证:

image.png

由于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喔!

附上一张效果图片:

RPReplay_Final1625472730.2021-07-05 16_13_58.gif