iOS开发--Swift:布局库——SnapKit

iOS开发--Swift:布局库——SnapKit,第1张

如果你是只从事过iOS开发,觉得使用SnapKit(OC中的Masonry)很方便,甚至xib拖拉也不错。

可以说,这些都是iOS开发稀疏平常的日常。

但一旦你学过Flutter/Vue写过UI组件,那么iOS的UI编写真的是有种不忍直视的感觉,可以说是原始社会。

虽然隔壁Android的UI写起来也不会特别友好,但是还是比iOS好。

为啥,因为其他的UI编写基本上都可以既见既所得了,就算犯了错,边看边边调试就行了

只有iOS的需要编译调试。。。编译调试。。。编译调试。。。

而且其他家的UI编写基本上都是一脉相承,前端里面的CSS,在Flutter中可以找到一些命名相同的组件,我们来举个例子来说明一下隔壁构建UI的简单和同化:

下面这个UI如何Flutter、H5、iOS来实现:

如果你正在面试,或者正准备跳槽,不妨看看我精心总结的面试资料:https://gitee.com/Mcci7/i-oser 来获取一份详细的大厂面试资料 为你的跳槽加薪多一份保障

Flutter:

Wrap(
  children: model.children
      .map(
        (topic) => Padding(
          padding: EdgeInsets.all(3.0),
          child: Chip(
            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
            key: ValueKey(topic.name),
            backgroundColor: _getChipBgColor(topic.name),
            label: Text(
              topic.name,
              style: TextStyle(fontSize: 14.0),
            ),
          ),
        ),
      )
      .toList(),
);

H5:


    



iOS:

我需要的代码太多了,这里就贴出来了 T_T

Flutter和H5都是用的wrap作为布局思路,Flutter有wrap组件,而H5直接用flex-wrap的CSS就可以了,而iOS中却没有,虽说很多iOS中的组件Flutter和H5需要自定义,而iOS开箱即用。

不过现在这个情况是,就算设计稿与UI元素都和iOS靠拢,但实际上Flutter和H5在组件上已经有非常成熟的官方组件或者第三方,而iOS却没有。。。加上没有热重载。。。

不过虽然说iOS的布局不好用,甚至不好使,但是SnapKit至少给你一点点曙光,此话怎讲,下面我们来看看同一个布局,使用iOS原生的布局和SnapKit的布局代码量吧。

iOS原生布局 VS SnapKit布局

ios原生布局:

contentView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false

addConstraint(NSLayoutConstraint(item: imageView,
                                 attribute: .leading,
                                 relatedBy: .equal,
                                 toItem: contentView,
                                 attribute: .leading,
                                 multiplier: 1,
                                 constant: 0))

addConstraint(NSLayoutConstraint(item: imageView,
                                 attribute: .top,
                                 relatedBy: .equal,
                                 toItem: contentView,
                                 attribute: .top,
                                 multiplier: 1,
                                 constant: 0))

addConstraint(NSLayoutConstraint(item: imageView,
                                 attribute: .trailing,
                                 relatedBy: .equal,
                                 toItem: contentView,
                                 attribute: .trailing,
                                 multiplier: 1,
                                 constant: 0))

addConstraint(NSLayoutConstraint(item: imageView,
                                 attribute: .bottom,
                                 relatedBy: .equal,
                                 toItem: contentView,
                                 attribute: .bottom,
                                 multiplier: 1,
                                 constant: 0))

SnapKit布局:

contentView.addSubview(imageView)

imageView.snp.makeConstraints { make in
    make.edges.equalTo(contentView)
}

使用原生布局还是SnapKit,不用我多说了。

SnapKit的使用、注意事项与疑惑

就我目前做过的项目,就算里面包含着大量的xib,也基本上会使用SnapKit或者Masonry,虽然说它只是原生Api的一层封装,但其链式调用、函数式编程的思想非常值得我们学习和借鉴,下面说几个使用SnapKit的经验谈:

使用SnapKit前,一定要先将子控件添加到父视图中

parentView.addSubview(subview)

一定一定一定在布局前将子控件添加到父视图中,否则会直接崩溃!!!

leading和left、trailing和right

其实在目前国内App中使用leading与left,trailing与right在正常情况下是等价的,这是因为国内的阅读习惯是从左到右的,不过如果你的App需要在阿拉伯国家上架,他们的布局是从右至左时(比如阿拉伯文) 则会对调。

