在平时开发过程中,用的最多的布局方式当然就是苹果自带的AutoLayout,但是使用的过程中也发现AutoLayout自身也有很多的问题。在我们的项目中,由于展现逻辑比较灵活,视觉每期的变化比较大,在使用过程中,很大一部分时间都在调整视觉中,所以想引入一个更简单的布局方式,这就是大名鼎鼎的Flex,也叫箱型布局。
首先来看看我们在AutoLayout中遇到的几个问题
AutoLayout自身的API简直反人类,所以大家使用过程中往往会引入第三方库(Masonry, PureLayout)。Masonry看上去更加直观,但是不太符合OC的编码习惯,PureLayout更符合OC的规范,使用起来也稍复杂点。两者都有很多的使用者。虽然第三方库简化了API,但并没有简化场景。
另一个AutoLayout令人诟病的问题,就是当视图增加的时候,计算复杂度成倍上升。AutoLayout依靠解线性方程组,所以越多的视图性能下降非常大。
在使用场景上,举几个例子。
|---- View1 ----|
|---- View2 ----|
|---- View3 ----|
如果要排上图3个View的时候,AutoLayout必须指定相互间的依赖。
如果仅仅如此还好解决,那么现在告诉你,View1可能是个1行或者2行
的Label,那么如何平均分配之间的大小和间隔呢?
如果这样还是容易解决,那么再告诉你,View2可能是个没有内容的视图
,当没有视图的时候1和3之间要保持之前的间距,这时候,不可能简单的解决了,必须修改依赖,一种方式是删除所有的AutoLayout,重新布局一遍,另外一种是保存AutoLayout实例,修改单个属性。无论如何,都不能避免修改依赖。
如果说到这里你都觉得轻松解决,那么下面这种呢
| |---- View1 ----| | |
| Image |---- View2 ----| Action1 | Action1 |
| |---- View3 ----| | |
这是标准的TableViewCell样式,但是如果说View和Action都是动态的,可能会被隐藏,但隐藏的时候不能空着一块,垂直的需要居中,水平的需要拉伸。这时候就彻底的蛋疼了,恰好我们的项目中就有很多类似的场景,由此我们很多时间都浪费在调整UI上,又容易漏掉场景导致一些视觉bug。
在一次次的UI修改中,我终于受不了了
在视觉排版这个从印刷术发明就存在的问题,到现在计算机领域,最成熟最靠谱的当然是Web技术,从互联网出现,到现在的响应式界面,web经历了历史的历练,从而产生了一套稳定靠谱的布局方式,而最新的就是不久前被加入标准的Flex布局(也可以称为Box布局)。
关于布局使用,有很多有名的介绍文章,我作为一个半吊子人士,就不做详细介绍了。简单来说,就是把每个视图都看作一个箱子,拥有内边距外边距,然后根据一定顺序排布下去,相互间不会产生依赖,当某个视图的属性display: none
,自动忽略该视图。
比AutoLayout好的地方
首先,Flex计算简单,不会因为视图的增加性能急剧下降。
同时也可以方便的解决上述的几种场景,可以动态的根据视图来排版,而不需要相对的去设置,同时拉伸和挤压也很好控制(flex-grow, flex-shrink)。
甚至可以解决部分需要UITableView或者UICollectionView才比较好解决的问题。举个例子,微信九宫格图片,如果不是用UICollectionView来做,那么可能你需要保存9个实例,然后在layoutSubview里面做各种判断,计算frame,那简直是太恐怖了!看到这样的代码谁还有兴趣读下去吗。而使用UICollectionView则可以很好的解决布局问题,还能复用,但是很多场景下并不会产生复用,而使用UICollectionView又会新引入很多层视图层级和一些不需要的功能,虽然很好解决了问题,但并不是特别简单。那么这时候Flex布局的优势就特别明显了,只要flex-wrap
属性就可以搞定一切。
总的来说,使用简单,学习成本低,性能也很不错,兼容性也高,这也是很多人为什么会嘲笑苹果自己搞了一套复杂无比的布局系统。
缺陷
Flex布局最大的缺陷就是视图层级的增加,每个箱子都是一个视图,看web的代码可以发现无数的div
,单纯的为了实现一套布局。不过对于移动端来说,一个视图不会拥有太多的元素,可以说这个问题没有那么的严重。
我也在考虑是否可以创建一层virtual view,可以用来代替view作为箱型容器,或者把布局系统移除view作为容器这一web上的逻辑,分离布局和view。无论怎么样这两种方案都会增加复杂度。
同时,由于原生并不提供Flex支持,所以需要自己引入Flex库,并且需要在layoutSubview
或者viewDidLayoutSubview
中,手动触发计算。关于这个问题,可能自己创建系列Flex根控件比较好。
目前使用Flex的项目,或者类似的功能
现在最有名的方案是Facebook的Yoga,遗憾的是iOS端的YogaKit正处于开发阶段,可能不是那么的稳定。
正在使用Yoga的开源项目也有很多,有名的有AsyncDisplayKit
, React Native
, weex
。所以从目前使用场景来看是没有什么问题的。
比较相似的功能其实早有方案,比如Android的LinearLayout和iOS的UIStackView,可以说这两者都是阉割版的Flex。
YogaKit
我们来看下YogaKit的一些API。不要问我为什么是swift,老外现在都在玩swift。
root.configureLayout { (layout) in
layout.isEnabled = true
layout.width = YGValue(containerSize.width)
layout.height = YGValue(containerSize.height)
layout.alignItems = .center
layout.justifyContent = .center
}
如果是swift,将会非常简单,枚举的一半都被省略了,但是OC可能会稍微麻烦一点。API也是几乎参考的Masonry,重要的是,再也不会出现依赖了!!!感觉棒棒的。
同时Yoga不是完全的实现了Flex的功能,他是为移动端、客户端专门定制的布局系统,所以也有部分Flex没有的功能,也有部分Flex属性没有效果。
同时Yoga非常的精简,核心代码只有3000多行的c语言,引入成本也非常的低,所以决定在下一期实战型的使用下,来解决一直遗留的问题。
更多的考虑
在项目的过程中,作为程序员,都希望代码能够复用,提高稳定性。然而现实是残酷的,其他人并不会替你思考这些问题,所以就有很多场景,明明看着一模一样,但是某几个字体就是不一样,大小就是差那么几个像素。那么要处理这种问题,一般有两种方案。
1,继承,基类写基本成员,子类来写布局和属性。这样会导致很多子类,不熟悉的人会很疑惑这些东西都在哪里用的。
2,增加style属性,使用style来重写布局和属性,这样可能会随着类型的增多switch-case也增加。
同样,这里web也给了我们一个很好的思路。内容-样式分离,我们可以做一套类似于css的系统,使用class来设置布局样式,这样布局样式也可以复用了!
当然这个思路有部分人已经做好了开源库,css样式直接应用到控件。但是感觉没有那么完美,所以以后可以考虑下如何更好的把样式布局给统一到外部。