前言
本文要讲述的是如何使用 YogaKit
提供的 FlexBox 布局能力来实现类似 Xib 的功能(Xib 布局是 AutoLayout)。
GitHub 地址:https://github.com/danleechina/YogaLayout
构想
Xib(StoryBoard) 的原理
先说一下 Xib(StoryBoard) 的原理:
- 在 Xcode 中使用 Interface Builder 来构建一个界面或者视图。这个过程其实就是创建、修改 XML 界面描述文件。右键 xib 类型的文件,在 Open As 中选择 Source Code,可以看到 xib 类型的文件其实就是苹果用自己定义的一套 XML 来描述视图
- 编译的时候,IDE 会解析项目中所有的 xib 类型的文件,根据苹果自己的一套规则(我们不可知),生成一个可归档的类,然后将该类归档生成一个 nib 类型的二机制文件。如果项目中有使用 xib 的话,解压打包出来的 ipa,就会看到这些 nib 类型的文件,这些文件就是 IDE 在编译时候解析生成的。
- 运行时,当需要展示一个 xib 描述的视图时,iOS 系统则会去加载 nib 文件得到之前编译时候生成的归档类实例,根据这个实例生成视图需要的所有子视图,并设置这些子视图的约束和属性
- 最后如果有设置 IBOutlet 的话,会将生成的视图赋值给其 owner。
想要了解 xib 更加深入的内容可以查看我之前翻译的一篇文章:macOS 和 iOS 中 Nib 文件实现原理以及最佳实践
可以看到,要实现类似 Xib 这样的工具需要如下一些条件:
- 一套类似的 XML 来描述视图的组成(比如某个视图有哪些子视图,它又是那个视图的子视图),配置视图的属性(比如配置 label 的 text 属性),以及定义视图之间的布局(比如使用 autolayout)
- 出于性能考虑,可以提前解析该 XML,生成类似 nib 的类
- 运行时解析该 XML(如果在编译阶段提前解析了,则不需要),将 XML 中视图的组成,视图的属性,视图的布局给提取出来,动态生成相应视图,并配置其属性、子视图、父视图、布局等
- 如果 XML 描述的文件有指定的 Outlet 的话,则将其赋值给相应的类
构想实现
定义 XML 描述视图规则
<UIView id="gradeTipView" style="alignItems:center;justifyContent:center; marginLeft:12;marginRight:8">
<UILabel id="gradeNameLabel" style=""></UILabel>
<StarBarView id="starBarView" style=""></StarBarView>
</UIView>
- 在这里,每一个 XML 标签名字是视图类的名字,以便运行时通过
NSClassFromString
得到类对象,再通过类对象生成该视图类的一个实例 - 每一个 XML 标签有一些属性,比如上面的 id 和 style。id 的作用等同于 IBOutlet,将动态生成的类实例赋值给其 owner,style 的作用是描述布局,然后将其设置到 YogaKit 中。还可以添加别的属性,比如说如果是 UILabel 的话设置
text="我是文本"
来描述其 text 属性,代码中使用 KVC 来实现 - style 属性的值指定多个的时候需要通过分号隔离,键值对通过冒号隔离
- 每一个 XML 标签会包含其他 XML 标签,比如上面 UIView 包含一个 UILabel 标签,一个自定义的 StarBarView 标签,意思就是说该 UIView 有两个子视图,第一个是一个 UILabel,第二个是自定义视图 StarBarView
- 为了简单,每一个类似该 XML 的文件只能有一个根标签
比如不能出现下列场景:
<UIView></UIView>
<UILabel></UILabel>
<UIButton></UIButton>
在这里,根标签有三个,第一个 UIView,第二个 UILabel,第三个 UIButton,这个情况目前不被允许(对 xib 了解的可能会知道从 nib 获取视图对象会返回一个数组,就是说 xib 可以有多个根层级的视图)
实现细节
- 如何解析 XML?为了简单起见我直接使用了苹果官方处理 XML 的类
NSXMLParser
。实现代理方法,调用parse
函数即可完成 XML 解析。有一点要注意,调用parse
函数是同步的,也就是说当parse
函数返回时,如果实现了代理方法,则代理方法均已经被使用过了。 - 解析 XML 的结果是一个树形的数据模型,该树形结构的每一个节点包含视图类的名字,视图的属性(包括视图的布局信息),子视图信息,以及父视图信息(没有父视图意味则是根节点)
- 从树形数据模型的根节点开始动态生成该视图的实例,并配置实例的属性,子视图信息和父视图信息等
- 在 XML 中有时候定义一个视图的宽高时会需要屏幕宽高的信息,并做一下简单表达式的计算,比如视图的高时屏幕的高的 2/3,这里做表达式计算的时候可以使用
NSExpression
来处理。 - 用 Swift 编写的视图类使用 Objective C 来动态生成的时候,需要加上当前二机制文件的名字,这个需要注意
未来
- 支持动态设置文本、颜色、图片、CGRect(结构体) 等属性
- 简化 Swift 中 Outlet 的设置
- 考虑能否去掉 Outlet,使用者传入数据,数据和视图能够双向绑定
- 支持 Debug 模式下不重新编译,仅需更改 XML 文件即可更新视图布局
网友评论