我个人的习惯是使用leading和trailing,这也可能和我做过国际化有关。

给控件添加、更新约束、引用约束、停用、启用 添加新的约束
contentView.addSubview(imageView)
imageView.snp.makeConstraints { (make) in

}
删除控件以前所有约束,添加新约束

由于imageView已经添加到contentView上了,所以remake的时候不需要调用addSubview的方法

imageView.snp.remakeConstraints { (make) in

}
更新约束,写哪条更新哪条,其他约束不变

不过有的时候更新一条约束的时候可能会崩溃,我在cell中进行update的时候就遇到过,至于原因正在排查,这个时候如果崩溃了,可能考虑使用remakeConstraints已经控件的重新布局,虽然这样会消耗一点性能,但总比崩溃好。

imageView.snp.updateConstraints { (make) in

}
引用约束,声明一个局部变量或者类属性来引用要修改的约束

值得注意的是,topConstraint使用的是可选类型,这样保证了在空时,调用deactivate()和activate()方法不会崩溃。同时设置为全局变量,方便在各种情况进行调用。

var topConstraint: Constraint? = nil

override func viewDidLoad() {
    super.viewDidLoad()        

    let imageView = UIImageView()
    view.addSubview(imageView)

    imageView.snp.makeConstraints { (make) in
        self.topConstraint = make.left.equalToSuperview().offset(100).constraint
        make.right.equalToSuperview().offset(-100)
        make.top.equalToSuperview().offset(100)
        make.bottom.equalToSuperview().offset(-100)
     }
}  

停用

topConstraint?.deactivate()

启用

topConstraint?.activate()
设置约束关系
约束关系说明
equalTo()设置属性等于某个数值
greaterThanOrEqualTo()设置属性大于或等于某个数值
lessThanOrEqualTo()设置属性小于或等于某个数值
multipliedBy()设置属性乘以因子后的值
multipliedBy()设置属性除以因子后的值
设置控件布局属性
约束关系说明
size尺寸 CGSize
width、height宽、高
left边距左边
top边距顶部
right边距右边
bottom边距底部
center、centerX、centerYx,y轴的交汇中心点、 x轴的中心点、y轴的中心点
leading阅读习惯的起始的边距
trailing阅读习惯的终末的边距
设置约束偏移
方法参数说明
offsetCGFloat控件属性相对于参照物偏移多少
insets(MASEdgeInsets insets)UIEdgeInsets控件四边相对于参照物偏移多少

举两个例子:

imageView.snp.makeConstraints { (make) in
    make.left.equalToSuperview().offset(20)
    make.right.equalToSuperview().offset(-20)
    make.top.equalToSuperview().offset(20)
    make.bottom.equalToSuperview().offset(-20)
}
/// 具体父控件四周都是20间距
imageView.snp.makeConstraints { (make) in
    make.edges.equalToSuperview().inset(UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20))
}

第2种形式与第1种形式通过left、right、top、bottom写出来的效果一模一样,只是组织语言的形式不一样,SnapKit的编程方式虽然是Swift进行组织的,但是更多是靠其自身构建的DSL进行编码。

设置约束优先级 SnapKit为我们提供了三个默认的方法,required、high、medium、low,优先级最大数值是1000
public static var required: ConstraintPriority {
      return 1000.0
}
public static var high: ConstraintPriority {
      return 750.0
}

public static var medium: ConstraintPriority {
      #if os(OSX)
          return 501.0
      #else
          return 500.0
      #endif
}

public static var low: ConstraintPriority {
      return 250.0
}
自己设置优先级的值,可以通过priority()方法来设置
imageView.snp.makeConstraints { (make) in
    make.center.equalToSuperview()
    make.width.equalTo(100).priority(ConstraintPriority.low)
    make.height.equalTo(50).priority(800)
}
复制代码

其实这个优先级的使用,我自己在开发过程中很少使用到,还需要更多的学习和探索。

SnapKit与UIScrollView

很多新手将在将子控件放入UIScrollView进行布局的时候,经常会发现ScrollView滑不动了。

首先要理解一点核心思想:UIScrollView是依靠与其子视图(subview)之间的约束来确定ContentSize的大小。为什么这么说呢?

这是因为UIScrollView是个非常特殊的UIView,对于UIScrollView的subview来说,它的leading/trailing/top/bottom的space是相对于UIScrollView的contentSize而不是bounds来确定的,换句话说:UIScrollView与其subview之间相对位置的约束并不会直接用于frame的计算,而是会转化为对contentSize的计算。当UIScrollView知道了上下左右的约束分别指向subview的什么位置之后,只要subview的位置固定下来了,那么UIScrollView的contentSize的大小就确定下来了。

