作为Bee系列解析的最后一个部分,本文分享一下Bee的UI开发。Bee的UI开发很有特点:
- BeeUI的核心是XML布局和风格。支持CSS语法,支持额外的布局优化,支持更统一方便的UISignal事件。感觉吸收了QT和JS,CSS各自UI开发的长处(布局,事件,CSS标签)。
- BeeUI的内部还是通过UIView的层级和frame的设定实现自动布局,支持原生自定义控件。。
- Bee在UI容器中引入了UILayout树,每个节点是一个BeeUILayout对象,附着在对应的UIView对象上管理布局和风格。
- Bee的UI开发混合了多种UI开发的模式,可以非常灵活地组合,但得心应手地掌握取舍需要时间和学习成本。
本文试图对比开发手册(代码目录中./document/developer_manual)和XML模板说明(
https://github.com/gavinkwoe/BeeFramework/wiki/Bee-Templates-Manual),对BeeUI的实现原理,和用途做更深入的讲解,再举一些例子来说明自动布局的高级用法。来一些问题做为提纲:
- Bee有哪些UI容器,XML template和原生xib, storyboard相比,各有哪些优缺点和适用的场合?
- Bee有哪些UI控件,有何特点和适用场合?
- BeeUILayout是怎么实现自动布局的?有自定义布局需求的时候,该怎么去做?需要算UIView的frame吗?能使用原生基于Constraint的自动布局吗?如何选择?
- 如何快速掌握XML Template的书写和调试?
A1: 在前文提到BeeUIRouter和BeeUIBoard继承自ViewController, 区别是BeeUIRouter用于rootViewController, BeeUIStack封装了UINavigationController。一般BeeUIRouter是单件,管理若干BeeUIStack, BeeUIStack管理若干BeeUIBoard, 而BeeUIBoard就是页面。代码的使用比较简单,可以参考开发者手册。优缺点和使用场合见下表:
| 优点 | 缺点 | 备注 |
StoryBoard | 直观 | 不利于多人协同 |
|
Xib | 适用广泛 | Delegate/source/bind/Auto size复杂,维护代价稍高 | 也可以选择BeeUIBoard+Xib |
Bee XML容器 | 直观,维护容易 | 没有原生功能丰富,没有Constraint auto size等。 | 也可以支持自定义控件 |
那么,我究竟何时该使用XML模板,何时使用自定义控件?
除了支持遗留的自定义控件代码,个人认为XML模板提供的核心功能是布局/风格描述和自动布局。如果某个界面你不需要自动布局,那么就不要写XML,在代码里直接去写布局。比如浏览器界面,或者即时通信的泡泡消息界面等。
A2:BeeUI的页面上的控件主要是BeeUICell类, 继承自UIView,开发者可以用这个来实现自定义的格子控件。Bee也封装了大多数的常用原生控件,具体支持的类型可见Bee_UITemplateParserXML.m的load函数。可见Bee没有封装TableView, 一般使用BeeUIScrollView和BeeUICell代替。Bee的控件封装优点是引入了自动布局,CSS风格,使用简洁。如果需要使用TableView, CoreText, PickerView, Mapkit, GLkit, SceneKit View的时候,还是需要用原生的View。
A3: 如果使用Bee XML template而不使用xib, 是不能使用apple基于Constraint的自动布局的,原因是Bee自己实现了一套自动布局的机制。个人感觉这套布局机制基本能实现常规的布局需求,但要用好需要经验并了解原理。适配多种分辨率的时候,在XML template中似乎需要使用@media(device:){}语句块对特殊的尺寸布局,用百分比来布局会不够精确。关于BeeUILayout:
- 被用来描述某个UI控件的布局信息,不是真正的UIView对象,只是UIView之间关系的描述,包含布局关系,风格等。
- Bee在创建UI控件之前,会解析XML,读入布局树和CSS风格信息,然后根据布局和风格信息创建UIView树结构。而布局树和风格信息存储在BeeUILayout对象里。
- Bee把对应XML的UIView对象用objc_setAssociatedObject和对应的BeeUILayout对象关联,每个BeeUILayout会持有子BeeUILayout对象集合(childs属性)。
- UI控件的RELAYOUT() block会重新排布子View, 根据是对应的BeeUILayout对象。
可以看到Bee对原生UIView树的扩展了BeeUILayout树。BeeUILayout会在第一次读入XML的时候创建。如果要运行时修改布局,可以:
- 在运行时修改BeeUILayout对象。可以不用在修改UIView的frame,推荐。
- 如果定制BeeUILayout对象难于实现需求,也可以修改所有相关UIView的frame,这样就回到了原生的做法,琐碎难以维护。
现在来看一个具体的例子:首先用向导新建一个Bee View, 名字为TestCell, 基类选择BeeUICell, 选择自动生成XML模板。编辑TestCell.xml:
<ui namespace="TestCell">
<linear id="layoutTest" orientation="v" class="wrapper">
<button id="btn1" class="view gray">Hello</button>
<label id="label1" class="view blue">World</label>
</linear>
<style type="text/css">
.wrapper { width: 40px; height: auto;}
.view { width: 40px; height: 20px;}
.blue { background-color:#007DBC }
.gray { background-color: #AEAEAE }
</style>
</ui>
这里使用"layoutTest"作为垂直的约束,包含一个灰色的Button, "Hello", 和一个蓝色的Label, "World"。看上去应该是这样:
好了,我想插入两个这样的TestCell, 在一个BeeUIScrollView里显示。先封装两个TestCell, 新建一个名为BeCollectionCell的Bee View, xml的代码如下:
<ui namespace="BeCollectionCell">
<TestCell id="test"/>
<TestCell id="test1"/>
<style type="text/css">
#test {
width: 100%;
height: auto;
}
#test1 {
width: 100%;
height: auto;
}
</style>
</ui>
包含两个TestCell, id分别为test, test1, 注意排布规则,在height上都是auto, 而在TestCell里,每个label/button高度是20px。注意XML的控件是可以嵌套的。找个BeeUIScrollView把这个BeCollectionCell插进去,代码可以参考BeeFramework的Dribble sample:
self.list.whenReloading = ^
{
self.list.lineCount = 1;
self.list.total = 1;
BeeUIScrollItem * item = self.list.items[0];
item.clazz = [BeCollectionCell class];
item.size = self.list.size;
item.rule = BeeUIScrollLayoutRule_Tile;
item.size = CGSizeAuto;
}
运行,可以看到效果如下:
没有问题。现在有个如下的需求:
将第1个TestCell的label("World")删除掉,并自动排布。
这个例子的UIView树和BeeLayout的树结构如下:
如题,黄色的矩形节点树代表UIView树,这里因为有两张XML template, 有两棵椭圆节点的BeeUILayout树,注意观察linear layout "layoutTest"的位置,现在要做的就是将左边那棵BeeUILayout树修改下,移除掉label1节点,然后调用BeCollectionCell的RELAYOUT。打开BeCollectionCell.m, 找到layoutDidFinish函数并添加编辑BeeUILayout子树的代码:
- (void)layoutDidFinish
{
// TODO: custom layout here
if(_removed == NO)
{
//找到test的子UICell
TestCell* subCell = (TestCell*)$(self).FIND(@"#test").view;
//获得TestCell的全局layout
BeeUILayout* cellLayout = [subCell layout];
//获得子layout, 名为"layoutTest"
NSMutableArray* children = [cellLayout childs];
BeeUILayout* layout = [children objectAtIndex:0];
//移除layoutTest的label, 位于第2个子layout
NSMutableArray* subs = [layout childs];
[subs removeObjectAtIndex:1];
//成功后下次不再次删除
_removed = YES;
self.RELAYOUT();
}
}
运行程序,成功:
以上就是动态调整Layout的办法,当然也可以直接操作UIView树,用removeFromSuperView方法,然后对剩余的UIView指定新的frame。
A4: 可以参照Bee的XML模板说明(https://github.com/gavinkwoe/BeeFramework/wiki/Bee-Templates-Manual), 注意在布局约束上中Bee主要支持margin, padding, 像素数量,size百分比,停靠(align, float)等约束,理解了这些布局约束和CSS语法,编写BeeUI就没有问题。如果开发者了解Bee的UI是在原生的基础上扩展了Layout和Style, 并能自如得操作Layout和Style,相信使用起来更加顺手。大家注意还有一个很棒的调试功能:在模拟器中调试程序时,可以直接修改xml模板代码,界面排布会即时更新。
最后,谢谢大家,祝大家春节快乐!Bee的分享暂时结束了,欢迎反馈,祝大家新年iOS更上台阶!