但是当我们尝试使用UIScrollView和它subview的leading/trailing/top/bottom来互相决定大小的时候,会出现Has ambiguous scrollable content width/height的 warning。

根据经验,习惯的做法是在UIScrollView和它原来的subviews之间增加一个contentView,依靠contentView来确定contentSize。

代码如下图所示:

class ViewController: UIViewController {

    lazy let scrollView = UIScrollView()
    lazy let contentView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        view.addSubview(scrollView)
        scrollView.snp.makeConstraints { (make) in
            make.edges.equalTo(view)
        }

        /// 添加容器视图
        scrollView.addSubview(contentView)
        contentView.snp.makeConstraints { (make) in
            make.top.bottom.equalTo(scrollView)
            make.left.right.equalTo(view) // 确定的宽度,因为垂直滚动
        }

        let label1 = UILabel()

        /// 注意将组件添加到contentView上,而不是scrollView上面
        contentView.addSubview(label1)
        label1.numberOfLines = 0
        label1.backgroundColor = .yellow

        label1.snp.makeConstraints { (make) in
            make.left.right.equalTo(contentView).inset(20)
            make.top.equalTo(contentView).offset(20)
        }

        let label2 = UILabel()
        /// 注意将组件添加到contentView上,而不是scrollView上面
        contentView.addSubview(label2)
        label2.numberOfLines = 0
        label2.backgroundColor = .red

        label2.snp.makeConstraints { (make) in
            make.left.right.equalTo(label1)
            make.top.equalTo(label1.snp.bottom).offset(20)
            /// 底部约束一定要添加,用于告诉contentView在哪里,不然不能够确定contentSize。
            make.bottom.equalToSuperview() 
        }

        label1.text = """
        hi,掘友们!
        你们发现没,为什么程序员的技术水平差不多,有的升职加薪机会多,有的不去找工作,总有工作找他们?同等“硬件”条件下,为啥竞争力不一样呢?
        很主要的一个原因是,大家的影响力不一样。写博客、做分享、写开源…… 这些都是提高影响力的方式,很多技术达人,甚至靠输出自己的技术知识和经验,把副业做成了主业。当然,这样的影响力不是一天一夕养成的,需要持续输出,积点成势。
        很多人也立了Flag要每天写点东西,可总是坚持不了两天,Flag立了倒,倒了立,一直得不到正反馈,最后自己都气馁了。这一次,我们帮你培养长期坚持且正向激励的好习惯,扶稳Flag!
        """

        label2.text = """
        小王1人更文了28天,小王将获得3千元以内等值奖品,比如Gopro hero8。如果小王同时被幸运大奖砸中,小王将在幸运大奖和一等奖之内选择其一。
        小王和小李共2人完成了27天更文挑战,小王和小李每人获得1500元等值奖品,比如AirPos2代。
        小李更满30天,除了一等奖之外,还将获得满勤奖奖励。
        小王等共6人完成了27天更文挑战,小王等6人每人可选833元以内等值奖品。
        小王更文了23天,可选择二等奖/三等奖中的一种。
        """
    }
}

如果文本还不够使得scrollView进行滑动,请在label1和label2中的text中多随意写些字符串。

其他选择

随着移动端App向着大前端的思路进化,目前iOS和Android的UI开发工具都在向声明式演进,iOS有SwiftUI,Android有ComposeUI,加上跨平台的Flutter,其实基本上都是一个模子刻出来的,只是语言不同而已。

另外iOS的OC中还有一个FlexLib 库。

该布局框架基于flexbox模型,这个模型是web端的布局标准。基于flexbox模型,FlexLib提供了强大的布局能力,并且易于使用。

之前有同事弄过,用过的都说好,我也准备探索一下。

如果你正在面试,或者正准备跳槽,不妨看看我精心总结的面试资料:https://gitee.com/Mcci7/i-oser 来获取一份详细的大厂面试资料 为你的跳槽加薪多一份保障

明日继续

终于挨过了周末更文,最近工作上因为iOS与H5的交互非常多,所以考虑下周会插播一些工作上的问题,进行存档。还望大家海涵。

作者:season_zhu
链接:https://juejin.cn/post/6970515865003360263

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/994414.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存